From e95f81d2271f0b82f63e16cd6c7e3d0734084e03 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Jul 2017 10:45:40 +0100 Subject: [PATCH 001/130] Contextual breadcrumb title fixes Also moves action buttons into the new breadcrumbs Closes #34030, #34033 --- app/assets/stylesheets/new_nav.scss | 2 +- app/views/groups/show.html.haml | 1 + app/views/layouts/nav/_new_dashboard.html.haml | 2 +- app/views/projects/labels/index.html.haml | 10 +++++++--- app/views/projects/milestones/index.html.haml | 8 ++++++-- .../projects/pipeline_schedules/index.html.haml | 10 +++++++--- app/views/projects/show.html.haml | 1 + app/views/projects/snippets/index.html.haml | 15 ++++++--------- app/views/projects/tree/show.html.haml | 1 + 9 files changed, 31 insertions(+), 19 deletions(-) diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index bfb7a0c7e25..c7ef5a5ae1f 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -323,7 +323,7 @@ header.navbar-gitlab-new { white-space: nowrap; > a { - &:last-of-type { + &:last-of-type:not(:first-child) { font-weight: 600; } } diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 80a8ba4a755..fcdd0a84990 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- @breadcrumb_title = "Group" = content_for :meta_tags do = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") diff --git a/app/views/layouts/nav/_new_dashboard.html.haml b/app/views/layouts/nav/_new_dashboard.html.haml index 7109baa4dad..cfdfcbebc9f 100644 --- a/app/views/layouts/nav/_new_dashboard.html.haml +++ b/app/views/layouts/nav/_new_dashboard.html.haml @@ -3,7 +3,7 @@ = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do Projects - = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do + = nav_link(controller: ['dashboard/groups', 'explore/groups']) do = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do Groups diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index fc72c4fb635..0a63325f26e 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,6 +1,11 @@ - @no_container = true - page_title "Labels" - hide_class = '' + +- if show_new_nav? && can?(current_user, :admin_label, @project) + - content_for :breadcrumbs_extra do + = link_to "New label", new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" + = render "shared/mr_head" - if @labels.exists? || @prioritized_labels.exists? @@ -9,10 +14,9 @@ .nav-text Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } - if can?(current_user, :admin_label, @project) - = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do - New label + = link_to "New label", new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" .labels - if can?(current_user, :admin_label, @project) diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index e1096bd1d67..63522a11b9d 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,5 +1,10 @@ - @no_container = true - page_title 'Milestones' + +- if show_new_nav? + - content_for :breadcrumbs_extra do + = link_to "New milestone", new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' + = render "shared/mr_head" %div{ class: container_class } @@ -9,8 +14,7 @@ .nav-controls = render 'shared/milestones_sort_dropdown' - if can?(current_user, :admin_milestone, @project) - = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do - New milestone + = link_to "New milestone", new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new #{("visible-xs hidden-sm hidden-md hidden-lg" if show_new_nav?)}", title: 'New milestone' .milestones %ul.content-list diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index c296152e54f..127f26899ea 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -4,6 +4,11 @@ - @no_container = true - page_title _("Pipeline Schedules") + +- if show_new_nav? + - content_for :breadcrumbs_extra do + = link_to _('New schedule'), new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' + = render "projects/pipelines/head" %div{ class: container_class } @@ -12,9 +17,8 @@ - schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) } = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope - .nav-controls - = link_to new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' do - %span= _('New schedule') + .nav-controls{ class: ("visible-xs" if show_new_nav?) } + = link_to _('New schedule'), new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' - if @schedules.present? %ul.content-list diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index e1e70a53709..cbf28a61870 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- @breadcrumb_title = "Project" = content_for :meta_tags do = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, rss_url_options), title: "#{@project.name} activity") diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 84e05cd6d88..f6d8f5e8c4f 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,19 +1,16 @@ - page_title "Snippets" +- if show_new_nav? && can?(current_user, :create_project_snippet, @project) + - content_for :breadcrumbs_extra do + = link_to "New snippet", new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" + - if current_user .top-area - include_private = @project.team.member?(current_user) || current_user.admin? = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } - .nav-controls.hidden-xs + .nav-controls{ class: ("visible-xs" if show_new_nav?) } - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do - New snippet - -- if can?(current_user, :create_project_snippet, @project) - .visible-xs -   - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do - New snippet + = link_to "New snippet", new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" = render 'snippets/snippets' diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 96a08f9f8be..722e20763c8 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- @breadcrumb_title = _("Files") - page_title @path.presence || _("Files"), @ref = content_for :meta_tags do From 08a0af9fcf4de2ae1f56c46104eb0ae171db38df Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Jul 2017 11:58:06 +0100 Subject: [PATCH 002/130] moved more action buttons --- app/assets/javascripts/dispatcher.js | 3 +++ app/assets/javascripts/project_select.js | 8 ++++++++ app/assets/stylesheets/new_nav.scss | 10 ++++++++++ app/views/dashboard/_groups_head.html.haml | 9 ++++++--- app/views/dashboard/_projects_head.html.haml | 10 +++++++--- app/views/dashboard/milestones/index.html.haml | 6 +++++- .../shared/_new_project_item_select.html.haml | 15 +-------------- 7 files changed, 40 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 4247540de22..c9b424932aa 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -150,6 +150,9 @@ import initExperimentalFlags from './experimental_flags'; shortcut_handler = new ShortcutsIssuable(); new ZenMode(); break; + case 'dashboard:milestones:index': + new ProjectSelect(); + break; case 'projects:milestones:show': case 'groups:milestones:show': case 'dashboard:milestones:show': diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 9896b88d487..ebcefc819f5 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -104,6 +104,14 @@ import Api from './api'; dropdownCssClass: "ajax-project-dropdown" }); }); + + $('.new-project-item-select-button').on('click', function() { + $('.project-item-select', this.parentNode).select2('open'); + }); + + $('.project-item-select').on('click', function() { + window.location = `${$(this).val()}/${this.dataset.relativePath}`; + }); } return ProjectSelect; diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index c7ef5a5ae1f..e88b383617b 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -388,3 +388,13 @@ header.navbar-gitlab-new { color: $gl-text-color; } } + +.top-area { + .nav-controls-new-nav { + .dropdown { + @media (min-width: $screen-sm-min) { + margin-right: 0; + } + } + } +} diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 4594c52b34b..5a379eae8f4 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -1,3 +1,7 @@ +- if show_new_nav? && current_user.can_create_group? + - content_for :breadcrumbs_extra do + = link_to "New group", new_group_path, class: "btn btn-new" + .top-area %ul.nav-links = nav_link(page: dashboard_groups_path) do @@ -6,9 +10,8 @@ = nav_link(page: explore_groups_path) do = link_to explore_groups_path, title: 'Explore public groups' do Explore public groups - .nav-controls + .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) } = render 'shared/groups/search_form' = render 'shared/groups/dropdown' - if current_user.can_create_group? - = link_to new_group_path, class: "btn btn-new" do - New group + = link_to "New group", new_group_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}" diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 64b737ee886..1f9a5b401b6 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,5 +1,10 @@ = content_for :flash_message do = render 'shared/project_limit' + +- if show_new_nav? && current_user.can_create_project? + - content_for :breadcrumbs_extra do + = link_to "New project", new_project_path, class: 'btn btn-new' + .top-area.scrolling-tabs-container.inner-page-scroll-tabs .fade-left= icon('angle-left') .fade-right= icon('angle-right') @@ -14,9 +19,8 @@ = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do Explore projects - .nav-controls + .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) } = render 'shared/projects/search_form' = render 'shared/projects/dropdown' - if current_user.can_create_project? - = link_to new_project_path, class: 'btn btn-new' do - New project + = link_to "New project", new_project_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}" diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index ef1467c4d78..37dbcaf5cb8 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -2,10 +2,14 @@ - page_title 'Milestones' - header_title 'Milestones', dashboard_milestones_path +- if show_new_nav? + - content_for :breadcrumbs_extra do + = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true + .top-area = render 'shared/milestones_filter', counts: @milestone_states - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true .milestones diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index 9ed844cf5e7..c1acee1a211 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -1,19 +1,6 @@ - if @projects.any? .project-item-select-holder = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }, with_feature_enabled: local_assigns[:with_feature_enabled] - %a.btn.btn-new.new-project-item-select-button + %a.btn.btn-new.new-project-item-select-button{ data: { relative_path: local_assigns[:path] } } = local_assigns[:label] = icon('caret-down') - - :javascript - $('.new-project-item-select-button').on('click', function() { - $('.project-item-select').select2('open'); - }); - - var relativePath = '#{local_assigns[:path]}'; - - $('.project-item-select').on('click', function() { - window.location = $(this).val() + '/' + relativePath; - }); - - new ProjectSelect() From 6184197fb0b2a741616b5a7802bb16daf12bd7a4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Jul 2017 12:51:35 +0100 Subject: [PATCH 003/130] more action button movements fixed eslint failures fixed project select dropdown not working on some pages --- app/assets/javascripts/dispatcher.js | 4 ++++ app/views/dashboard/_snippets_head.html.haml | 9 ++++++--- app/views/dashboard/issues.html.haml | 8 +++++++- app/views/dashboard/merge_requests.html.haml | 6 +++++- app/views/groups/issues.html.haml | 11 +++++++++-- app/views/groups/labels/index.html.haml | 10 +++++++--- app/views/groups/merge_requests.html.haml | 6 +++++- app/views/groups/milestones/index.html.haml | 9 ++++++--- 8 files changed, 49 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index c9b424932aa..51fc8c44c9a 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,4 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ +/* global ProjectSelect */ /* global UsernameValidator */ /* global ActiveTabMemoizer */ /* global ShortcutsNavigation */ @@ -151,6 +152,8 @@ import initExperimentalFlags from './experimental_flags'; new ZenMode(); break; case 'dashboard:milestones:index': + case 'dashboard:issues': + case 'dashboard:merge_requests': new ProjectSelect(); break; case 'projects:milestones:show': @@ -162,6 +165,7 @@ import initExperimentalFlags from './experimental_flags'; case 'groups:issues': case 'groups:merge_requests': new UsersSelect(); + new ProjectSelect(); break; case 'dashboard:todos:index': new gl.Todos(); diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 02e90bbfa55..fd5389106bb 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,3 +1,7 @@ +- if show_new_nav? && current_user + - content_for :breadcrumbs_extra do + = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet" + .top-area %ul.nav-links = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do @@ -8,6 +12,5 @@ Explore Snippets - if current_user - .nav-controls.hidden-xs - = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do - New snippet + .nav-controls.hidden-xs{ class: ("hidden-sm hidden-md hidden-lg" if show_new_nav?) } + = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet" diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index d6b46dee0e4..9f3f6b74008 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -3,9 +3,15 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues") +- if show_new_nav? + - content_for :breadcrumbs_extra do + = link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do + = icon('rss') + = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues' + .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = icon('rss') = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 6f6afe161d1..32c57c26133 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,9 +1,13 @@ - page_title "Merge Requests" - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) +- if show_new_nav? + - content_for :breadcrumbs_extra do + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests' + .top-area = render 'shared/issuable/nav', type: :merge_requests - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests' = render 'shared/issuable/filter', type: :merge_requests diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 182dbe2f98a..735d9390699 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,12 +1,19 @@ - page_title "Issues" +- group_issues_exists = group_issues(@group).exists? = render "head_issues" = content_for :meta_tags do = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues") -- if group_issues(@group).exists? +- if show_new_nav? && group_issues_exists + - content_for :breadcrumbs_extra do + = link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do + = icon('rss') + = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue" + +- if group_issues_exists .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = link_to params.merge(rss_url_options), class: 'btn' do = icon('rss') %span.icon-label diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 2bc00fb16c8..50179a47797 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -1,14 +1,18 @@ - page_title 'Labels' +- if show_new_nav? && can?(current_user, :admin_label, @group) + - content_for :breadcrumbs_extra do + = link_to "New label", new_group_label_path(@group), class: "btn btn-new" + = render "groups/head_issues" + .top-area.adjust .nav-text Labels can be applied to issues and merge requests. Group labels are available for any project within the group. - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } - if can?(current_user, :admin_label, @group) - = link_to new_group_label_path(@group), class: "btn btn-new" do - New label + = link_to "New label", new_group_label_path(@group), class: "btn btn-new" .labels .other-labels diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 45e39252e16..997c82c77d9 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,12 +1,16 @@ - page_title "Merge Requests" +- if show_new_nav? && current_user + - content_for :breadcrumbs_extra do + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request" + - if @group_merge_requests.empty? = render 'shared/empty_states/merge_requests', project_select_button: true - else .top-area = render 'shared/issuable/nav', type: :merge_requests - if current_user - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request" = render 'shared/issuable/filter', type: :merge_requests diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index f91bee0b610..94602b47ab5 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,13 +1,16 @@ - page_title "Milestones" +- if show_new_nav? && can?(current_user, :admin_milestones, @group) + - content_for :breadcrumbs_extra do + = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" + = render "groups/head_issues" .top-area = render 'shared/milestones_filter', counts: @milestone_states - .nav-controls + .nav-controls{ class: ("visible-xs" if show_new_nav?) } - if can?(current_user, :admin_milestones, @group) - = link_to new_group_milestone_path(@group), class: "btn btn-new" do - New milestone + = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" .row-content-block Only milestones from From 19437e47095719d956a3520546292d13845edc3b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Jul 2017 15:38:08 +0100 Subject: [PATCH 004/130] fixed dispatcher case not running for dashboard issues & merge requests --- app/assets/javascripts/dispatcher.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 51fc8c44c9a..c42be091097 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -152,8 +152,6 @@ import initExperimentalFlags from './experimental_flags'; new ZenMode(); break; case 'dashboard:milestones:index': - case 'dashboard:issues': - case 'dashboard:merge_requests': new ProjectSelect(); break; case 'projects:milestones:show': @@ -259,6 +257,7 @@ import initExperimentalFlags from './experimental_flags'; break; case 'dashboard:issues': case 'dashboard:merge_requests': + new ProjectSelect(); new UsersSelect(); break; case 'projects:commit:show': From 6d3a9cc98e1503b0ef49906833aab094a8e6b686 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Jul 2017 14:05:26 +0100 Subject: [PATCH 005/130] fix some breadcrumb titles that are wrong changed color of divider --- app/assets/stylesheets/new_nav.scss | 1 + app/assets/stylesheets/new_sidebar.scss | 2 +- app/views/groups/activity.html.haml | 1 + app/views/projects/activity.html.haml | 1 + app/views/projects/graphs/charts.html.haml | 1 + app/views/projects/graphs/show.html.haml | 1 + app/views/projects/network/show.html.haml | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index e88b383617b..56368fc58ae 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -376,6 +376,7 @@ header.navbar-gitlab-new { &::after { content: "/"; margin: 0 2px 0 5px; + color: rgba($black, .65); } } diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 17f23f7fce3..a961e4332f1 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -22,7 +22,7 @@ $new-sidebar-width: 220px; font-weight: 600; display: flex; align-items: center; - padding: 10px 14px; + padding: 10px 14px 9px; .avatar-container { flex: 0 0 40px; diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml index 3969e56f937..adc379e11d4 100644 --- a/app/views/groups/activity.html.haml +++ b/app/views/groups/activity.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Activity" = content_for :meta_tags do = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index ef8d8051cbf..9ff6ac4f8ad 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- @breadcrumb_title = "Activity" - page_title "Activity" = render "projects/head" diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 464ac34d961..79872c4ea69 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- @breadcrumb_title = "Charts" - page_title "Charts" - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 680f8ae6c8f..239a189caef 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- @breadcrumb_title = "Contributors" - page_title "Contributors" - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index ed6077f6c6b..c89b037f441 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Graph" - page_title "Graph", @ref - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('network') From c85df8105e3b8982ecf6a16f8ae9f3af2f130030 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Jul 2017 17:24:03 +0100 Subject: [PATCH 006/130] improvements to breadcrumb titles, links & nesting --- app/assets/javascripts/group_name.js | 15 +++++++++------ app/helpers/breadcrumbs_helper.rb | 19 +++++++++++++++++++ app/views/admin/appearances/show.html.haml | 1 + .../admin/application_settings/show.html.haml | 1 + .../admin/broadcast_messages/edit.html.haml | 1 + .../admin/broadcast_messages/index.html.haml | 1 + app/views/admin/hooks/index.html.haml | 1 + app/views/admin/services/index.html.haml | 1 + app/views/dashboard/activity.html.haml | 1 + app/views/dashboard/issues.html.haml | 1 + app/views/dashboard/merge_requests.html.haml | 1 + .../dashboard/projects/starred.html.haml | 1 + app/views/dashboard/todos/index.html.haml | 1 + app/views/explore/groups/index.html.haml | 1 + app/views/explore/projects/index.html.haml | 1 + app/views/explore/projects/starred.html.haml | 1 + app/views/explore/projects/trending.html.haml | 1 + app/views/groups/edit.html.haml | 1 + .../groups/group_members/index.html.haml | 1 + app/views/groups/issues.html.haml | 1 + app/views/groups/merge_requests.html.haml | 1 + app/views/groups/new.html.haml | 1 + app/views/layouts/_page.html.haml | 2 +- app/views/layouts/nav/_breadcrumbs.html.haml | 11 ++++++++--- app/views/profiles/audit_log.html.haml | 1 + app/views/profiles/show.html.haml | 1 + .../profiles/two_factor_auths/show.html.haml | 6 ++++++ app/views/projects/activity.html.haml | 3 +++ app/views/projects/boards/_show.html.haml | 4 ++-- app/views/projects/branches/index.html.haml | 3 +++ app/views/projects/commits/show.html.haml | 3 +++ app/views/projects/compare/index.html.haml | 2 ++ app/views/projects/compare/show.html.haml | 2 ++ .../projects/cycle_analytics/show.html.haml | 2 ++ app/views/projects/edit.html.haml | 1 + app/views/projects/graphs/charts.html.haml | 2 ++ app/views/projects/graphs/show.html.haml | 4 ++++ .../merge_requests/creations/new.html.haml | 1 + app/views/projects/network/show.html.haml | 2 ++ app/views/projects/new.html.haml | 1 + .../pipeline_schedules/index.html.haml | 4 ++++ .../projects/pipeline_schedules/new.html.haml | 5 +++++ app/views/projects/pipelines/charts.html.haml | 3 +++ .../projects/settings/ci_cd/show.html.haml | 5 +++++ app/views/projects/tags/index.html.haml | 3 +++ app/views/projects/wikis/show.html.haml | 1 + app/views/search/show.html.haml | 1 + app/views/users/show.html.haml | 2 ++ 48 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 app/helpers/breadcrumbs_helper.rb diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js index 37c6765d942..3e483b69fd2 100644 --- a/app/assets/javascripts/group_name.js +++ b/app/assets/javascripts/group_name.js @@ -5,12 +5,15 @@ export default class GroupName { constructor() { this.titleContainer = document.querySelector('.js-title-container'); this.title = this.titleContainer.querySelector('.title'); - this.titleWidth = this.title.offsetWidth; - this.groupTitle = this.titleContainer.querySelector('.group-title'); - this.groups = this.titleContainer.querySelectorAll('.group-path'); - this.toggle = null; - this.isHidden = false; - this.init(); + + if (this.title) { + this.titleWidth = this.title.offsetWidth; + this.groupTitle = this.titleContainer.querySelector('.group-title'); + this.groups = this.titleContainer.querySelectorAll('.group-path'); + this.toggle = null; + this.isHidden = false; + this.init(); + } } init() { diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb new file mode 100644 index 00000000000..eb8df3bc1ea --- /dev/null +++ b/app/helpers/breadcrumbs_helper.rb @@ -0,0 +1,19 @@ +module BreadcrumbsHelper + def breadcrumbs_extra_links(text, link) + @breadcrumbs_extra_links ||= [] + @breadcrumbs_extra_links.push({ + text: text, + link: link + }) + end + + def breadcrumb_title_link + return @breadcrumb_link if @breadcrumb_link + + if controller.available_action?(:index) + url_for(action: "index") + else + request.path + end + end +end diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml index 454b779842c..3d1929a8b70 100644 --- a/app/views/admin/appearances/show.html.haml +++ b/app/views/admin/appearances/show.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Appearance" - page_title "Appearance" %h3.page-title diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml index ecc46d86afe..2e7f47e261a 100644 --- a/app/views/admin/application_settings/show.html.haml +++ b/app/views/admin/application_settings/show.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Settings" - page_title "Settings" %h3.page-title Settings diff --git a/app/views/admin/broadcast_messages/edit.html.haml b/app/views/admin/broadcast_messages/edit.html.haml index 45e053eb31d..d0e4d4435dd 100644 --- a/app/views/admin/broadcast_messages/edit.html.haml +++ b/app/views/admin/broadcast_messages/edit.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Messages" - page_title "Broadcast Messages" = render 'form' diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml index 4f2ae081d7a..2e4390c3614 100644 --- a/app/views/admin/broadcast_messages/index.html.haml +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Messages" - page_title "Broadcast Messages" %h3.page-title diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index e92b8bc39f4..89592d4464e 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "System Hooks" - page_title 'System Hooks' %h3.page-title System hooks diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml index 50132572096..438907e2345 100644 --- a/app/views/admin/services/index.html.haml +++ b/app/views/admin/services/index.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Services Templates" - page_title "Service Templates" %h3.page-title Service templates %p.light Service template allows you to set default values for project services diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index ad35d05c29a..60dfa75351b 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Activity" - @hide_top_links = true - @no_container = true diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 9f3f6b74008..52e0012fd7d 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Issues" - header_title "Issues", issues_dashboard_path(assignee_id: current_user.id) = content_for :meta_tags do diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 32c57c26133..c3fe14da2b2 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Merge Requests" - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 99efe9c9b86..da80515c17f 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - @no_container = true - page_title "Starred Projects" diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 52d6ebd8a14..9b615ec999e 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Todos" - header_title "Todos", dashboard_todos_path diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index ffe07b217a7..2651ef37e67 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Groups" - header_title "Groups", dashboard_groups_path diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index ec461755103..f00802e0af7 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index ec461755103..f00802e0af7 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index ec461755103..f00802e0af7 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 7d5add3cc1c..2d32d5299b7 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Settings" = render "groups/settings_head" .panel.panel-default.prepend-top-default .panel-heading diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 2e4e4511bb6..9864c805b04 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Members" - page_title "Members" .project-members-page.prepend-top-default diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 735d9390699..a7003f6cd33 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Issues" - page_title "Issues" - group_issues_exists = group_issues(@group).exists? = render "head_issues" diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 997c82c77d9..a43061dacfc 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Merge Requests" - page_title "Merge Requests" - if show_new_nav? && current_user diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 000c7af2326..dc7bd183432 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title 'New Group' - header_title "Groups", dashboard_groups_path diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 1a9f5401a78..f2c16d82d4f 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -14,7 +14,7 @@ = render "layouts/broadcast" = render "layouts/flash" = yield :flash_message - - if show_new_nav? + - if show_new_nav? && !@hide_breadcrumbs = render "layouts/nav/breadcrumbs" %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } .content{ id: "content-body" } diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index 5f1641f4300..171ece25de0 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -1,4 +1,5 @@ - breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize +- breadcrumb_link = breadcrumb_title_link - hide_top_links = @hide_top_links || false %nav.breadcrumbs{ role: "navigation" } @@ -8,12 +9,16 @@ .title = link_to "GitLab", root_path \/ + - if content_for?(:header_title_before) + = yield :header_title_before + \/ = header_title %h2.breadcrumbs-sub-title %ul.list-unstyled - - if content_for?(:sub_title_before) - = yield :sub_title_before - %li= link_to breadcrumb_title, request.path + - if @breadcrumbs_extra_links + - @breadcrumbs_extra_links.each do |extra| + %li= link_to extra[:text], extra[:link] + %li= link_to breadcrumb_title, breadcrumb_link - if content_for?(:breadcrumbs_extra) .breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra = yield :header_content diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index 1a392e29e2a..00d61e5b925 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Authentication log" - page_title "Authentication log" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index bac75a49075..6d97d08ed12 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Profile" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 67792de3870..35a547c2e3f 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,6 +1,12 @@ - page_title 'Two-Factor Authentication', 'Account' - header_title "Two-Factor Authentication", profile_two_factor_auth_path +- @breadcrumb_title = "Two-Factor Authentication" - @content_class = "limit-container-width" unless fluid_layout + +- if show_new_nav? + - content_for :header_title_before do + = link_to "User Settings", profile_path + = render 'profiles/head' - if inject_u2f_api? diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index 9ff6ac4f8ad..23eb1b5290c 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -1,6 +1,9 @@ - @no_container = true - @breadcrumb_title = "Activity" +- if show_new_nav? + - breadcrumbs_extra_links("Project", project_path(@project)) + - page_title "Activity" = render "projects/head" diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index 3622720a8b7..8e070a7cc0c 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -1,10 +1,10 @@ - @no_container = true - @content_class = "issue-boards-content" +- @breadcrumb_title = "Board" - page_title "Boards" - if show_new_nav? - - content_for :sub_title_before do - %li= link_to "Issues", namespace_project_issues_path(@project.namespace, @project) + - breadcrumbs_extra_links("Issues", namespace_project_issues_path(@project.namespace, @project)) - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 4bade77a077..b4d5d045a2a 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -2,6 +2,9 @@ - page_title "Branches" = render "projects/commits/head" +- if show_new_nav? + - breadcrumbs_extra_links("Repository", project_files_path(@project)) + %div{ class: container_class } .top-area.adjust .nav-text diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 7ed7e441344..a824af14171 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -4,6 +4,9 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") +- if show_new_nav? + - breadcrumbs_extra_links("Repository", project_files_path(@project)) + = content_for :sub_nav do = render "head" diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 2cf14859f30..d71bc88298b 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,5 +1,7 @@ - @no_container = true - page_title "Compare" +- if show_new_nav? + - breadcrumbs_extra_links("Repository", project_files_path(@project)) = render "projects/commits/head" %div{ class: container_class } diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index a1bca2cf83a..db722554f1a 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,5 +1,7 @@ - @no_container = true - page_title "#{params[:from]}...#{params[:to]}" +- if show_new_nav? + - breadcrumbs_extra_links("Repository", project_files_path(@project)) = render "projects/commits/head" %div{ class: container_class } diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 7000b289f75..f13499a5c11 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -1,5 +1,7 @@ - @no_container = true - page_title "Cycle Analytics" +- if show_new_nav? + - breadcrumbs_extra_links("Project", project_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('cycle_analytics') diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 78057facde7..61c414b2e13 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -1,4 +1,5 @@ - @content_class = "limit-container-width" unless fluid_layout +- @breadcrumb_title = "Settings" = render "projects/settings/head" .project-edit-container diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 79872c4ea69..f873f629937 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -1,6 +1,8 @@ - @no_container = true - @breadcrumb_title = "Charts" - page_title "Charts" +- if show_new_nav? + - breadcrumbs_extra_links("Repository", project_files_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 239a189caef..67383a23423 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -4,6 +4,10 @@ - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') + +- if show_new_nav? + - breadcrumbs_extra_links("Repository", project_files_path(@project)) + = render 'projects/commits/head' %div{ class: container_class } diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml index 2e798ce780a..59a88c68009 100644 --- a/app/views/projects/merge_requests/creations/new.html.haml +++ b/app/views/projects/merge_requests/creations/new.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Merge Requests" - page_title "New Merge Request" - if @merge_request.can_be_created && !params[:change_branches] diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index c89b037f441..efe0513037e 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -2,6 +2,8 @@ - page_title "Graph", @ref - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('network') +- if show_new_nav? + - breadcrumbs_extra_links("Repository", project_files_path(@project)) = render "projects/commits/head" = render "head" %div{ class: container_class } diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 7b8be58554a..f025a7a55a5 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title 'New Project' - header_title "Projects", dashboard_projects_path - visibility_level = params.dig(:project, :visibility_level) || default_project_visibility diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 127f26899ea..cbda522f28f 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -1,3 +1,5 @@ +- @breadcrumb_title = "Schedules" + - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'schedules_index' @@ -9,6 +11,8 @@ - content_for :breadcrumbs_extra do = link_to _('New schedule'), new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' + - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + = render "projects/pipelines/head" %div{ class: container_class } diff --git a/app/views/projects/pipeline_schedules/new.html.haml b/app/views/projects/pipeline_schedules/new.html.haml index 87390d4dd02..f7db2498148 100644 --- a/app/views/projects/pipeline_schedules/new.html.haml +++ b/app/views/projects/pipeline_schedules/new.html.haml @@ -1,5 +1,10 @@ +- @breadcrumb_title = "Schedules" +- @breadcrumb_link = namespace_project_pipeline_schedules_path(@project.namespace, @project) - page_title _("New Pipeline Schedule") +- if show_new_nav? + - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + %h3.page-title = _("Schedule a new pipeline") %hr diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index 8ffddfe6154..d557126bf3b 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,5 +1,8 @@ - @no_container = true +- @breadcrumb_title = "Charts" - page_title "Charts", "Pipelines" +- if show_new_nav? + - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 00ccc3ec41e..712799dbadf 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -1,5 +1,10 @@ - @content_class = "limit-container-width" unless fluid_layout +- @breadcrumb_title = "Pipelines" - page_title "Pipelines" + +- if show_new_nav? + - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + = render "projects/settings/head" = render 'projects/runners/index' diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 56656ea3d86..c0b801124b9 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -3,6 +3,9 @@ - page_title "Tags" = render "projects/commits/head" +- if show_new_nav? + - breadcrumbs_extra_links("Repository", project_files_path(@project)) + .flex-list{ class: container_class } .top-area.adjust .nav-text.row-main-content diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index f003ff6b63f..0ca811e2959 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,4 +1,5 @@ - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout +- @breadcrumb_title = "Wiki" - page_title @page.title.capitalize, "Wiki" .wiki-page-header.has-sidebar-toggle diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 215dbb3909e..4834441f786 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title @search_term .prepend-top-default diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index f246bd7a586..919ba5d15d3 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,3 +1,5 @@ +- @hide_top_links = true +- @hide_breadcrumbs = true - page_title @user.name - page_description @user.bio - content_for :page_specific_javascripts do From 15a282387938c896ff7b83cb1bb4fe447b856b2b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 7 Jul 2017 11:51:03 +0100 Subject: [PATCH 007/130] fixed breadcrumb titles --- app/views/admin/services/edit.html.haml | 1 + app/views/dashboard/issues.html.haml | 1 + app/views/dashboard/merge_requests.html.haml | 1 + app/views/explore/snippets/index.html.haml | 1 + app/views/groups/new.html.haml | 1 + app/views/layouts/nav/_new_admin_sidebar.html.haml | 2 +- app/views/profiles/accounts/show.html.haml | 1 + app/views/profiles/two_factor_auths/show.html.haml | 9 ++++----- app/views/projects/boards/_show.html.haml | 2 +- app/views/projects/branches/index.html.haml | 2 +- app/views/projects/commits/show.html.haml | 2 +- app/views/projects/compare/index.html.haml | 2 +- app/views/projects/compare/show.html.haml | 2 +- app/views/projects/environments/index.html.haml | 3 +++ app/views/projects/graphs/charts.html.haml | 2 +- app/views/projects/graphs/show.html.haml | 2 +- app/views/projects/jobs/index.html.haml | 3 +++ app/views/projects/network/show.html.haml | 2 +- app/views/projects/new.html.haml | 1 + app/views/projects/services/edit.html.haml | 5 +++++ app/views/projects/tags/index.html.haml | 2 +- app/views/projects/tree/show.html.haml | 2 +- app/views/snippets/new.html.haml | 1 + app/views/snippets/show.html.haml | 1 + 24 files changed, 35 insertions(+), 16 deletions(-) diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml index 53d970e33c1..0a641c3f7a6 100644 --- a/app/views/admin/services/edit.html.haml +++ b/app/views/admin/services/edit.html.haml @@ -1,2 +1,3 @@ +- @breadcrumb_title = "Service Templates" - page_title @service.title, "Service Templates" = render 'form' diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 52e0012fd7d..d2fb4ab101e 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Issues" - @hide_top_links = true - page_title "Issues" - header_title "Issues", issues_dashboard_path(assignee_id: current_user.id) diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index c3fe14da2b2..df2470a0118 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Merge Requests" - @hide_top_links = true - page_title "Merge Requests" - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index e5706d04736..94fc4ac21d2 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "Snippets" - header_title "Snippets", snippets_path diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index dc7bd183432..10eb8ce6cc7 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_link = dashboard_groups_path - @hide_top_links = true - page_title 'New Group' - header_title "Groups", dashboard_groups_path diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index 40c1ca7b53e..4a1d27047ac 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -13,7 +13,7 @@ = nav_link(controller: :dashboard, html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview' do %span - Overview + Dashboard = nav_link(controller: [:admin, :projects]) do = link_to admin_projects_path, title: 'Projects' do %span diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index ed079ed7dfb..c5917ae5aeb 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Account" - page_title "Account" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 35a547c2e3f..e759d0d0a4a 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,12 +1,11 @@ - page_title 'Two-Factor Authentication', 'Account' -- header_title "Two-Factor Authentication", profile_two_factor_auth_path +- if show_new_nav? + - breadcrumbs_extra_links("Account", profile_path) +- else + - header_title "Two-Factor Authentication", profile_two_factor_auth_path - @breadcrumb_title = "Two-Factor Authentication" - @content_class = "limit-container-width" unless fluid_layout -- if show_new_nav? - - content_for :header_title_before do - = link_to "User Settings", profile_path - = render 'profiles/head' - if inject_u2f_api? diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index 364c4c0e473..cb916059afb 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -4,7 +4,7 @@ - page_title "Boards" - if show_new_nav? - - breadcrumbs_extra_links("Issues", project_issues_path(@project.namespace)) + - breadcrumbs_extra_links("Issues", project_issues_path(@project)) - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 4f70a57243b..01442dc517e 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -3,7 +3,7 @@ = render "projects/commits/head" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_files_path(@project)) + - breadcrumbs_extra_links("Repository", project_tree_path(@project)) %div{ class: container_class } .top-area.adjust diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index edebd08dbb5..a949ceefd6e 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -5,7 +5,7 @@ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_files_path(@project)) + - breadcrumbs_extra_links("Repository", project_tree_path(@project)) = content_for :sub_nav do = render "head" diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index d71bc88298b..b245a91dc33 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,7 +1,7 @@ - @no_container = true - page_title "Compare" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_files_path(@project)) + - breadcrumbs_extra_links("Repository", project_tree_path(@project)) = render "projects/commits/head" %div{ class: container_class } diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index db722554f1a..e23fa6068a6 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,7 +1,7 @@ - @no_container = true - page_title "#{params[:from]}...#{params[:to]}" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_files_path(@project)) + - breadcrumbs_extra_links("Repository", project_tree_path(@project)) = render "projects/commits/head" %div{ class: container_class } diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 30cdbc5ae04..595c444f7ea 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -2,6 +2,9 @@ - page_title "Environments" = render "projects/pipelines/head" +- if show_new_nav? + - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag("environments") diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index f873f629937..9f21655a11e 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -2,7 +2,7 @@ - @breadcrumb_title = "Charts" - page_title "Charts" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_files_path(@project)) + - breadcrumbs_extra_links("Repository", project_tree_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 1b4b12ba517..13161d6da22 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -6,7 +6,7 @@ = page_specific_javascript_bundle_tag('graphs') - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_files_path(@project)) + - breadcrumbs_extra_links("Repository", project_tree_path(@project)) = render 'projects/commits/head' diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml index 8604c7d3ea4..f4fe56036b1 100644 --- a/app/views/projects/jobs/index.html.haml +++ b/app/views/projects/jobs/index.html.haml @@ -2,6 +2,9 @@ - page_title "Jobs" = render "projects/pipelines/head" +- if show_new_nav? + - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + %div{ class: container_class } .top-area - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) } diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 1125207ef73..1cb9ce25d2e 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -3,7 +3,7 @@ - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('network') - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_files_path(@project)) + - breadcrumbs_extra_links("Repository", project_tree_path(@project)) = render "projects/commits/head" = render "head" %div{ class: container_class } diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index f025a7a55a5..e2cda1a6c01 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_link = dashboard_projects_path - @hide_top_links = true - page_title 'New Project' - header_title "Projects", dashboard_projects_path diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml index 0f1a76a104a..4179d2dc8a4 100644 --- a/app/views/projects/services/edit.html.haml +++ b/app/views/projects/services/edit.html.haml @@ -1,3 +1,8 @@ +- @breadcrumb_title = "Integrations" - page_title @service.title, "Services" + +- if show_new_nav? + - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + = render "projects/settings/head" = render 'form' diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 7fafbe1c432..805cc960c54 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -4,7 +4,7 @@ = render "projects/commits/head" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_files_path(@project)) + - breadcrumbs_extra_links("Repository", project_tree_path(@project)) .flex-list{ class: container_class } .top-area.adjust diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 3f3fa67f390..ad389e17221 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- @breadcrumb_title = _("Files") +- @breadcrumb_title = _("Repository") - page_title @path.presence || _("Files"), @ref = content_for :meta_tags do diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml index ca8afb4bb6a..513e26e1d81 100644 --- a/app/views/snippets/new.html.haml +++ b/app/views/snippets/new.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - page_title "New Snippet" %h3.page-title New Snippet diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 8818590362d..706f13dd004 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -1,3 +1,4 @@ +- @hide_top_links = true - @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout - page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" From c70bf95c2a5cc0bd8a70d8b65fc678ee1c631420 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 7 Jul 2017 16:04:23 +0100 Subject: [PATCH 008/130] fixed up more breadcrumbs --- app/views/admin/services/index.html.haml | 2 +- app/views/profiles/two_factor_auths/show.html.haml | 2 +- app/views/projects/blob/edit.html.haml | 1 + app/views/projects/blob/new.html.haml | 1 + app/views/projects/blob/show.html.haml | 1 + app/views/projects/project_members/index.html.haml | 4 ++++ app/views/projects/settings/integrations/show.html.haml | 2 ++ app/views/projects/settings/repository/show.html.haml | 4 ++++ 8 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml index 438907e2345..4b5147b9cac 100644 --- a/app/views/admin/services/index.html.haml +++ b/app/views/admin/services/index.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Services Templates" +- @breadcrumb_title = "Service Templates" - page_title "Service Templates" %h3.page-title Service templates %p.light Service template allows you to set default values for project services diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index e759d0d0a4a..5e4bf4297e0 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,6 +1,6 @@ - page_title 'Two-Factor Authentication', 'Account' - if show_new_nav? - - breadcrumbs_extra_links("Account", profile_path) + - breadcrumbs_extra_links("Account", profile_account_path) - else - header_title "Two-Factor Authentication", profile_two_factor_auth_path - @breadcrumb_title = "Two-Factor Authentication" diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index f8cb612a2b4..43fef9f134f 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Repository" - @no_container = true - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 8620a470041..4433aed2023 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Repository" - page_title "New File", @path.presence, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 6e2ae4717cd..a3c6c57607c 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,3 +1,4 @@ +- @breadcrumb_title = "Repository" - @no_container = true - page_title @blob.path, @ref diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 25153fd0b6f..e8f0a8ab24a 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,5 +1,9 @@ +- @breadcrumb_title = "Members" - page_title "Members" +- if show_new_nav? + - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + .row.prepend-top-default .col-lg-12 %h4 diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml index 1d1d0849289..3bbbc0af017 100644 --- a/app/views/projects/settings/integrations/show.html.haml +++ b/app/views/projects/settings/integrations/show.html.haml @@ -1,5 +1,7 @@ - @content_class = "limit-container-width" unless fluid_layout - page_title 'Integrations' +- if show_new_nav? + - breadcrumbs_extra_links("Settings", edit_project_path(@project)) = render "projects/settings/head" = render 'projects/hooks/index' = render 'projects/services/index' diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml index 40ea02abce9..516b126f029 100644 --- a/app/views/projects/settings/repository/show.html.haml +++ b/app/views/projects/settings/repository/show.html.haml @@ -1,5 +1,9 @@ - page_title "Repository" - @content_class = "limit-container-width" unless fluid_layout + +- if show_new_nav? + - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + = render "projects/settings/head" - content_for :page_specific_javascripts do From 8e9ae78007010afb967cdf01f29568744d4c8c7a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 7 Jul 2017 21:50:34 +0100 Subject: [PATCH 009/130] rename method --- app/helpers/breadcrumbs_helper.rb | 2 +- app/views/profiles/two_factor_auths/show.html.haml | 2 +- app/views/projects/activity.html.haml | 2 +- app/views/projects/boards/_show.html.haml | 2 +- app/views/projects/branches/index.html.haml | 2 +- app/views/projects/commits/show.html.haml | 2 +- app/views/projects/compare/index.html.haml | 2 +- app/views/projects/compare/show.html.haml | 2 +- app/views/projects/cycle_analytics/show.html.haml | 2 +- app/views/projects/environments/index.html.haml | 2 +- app/views/projects/graphs/charts.html.haml | 2 +- app/views/projects/graphs/show.html.haml | 2 +- app/views/projects/jobs/index.html.haml | 2 +- app/views/projects/network/show.html.haml | 2 +- app/views/projects/pipeline_schedules/index.html.haml | 2 +- app/views/projects/pipeline_schedules/new.html.haml | 2 +- app/views/projects/pipelines/charts.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 2 +- app/views/projects/services/edit.html.haml | 2 +- app/views/projects/settings/ci_cd/show.html.haml | 2 +- app/views/projects/settings/integrations/show.html.haml | 2 +- app/views/projects/settings/repository/show.html.haml | 2 +- app/views/projects/tags/index.html.haml | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb index eb8df3bc1ea..6eb1060ed4c 100644 --- a/app/helpers/breadcrumbs_helper.rb +++ b/app/helpers/breadcrumbs_helper.rb @@ -1,5 +1,5 @@ module BreadcrumbsHelper - def breadcrumbs_extra_links(text, link) + def add_to_breadcrumbs(text, link) @breadcrumbs_extra_links ||= [] @breadcrumbs_extra_links.push({ text: text, diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 5e4bf4297e0..326a87f579f 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,6 +1,6 @@ - page_title 'Two-Factor Authentication', 'Account' - if show_new_nav? - - breadcrumbs_extra_links("Account", profile_account_path) + - add_to_breadcrumbs("Account", profile_account_path) - else - header_title "Two-Factor Authentication", profile_two_factor_auth_path - @breadcrumb_title = "Two-Factor Authentication" diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index 23eb1b5290c..dc79be269c6 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -2,7 +2,7 @@ - @breadcrumb_title = "Activity" - if show_new_nav? - - breadcrumbs_extra_links("Project", project_path(@project)) + - add_to_breadcrumbs("Project", project_path(@project)) - page_title "Activity" = render "projects/head" diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index cb916059afb..49a92240f91 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -4,7 +4,7 @@ - page_title "Boards" - if show_new_nav? - - breadcrumbs_extra_links("Issues", project_issues_path(@project)) + - add_to_breadcrumbs("Issues", project_issues_path(@project)) - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 01442dc517e..f18a37ba499 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -3,7 +3,7 @@ = render "projects/commits/head" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_tree_path(@project)) + - add_to_breadcrumbs("Repository", project_tree_path(@project)) %div{ class: container_class } .top-area.adjust diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index a949ceefd6e..61724e99bf2 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -5,7 +5,7 @@ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_tree_path(@project)) + - add_to_breadcrumbs("Repository", project_tree_path(@project)) = content_for :sub_nav do = render "head" diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index b245a91dc33..05de21e8dbf 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,7 +1,7 @@ - @no_container = true - page_title "Compare" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_tree_path(@project)) + - add_to_breadcrumbs("Repository", project_tree_path(@project)) = render "projects/commits/head" %div{ class: container_class } diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index e23fa6068a6..42a21d33013 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,7 +1,7 @@ - @no_container = true - page_title "#{params[:from]}...#{params[:to]}" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_tree_path(@project)) + - add_to_breadcrumbs("Repository", project_tree_path(@project)) = render "projects/commits/head" %div{ class: container_class } diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index f13499a5c11..aa5b6348bed 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -1,7 +1,7 @@ - @no_container = true - page_title "Cycle Analytics" - if show_new_nav? - - breadcrumbs_extra_links("Project", project_path(@project)) + - add_to_breadcrumbs("Project", project_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('cycle_analytics') diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 595c444f7ea..d0f723af5bf 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -3,7 +3,7 @@ = render "projects/pipelines/head" - if show_new_nav? - - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_vue') diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 9f21655a11e..36920260ae8 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -2,7 +2,7 @@ - @breadcrumb_title = "Charts" - page_title "Charts" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_tree_path(@project)) + - add_to_breadcrumbs("Repository", project_tree_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 13161d6da22..2d37963fb7d 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -6,7 +6,7 @@ = page_specific_javascript_bundle_tag('graphs') - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_tree_path(@project)) + - add_to_breadcrumbs("Repository", project_tree_path(@project)) = render 'projects/commits/head' diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml index f4fe56036b1..d78891546f7 100644 --- a/app/views/projects/jobs/index.html.haml +++ b/app/views/projects/jobs/index.html.haml @@ -3,7 +3,7 @@ = render "projects/pipelines/head" - if show_new_nav? - - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) %div{ class: container_class } .top-area diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 1cb9ce25d2e..314914fd7b2 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -3,7 +3,7 @@ - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('network') - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_tree_path(@project)) + - add_to_breadcrumbs("Repository", project_tree_path(@project)) = render "projects/commits/head" = render "head" %div{ class: container_class } diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 5f9518e5102..652d52d6814 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -11,7 +11,7 @@ - content_for :breadcrumbs_extra do = link_to _('New schedule'), new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' - - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) = render "projects/pipelines/head" diff --git a/app/views/projects/pipeline_schedules/new.html.haml b/app/views/projects/pipeline_schedules/new.html.haml index f7db2498148..115c43a0aec 100644 --- a/app/views/projects/pipeline_schedules/new.html.haml +++ b/app/views/projects/pipeline_schedules/new.html.haml @@ -3,7 +3,7 @@ - page_title _("New Pipeline Schedule") - if show_new_nav? - - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) %h3.page-title = _("Schedule a new pipeline") diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index ef949ad4443..7cc43501497 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -2,7 +2,7 @@ - @breadcrumb_title = "Charts" - page_title _("Charts"), _("Pipelines") - if show_new_nav? - - breadcrumbs_extra_links("Pipelines", project_pipelines_path(@project)) + - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('graphs') diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index e8f0a8ab24a..74b10574da0 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -2,7 +2,7 @@ - page_title "Members" - if show_new_nav? - - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + - add_to_breadcrumbs("Settings", edit_project_path(@project)) .row.prepend-top-default .col-lg-12 diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml index 4179d2dc8a4..3d1d62b886a 100644 --- a/app/views/projects/services/edit.html.haml +++ b/app/views/projects/services/edit.html.haml @@ -2,7 +2,7 @@ - page_title @service.title, "Services" - if show_new_nav? - - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + - add_to_breadcrumbs("Settings", edit_project_path(@project)) = render "projects/settings/head" = render 'form' diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 8ca15861025..3f118f8933d 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -3,7 +3,7 @@ - page_title "Pipelines" - if show_new_nav? - - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + - add_to_breadcrumbs("Settings", edit_project_path(@project)) = render "projects/settings/head" diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml index 3bbbc0af017..149da96d3f6 100644 --- a/app/views/projects/settings/integrations/show.html.haml +++ b/app/views/projects/settings/integrations/show.html.haml @@ -1,7 +1,7 @@ - @content_class = "limit-container-width" unless fluid_layout - page_title 'Integrations' - if show_new_nav? - - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + - add_to_breadcrumbs("Settings", edit_project_path(@project)) = render "projects/settings/head" = render 'projects/hooks/index' = render 'projects/services/index' diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml index 516b126f029..41080b81bdc 100644 --- a/app/views/projects/settings/repository/show.html.haml +++ b/app/views/projects/settings/repository/show.html.haml @@ -2,7 +2,7 @@ - @content_class = "limit-container-width" unless fluid_layout - if show_new_nav? - - breadcrumbs_extra_links("Settings", edit_project_path(@project)) + - add_to_breadcrumbs("Settings", edit_project_path(@project)) = render "projects/settings/head" diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 805cc960c54..00000e0667c 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -4,7 +4,7 @@ = render "projects/commits/head" - if show_new_nav? - - breadcrumbs_extra_links("Repository", project_tree_path(@project)) + - add_to_breadcrumbs("Repository", project_tree_path(@project)) .flex-list{ class: container_class } .top-area.adjust From dc8597a03c0afda24922f73542efe12d7d85322f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 10 Jul 2017 10:12:59 +0800 Subject: [PATCH 010/130] add russian translations to i18n --- ...34880-add-russian-translations-to-i18n.yml | 4 + lib/gitlab/i18n.rb | 1 + locale/ru/gitlab.po | 1201 +++++++++++++++++ locale/ru/gitlab.po.time_stamp | 0 4 files changed, 1206 insertions(+) create mode 100644 changelogs/unreleased/34880-add-russian-translations-to-i18n.yml create mode 100644 locale/ru/gitlab.po create mode 100644 locale/ru/gitlab.po.time_stamp diff --git a/changelogs/unreleased/34880-add-russian-translations-to-i18n.yml b/changelogs/unreleased/34880-add-russian-translations-to-i18n.yml new file mode 100644 index 00000000000..aed05dd1031 --- /dev/null +++ b/changelogs/unreleased/34880-add-russian-translations-to-i18n.yml @@ -0,0 +1,4 @@ +--- +title: Add Russian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. +merge_request: 12743 +author: Huang Tao diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index f3d489aad0d..4f31169610d 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -12,6 +12,7 @@ module Gitlab 'zh_HK' => '繁體中文(香港)', 'zh_TW' => '繁體中文(臺灣)', 'bg' => 'български', + 'ru' => 'Русский', 'eo' => 'Esperanto', 'it' => 'Italiano' }.freeze diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po new file mode 100644 index 00000000000..7856830987a --- /dev/null +++ b/locale/ru/gitlab.po @@ -0,0 +1,1201 @@ +# SAS , 2017. #zanata +# Huang Tao , 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2017-07-07 08:11-0400\n" +"Last-Translator: SAS \n" +"Language-Team: Russian\n" +"Language: ru\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" + +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%d additional commits have been omitted to prevent performance issues." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link} зафиксировано %{commit_timeago}" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "" + +msgid "About auto deploy" +msgstr "Автоматическое развертывание" + +msgid "Active" +msgstr "Активный" + +msgid "Activity" +msgstr "Активность" + +msgid "Add Changelog" +msgstr "Добавить в журнал изменений" + +msgid "Add Contribution guide" +msgstr "Добавить руководство для контрибьютеров" + +msgid "Add License" +msgstr "Добавить лицензию" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" + +msgid "Add new directory" +msgstr "Добавить новую директорию" + +msgid "Archived project! Repository is read-only" +msgstr "Архивный проект! Репозиторий доступен только для чтения" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Вы действительно хотите удалить это расписание конвейера?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"Ветка %{branch_name} создана. Для настройки автоматического " +"развертывания выберете GitLab CI Yaml-шаблон и зафиксируйте изменения. " +"%{link_to_autodeploy_doc}" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "" + +msgid "Branches" +msgstr "Ветки" + +msgid "Browse Directory" +msgstr "" + +msgid "Browse File" +msgstr "" + +msgid "Browse Files" +msgstr "" + +msgid "Browse files" +msgstr "Просмотр файлов" + +msgid "ByAuthor|by" +msgstr "" + +msgid "CI configuration" +msgstr "Настройка CI" + +msgid "Cancel" +msgstr "Отмена" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "ChangeTypeActionLabel|Отменить в ветке" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeTypeAction|Revert" +msgstr "" + +msgid "Changelog" +msgstr "Журнал изменений" + +msgid "Charts" +msgstr "Графики" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge request" +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "CiStatusLabel|отменено" + +msgid "CiStatusLabel|created" +msgstr "CiStatusLabel|создано" + +msgid "CiStatusLabel|failed" +msgstr "CiStatusLabel|неудачно" + +msgid "CiStatusLabel|manual action" +msgstr "CiStatusLabel|ручное действие" + +msgid "CiStatusLabel|passed" +msgstr "CiStatusLabel|пройдено" + +msgid "CiStatusLabel|passed with warnings" +msgstr "CiStatusLabel|пройдено с предупреждениями" + +msgid "CiStatusLabel|pending" +msgstr "CiStatusLabel|в ожидании" + +msgid "CiStatusLabel|skipped" +msgstr "CiStatusLabel|пропущено" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "CiStatusLabel|ожидание ручных действий" + +msgid "CiStatusText|blocked" +msgstr "CiStatusText|блокировано" + +msgid "CiStatusText|canceled" +msgstr "CiStatusText|отменено" + +msgid "CiStatusText|created" +msgstr "CiStatusText|создано" + +msgid "CiStatusText|failed" +msgstr "CiStatusText|неудачно" + +msgid "CiStatusText|manual" +msgstr "" + +msgid "CiStatusText|passed" +msgstr "CiStatusText|пройдено" + +msgid "CiStatusText|pending" +msgstr "CiStatusText|в ожидании" + +msgid "CiStatusText|skipped" +msgstr "CiStatusText|пропущено" + +msgid "CiStatus|running" +msgstr "CiStatus|выполняется" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "" + +msgid "Commit message" +msgstr "" + +msgid "CommitBoxTitle|Commit" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "CommitMessage|Добавить %{file_name}" + +msgid "Commits" +msgstr "" + +msgid "Commits feed" +msgstr "" + +msgid "Commits|History" +msgstr "Commits|История" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "Сравнение" + +msgid "Contribution guide" +msgstr "Руководство контрибьютора" + +msgid "Contributors" +msgstr "Контрибьюторы" + +msgid "Copy URL to clipboard" +msgstr "Копировать URL в буфер обмена" + +msgid "Copy commit SHA to clipboard" +msgstr "" + +msgid "Create New Directory" +msgstr "Создать новую директорию" + +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" + +msgid "Create directory" +msgstr "Создать директорию" + +msgid "Create empty bare repository" +msgstr "Создать пустой пустой репозиторий" + +msgid "Create merge request" +msgstr "Создать запрос на объединение" + +msgid "Create new..." +msgstr "Новый" + +msgid "CreateNewFork|Fork" +msgstr "CreateNewFork|Форк" + +msgid "CreateTag|Tag" +msgstr "CreateTag|Тэг" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "" + +msgid "Cron Timezone" +msgstr "Временная зона Cron" + +msgid "Cron syntax" +msgstr "Синтаксис Cron" + +msgid "Custom notification events" +msgstr "Пользовательские уведомления о событиях" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" + +msgid "Cycle Analytics" +msgstr "" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "" + +msgid "CycleAnalyticsStage|Code" +msgstr "" + +msgid "CycleAnalyticsStage|Issue" +msgstr "" + +msgid "CycleAnalyticsStage|Plan" +msgstr "" + +msgid "CycleAnalyticsStage|Production" +msgstr "" + +msgid "CycleAnalyticsStage|Review" +msgstr "" + +msgid "CycleAnalyticsStage|Staging" +msgstr "" + +msgid "CycleAnalyticsStage|Test" +msgstr "" + +msgid "Define a custom pattern with cron syntax" +msgstr "Определить пользовательский шаблон с синтаксисом cron" + +msgid "Delete" +msgstr "Удалить" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Description" +msgstr "Описание" + +msgid "Directory name" +msgstr "Наименование директории" + +msgid "Don't show again" +msgstr "Не показывать снова" + +msgid "Download" +msgstr "Загрузить" + +msgid "Download tar" +msgstr "Загрузить tar" + +msgid "Download tar.bz2" +msgstr "Загрузить tar.bz2" + +msgid "Download tar.gz" +msgstr "Загрузить tar.gz" + +msgid "Download zip" +msgstr "Загрузить zip" + +msgid "DownloadArtifacts|Download" +msgstr "DownloadArtifacts|Загрузка" + +msgid "DownloadCommit|Email Patches" +msgstr "DownloadCommit|Email-патчи" + +msgid "DownloadCommit|Plain Diff" +msgstr "DownloadCommit|Plain Diff" + +msgid "DownloadSource|Download" +msgstr "DownloadSource|Загрузка" + +msgid "Edit" +msgstr "Редактировать" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Изменить расписание конвейера %{id}" + +msgid "Every day (at 4:00am)" +msgstr "Ежедневно (в 4:00)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "Ежемесячно (каждое 1-е число в 4:00)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "Еженедельно (по воскресениями в 4:00)" + +msgid "Failed to change the owner" +msgstr "Не удалось изменить владельца" + +msgid "Failed to remove the pipeline schedule" +msgstr "Не удалось удалить расписание конвейера" + +msgid "Files" +msgstr "Файлы" + +msgid "Filter by commit message" +msgstr "" + +msgid "Find by path" +msgstr "" + +msgid "Find file" +msgstr "Найти файл" + +msgid "FirstPushedBy|First" +msgstr "" + +msgid "FirstPushedBy|pushed by" +msgstr "" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "ForkedFromProjectPath|Форк от " + +msgid "From issue creation until deploy to production" +msgstr "" + +msgid "From merge request merge until deploy to production" +msgstr "" + +msgid "Go to your fork" +msgstr "Перейти к вашему форку" + +msgid "GoToYourFork|Fork" +msgstr "GoToYourFork|Форк" + +msgid "Home" +msgstr "" + +msgid "Housekeeping successfully started" +msgstr "" + +msgid "Import repository" +msgstr "" + +msgid "Interval Pattern" +msgstr "" + +msgid "Introducing Cycle Analytics" +msgstr "" + +msgid "Jobs for last month" +msgstr "" + +msgid "Jobs for last week" +msgstr "" + +msgid "Jobs for last year" +msgstr "" + +msgid "LFSStatus|Disabled" +msgstr "LFSStatus|Отключено" + +msgid "LFSStatus|Enabled" +msgstr "LFSStatus|Включено" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Last Pipeline" +msgstr "Последний конвейер" + +msgid "Last Update" +msgstr "Последнее обновление" + +msgid "Last commit" +msgstr "" + +msgid "Learn more in the" +msgstr "" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "Подробнее в|документации по расписаниям конвейеров" + +msgid "Leave group" +msgstr "Покинуть группу" + +msgid "Leave project" +msgstr "Покинуть проект" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Median" +msgstr "" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "New Pipeline Schedule" +msgstr "Новое расписание конвейера" + +msgid "New branch" +msgstr "Новая ветка" + +msgid "New directory" +msgstr "Новая директория" + +msgid "New file" +msgstr "Новый файл" + +msgid "New issue" +msgstr "Новое обращение" + +msgid "New merge request" +msgstr "Новый запрос на объединение" + +msgid "New schedule" +msgstr "Новое расписание" + +msgid "New snippet" +msgstr "Новый сниппет" + +msgid "New tag" +msgstr "Новый тэг" + +msgid "No repository" +msgstr "Нет репозитория" + +msgid "No schedules" +msgstr "Нет расписания" + +msgid "Not available" +msgstr "" + +msgid "Not enough data" +msgstr "" + +msgid "Notification events" +msgstr "Уведомления о событиях" + +msgid "NotificationEvent|Close issue" +msgstr "NotificationEvent|Обращение закрыто" + +msgid "NotificationEvent|Close merge request" +msgstr "Запрос на объединение закрыт" + +msgid "NotificationEvent|Failed pipeline" +msgstr "NotificationEvent|Неудача в конвейере" + +msgid "NotificationEvent|Merge merge request" +msgstr "NotificationEvent|Объединить запрос на слияние" + +msgid "NotificationEvent|New issue" +msgstr "NotificationEvent|Новое обращение" + +msgid "NotificationEvent|New merge request" +msgstr "NotificationEvent|Новый запрос на слияние" + +msgid "NotificationEvent|New note" +msgstr "NotificationEvent|Новая заметка" + +msgid "NotificationEvent|Reassign issue" +msgstr "NotificationEvent|Переназначить обращение" + +msgid "NotificationEvent|Reassign merge request" +msgstr "NotificationEvent|Переназначить запрос на слияние" + +msgid "NotificationEvent|Reopen issue" +msgstr "NotificationEvent|Переоткрыть обращение" + +msgid "NotificationEvent|Successful pipeline" +msgstr "NotificationEvent|Успешно в конвейере" + +msgid "NotificationLevel|Custom" +msgstr "" + +msgid "NotificationLevel|Disabled" +msgstr "NotificationLevel|Отключено" + +msgid "NotificationLevel|Global" +msgstr "" + +msgid "NotificationLevel|On mention" +msgstr "NotificationLevel|С упоминанием" + +msgid "NotificationLevel|Participate" +msgstr "" + +msgid "NotificationLevel|Watch" +msgstr "NotificationLevel|Отслеживать" + +msgid "OfSearchInADropdown|Filter" +msgstr "OfSearchInADropdown|Фильтр" + +msgid "OpenedNDaysAgo|Opened" +msgstr "" + +msgid "Options" +msgstr "Настройки" + +msgid "Owner" +msgstr "Владелец" + +msgid "Pipeline" +msgstr "Конвейер" + +msgid "Pipeline Health" +msgstr "" + +msgid "Pipeline Schedule" +msgstr "Расписание конвейера" + +msgid "Pipeline Schedules" +msgstr "Расписания конвейеров" + +msgid "PipelineCharts|Failed:" +msgstr "" + +msgid "PipelineCharts|Overall statistics" +msgstr "" + +msgid "PipelineCharts|Success ratio:" +msgstr "" + +msgid "PipelineCharts|Successful:" +msgstr "" + +msgid "PipelineCharts|Total:" +msgstr "" + +msgid "PipelineSchedules|Activated" +msgstr "PipelineSchedules|Активировано" + +msgid "PipelineSchedules|Active" +msgstr "PipelineSchedules|Активно" + +msgid "PipelineSchedules|All" +msgstr "PipelineSchedules|Все" + +msgid "PipelineSchedules|Inactive" +msgstr "PipelineSchedules|Неактивно" + +msgid "PipelineSchedules|Next Run" +msgstr "PipelineSchedules|Следующий запуск" + +msgid "PipelineSchedules|None" +msgstr "PipelineSchedules|None" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "PipelineSchedules|Предоставьте краткое описание этого конвейера" + +msgid "PipelineSchedules|Take ownership" +msgstr "PipelineSchedules|Стать владельцем" + +msgid "PipelineSchedules|Target" +msgstr "PipelineSchedules|Цель" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "PipelineSheduleIntervalPattern|Выборочно" + +msgid "Pipelines" +msgstr "" + +msgid "Pipelines charts" +msgstr "" + +msgid "Pipeline|all" +msgstr "" + +msgid "Pipeline|success" +msgstr "" + +msgid "Pipeline|with stage" +msgstr "Pipeline|со стадией" + +msgid "Pipeline|with stages" +msgstr "Pipeline|со стадиями" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "" + +msgid "Project '%{project_name}' was successfully created." +msgstr "Проект '%{project_name}' успешно создан." + +msgid "Project '%{project_name}' was successfully updated." +msgstr "Проект '%{project_name}' успешно обновлен." + +msgid "Project '%{project_name}' will be deleted." +msgstr "Проект '%{project_name}' удален." + +msgid "Project access must be granted explicitly to each user." +msgstr "Доступ к проекту должен предоставляться явно каждому пользователю." + +msgid "Project export could not be deleted." +msgstr "" + +msgid "Project export has been deleted." +msgstr "" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" + +msgid "Project export started. A download link will be sent by email." +msgstr "" +"Начат экспорт проекта. Ссылка для скачивания будет отправлена по электронной " +"почте." + +msgid "Project home" +msgstr "Домашняя страница проекта" + +msgid "ProjectFeature|Disabled" +msgstr "ProjectFeature|Отключено" + +msgid "ProjectFeature|Everyone with access" +msgstr "ProjectFeature|Все с доступом" + +msgid "ProjectFeature|Only team members" +msgstr "ProjectFeature|Только члены команды" + +msgid "ProjectFileTree|Name" +msgstr "ProjectFileTree|Имя" + +msgid "ProjectLastActivity|Never" +msgstr "ProjectLastActivity|Никогда" + +msgid "ProjectLifecycle|Stage" +msgstr "" + +msgid "ProjectNetworkGraph|Graph" +msgstr "ProjectNetworkGraph|Граф" + +msgid "Read more" +msgstr "" + +msgid "Readme" +msgstr "Readme" + +msgid "RefSwitcher|Branches" +msgstr "RefSwitcher|Ветки" + +msgid "RefSwitcher|Tags" +msgstr "RefSwitcher|Тэги" + +msgid "Related Commits" +msgstr "" + +msgid "Related Deployed Jobs" +msgstr "" + +msgid "Related Issues" +msgstr "" + +msgid "Related Jobs" +msgstr "" + +msgid "Related Merge Requests" +msgstr "" + +msgid "Related Merged Requests" +msgstr "" + +msgid "Remind later" +msgstr "Напомнить позже" + +msgid "Remove project" +msgstr "Удалить проект" + +msgid "Request Access" +msgstr "Запрос доступа" + +msgid "Revert this commit" +msgstr "Отменить это изменение" + +msgid "Revert this merge request" +msgstr "Отменить этот запрос на слияние" + +msgid "Save pipeline schedule" +msgstr "Сохранить расписание конвейра" + +msgid "Schedule a new pipeline" +msgstr "Расписание нового конвейера" + +msgid "Scheduling Pipelines" +msgstr "Планирование конвейеров" + +msgid "Search branches and tags" +msgstr "Найти ветки и тэги" + +msgid "Select Archive Format" +msgstr "Выбрать формат архива" + +msgid "Select a timezone" +msgstr "Выбор временной зоны" + +msgid "Select target branch" +msgstr "Выбор целевой ветки" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" + +msgid "Set up CI" +msgstr "Настройка CI" + +msgid "Set up Koding" +msgstr "Настройка Koding" + +msgid "Set up auto deploy" +msgstr "Настройка автоматического развертывания" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "SetPasswordToCloneLink|установить пароль" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Source code" +msgstr "Исходный код" + +msgid "StarProject|Star" +msgstr "StarProject|Отметить" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "" + +msgid "Switch branch/tag" +msgstr "" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Tags" +msgstr "Тэги" + +msgid "Target Branch" +msgstr "Целевая ветка" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "" + +msgid "The fork relationship has been removed." +msgstr "" + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" + +msgid "The phase of the development lifecycle." +msgstr "" + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"Расписание конвейеров запускает в будущем неоднократно конвейеры, для " +"определенных ветвей или тэгов. Запланированные конвейеры наследуют " +"ограничения на доступ к проекту на основе связанного с ними пользователя." + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "" + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "" + +msgid "The project can be accessed by any logged in user." +msgstr "" + +msgid "The project can be accessed without any authentication." +msgstr "" + +msgid "The repository for this project does not exist." +msgstr "Репозиторий для этого проекта не существует." + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "" + +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "" + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" + +msgid "Time before an issue gets scheduled" +msgstr "" + +msgid "Time before an issue starts implementation" +msgstr "" + +msgid "Time between merge request creation and merge/close" +msgstr "" + +msgid "Time until first merge request" +msgstr "" + +msgid "Timeago|%s days ago" +msgstr "Timeago|%s дн(я|ей) назад" + +msgid "Timeago|%s days remaining" +msgstr "Timeago|Осталось %s дн(я|ей)" + +msgid "Timeago|%s hours remaining" +msgstr "Timeago|Осталось %s часов" + +msgid "Timeago|%s minutes ago" +msgstr "Timeago|%s минут назад" + +msgid "Timeago|%s minutes remaining" +msgstr "Timeago|Осталось %s минут(а|ы)" + +msgid "Timeago|%s months ago" +msgstr "Timeago|%s минут(а|ы) назад" + +msgid "Timeago|%s months remaining" +msgstr "Timeago|Осталось %s месяцев(а)" + +msgid "Timeago|%s seconds remaining" +msgstr "Timeago|Осталось %s секунд(ы)" + +msgid "Timeago|%s weeks ago" +msgstr "Timeago|%s недель(и) назад" + +msgid "Timeago|%s weeks remaining" +msgstr "Timeago|Осталось %s недель(и)" + +msgid "Timeago|%s years ago" +msgstr "Timeago|%s лет/года назад" + +msgid "Timeago|%s years remaining" +msgstr "Timeago|Осталось %s лет/года" + +msgid "Timeago|1 day remaining" +msgstr "Timeago|Остался день" + +msgid "Timeago|1 hour remaining" +msgstr "Timeago|Остался час" + +msgid "Timeago|1 minute remaining" +msgstr "Timeago|Осталась одна минута" + +msgid "Timeago|1 month remaining" +msgstr "Timeago|Остался месяц" + +msgid "Timeago|1 week remaining" +msgstr "Timeago|Осталась неделя" + +msgid "Timeago|1 year remaining" +msgstr "Timeago|Остался год" + +msgid "Timeago|Past due" +msgstr "Timeago|Просрочено" + +msgid "Timeago|a day ago" +msgstr "Timeago|день назад" + +msgid "Timeago|a month ago" +msgstr "Timeago|месяц назад" + +msgid "Timeago|a week ago" +msgstr "Timeago|неделю назад" + +msgid "Timeago|a while" +msgstr "Timeago|какое-то время" + +msgid "Timeago|a year ago" +msgstr "" + +msgid "Timeago|about %s hours ago" +msgstr "Timeago|около %s часов назад" + +msgid "Timeago|about a minute ago" +msgstr "Timeago|около минуты назад" + +msgid "Timeago|about an hour ago" +msgstr "Timeago|около часа назад" + +msgid "Timeago|in %s days" +msgstr "Timeago|через %s дня(ей)" + +msgid "Timeago|in %s hours" +msgstr "Timeago|через %s часа(ов)" + +msgid "Timeago|in %s minutes" +msgstr "Timeago|через %s минут(ы)" + +msgid "Timeago|in %s months" +msgstr "Timeago|через %s месяц(а|ев)" + +msgid "Timeago|in %s seconds" +msgstr "Timeago|через %s секунд(ы)" + +msgid "Timeago|in %s weeks" +msgstr "Timeago|через %s недели" + +msgid "Timeago|in %s years" +msgstr "Timeago|через %s лет/года" + +msgid "Timeago|in 1 day" +msgstr "Timeago|через день" + +msgid "Timeago|in 1 hour" +msgstr "Timeago|через час" + +msgid "Timeago|in 1 minute" +msgstr "Timeago|через минуту" + +msgid "Timeago|in 1 month" +msgstr "Timeago|через месяц" + +msgid "Timeago|in 1 week" +msgstr "Timeago|через неделю" + +msgid "Timeago|in 1 year" +msgstr "Timeago|через год" + +msgid "Timeago|less than a minute ago" +msgstr "Timeago|менее чем минуту назад" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Time|s" +msgstr "" + +msgid "Total Time" +msgstr "" + +msgid "Total test time for all commits/merges" +msgstr "" + +msgid "Unstar" +msgstr "Снять отметку" + +msgid "Upload New File" +msgstr "" + +msgid "Upload file" +msgstr "" + +msgid "UploadLink|click to upload" +msgstr "" + +msgid "Use your global notification setting" +msgstr "" + +msgid "View open merge request" +msgstr "" + +msgid "VisibilityLevel|Internal" +msgstr "" + +msgid "VisibilityLevel|Private" +msgstr "" + +msgid "VisibilityLevel|Public" +msgstr "" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "" + +msgid "We don't have enough data to show this stage." +msgstr "" + +msgid "Withdraw Access Request" +msgstr "" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Вы хотите удалить %{project_name_with_namespace}.\n" +"Удаленный проект НЕ МОЖЕТ быть восстановлен!\n" +"Вы АБСОЛЮТНО уверены?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You can only add files when you are on a branch" +msgstr "" + +msgid "You have reached your project limit" +msgstr "Вы достигли ограничения в вашем проекте" + +msgid "You must sign in to star a project" +msgstr "" + +msgid "You need permission." +msgstr "" + +msgid "You will not get any notifications via email" +msgstr "" + +msgid "You will only receive notifications for the events you choose" +msgstr "" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "" + +msgid "You will receive notifications for any activity" +msgstr "Вы будете получать уведомления о любых действиях" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" + +msgid "Your name" +msgstr "Ваше имя" + +msgid "day" +msgid_plural "days" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "new merge request" +msgstr "новый запрос на слияние" + +msgid "notification emails" +msgstr "email для уведомлений" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + diff --git a/locale/ru/gitlab.po.time_stamp b/locale/ru/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d From 4d4ba0637ee49c7132ebf84e7d6f72d97debab31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 10 Jul 2017 10:25:19 +0800 Subject: [PATCH 011/130] fix changelog issue number --- ...ons-to-i18n.yml => 34881-add-russian-translations-to-i18n.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelogs/unreleased/{34880-add-russian-translations-to-i18n.yml => 34881-add-russian-translations-to-i18n.yml} (100%) diff --git a/changelogs/unreleased/34880-add-russian-translations-to-i18n.yml b/changelogs/unreleased/34881-add-russian-translations-to-i18n.yml similarity index 100% rename from changelogs/unreleased/34880-add-russian-translations-to-i18n.yml rename to changelogs/unreleased/34881-add-russian-translations-to-i18n.yml From 65089c79d4ef66c6a9f28d2ea91631f7f1fa2c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Mon, 10 Jul 2017 10:33:19 +0800 Subject: [PATCH 012/130] add ukrainian translations to i18n --- ...880-add-ukrainian-translations-to-i18n.yml | 4 + lib/gitlab/i18n.rb | 1 + locale/uk/gitlab.po | 1199 +++++++++++++++++ locale/uk/gitlab.po.time_stamp | 0 4 files changed, 1204 insertions(+) create mode 100644 changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml create mode 100644 locale/uk/gitlab.po create mode 100644 locale/uk/gitlab.po.time_stamp diff --git a/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml b/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml new file mode 100644 index 00000000000..4e8a042fdb5 --- /dev/null +++ b/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml @@ -0,0 +1,4 @@ +--- +title: Add Ukrainian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. +merge_request: 12744 +author: Huang Tao diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index f3d489aad0d..ac8b8f75039 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -13,6 +13,7 @@ module Gitlab 'zh_TW' => '繁體中文(臺灣)', 'bg' => 'български', 'eo' => 'Esperanto', + 'uk' => 'Українська', 'it' => 'Italiano' }.freeze diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po new file mode 100644 index 00000000000..6af35ccf082 --- /dev/null +++ b/locale/uk/gitlab.po @@ -0,0 +1,1199 @@ +# Андрей Витюк , 2017. #zanata +# Huang Tao , 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2017-07-05 08:40-0400\n" +"Last-Translator: Андрей Витюк \n" +"Language-Team: Ukrainian\n" +"Language: uk\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" + +msgid "%d additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%d additional commits have been omitted to prevent performance issues." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d комміт" +msgstr[1] "%d комміта" +msgstr[2] "%d коммітів" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 конвеєр" +msgstr[1] "%d конвеєра" +msgstr[2] "%d конвеєрів" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Це набір графічних елементів для безперервної інтеграції" + +msgid "About auto deploy" +msgstr "Про авто розгортання" + +msgid "Active" +msgstr "Активні" + +msgid "Activity" +msgstr "Дії" + +msgid "Add Changelog" +msgstr "Додати Changelog" + +msgid "Add Contribution guide" +msgstr "Додати керівництво по співробітництву" + +msgid "Add License" +msgstr "Додати ліцензію" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "" +"Додати SSH ключа в свій профіль, щоб мати можливість завантажити чи " +"надіслати зміни через SSH." + +msgid "Add new directory" +msgstr "Додати новий каталог" + +msgid "Archived project! Repository is read-only" +msgstr "Заархівовані проект! Репозиторій буде доступний лише для читання" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "Ви впевнені, що хочете видалити цей розклад для Конвеєра?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "Прикріпити файл за допомогою перетягування або %{upload_link}" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "Гілка" +msgstr[1] "Гілки" +msgstr[2] "Гілок" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "Пошук гілок" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "Переключити гілку" + +msgid "Branches" +msgstr "Гілки" + +msgid "Browse Directory" +msgstr "Переглянути каталог" + +msgid "Browse File" +msgstr "Переглянути файл" + +msgid "Browse Files" +msgstr "Переглянути файли" + +msgid "Browse files" +msgstr "Перегляд файлів" + +msgid "ByAuthor|by" +msgstr "від" + +msgid "CI configuration" +msgstr "Конфігурація CI" + +msgid "Cancel" +msgstr "Скасувати" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "Повернутися до гілки" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeTypeAction|Revert" +msgstr "Скасувати" + +msgid "Changelog" +msgstr "Список змін" + +msgid "Charts" +msgstr "Графіки" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge request" +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "скасовано" + +msgid "CiStatusLabel|created" +msgstr "створений" + +msgid "CiStatusLabel|failed" +msgstr "невдача" + +msgid "CiStatusLabel|manual action" +msgstr "вручну" + +msgid "CiStatusLabel|passed" +msgstr "виконано" + +msgid "CiStatusLabel|passed with warnings" +msgstr "виконано з попередженнями" + +msgid "CiStatusLabel|pending" +msgstr "очікування" + +msgid "CiStatusLabel|skipped" +msgstr "пропущено" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "Очікування ручних дій" + +msgid "CiStatusText|blocked" +msgstr "заблоковано" + +msgid "CiStatusText|canceled" +msgstr "скасовано" + +msgid "CiStatusText|created" +msgstr "створено" + +msgid "CiStatusText|failed" +msgstr "Невдача" + +msgid "CiStatusText|manual" +msgstr "вручну" + +msgid "CiStatusText|passed" +msgstr "виконано" + +msgid "CiStatusText|pending" +msgstr "очікування" + +msgid "CiStatusText|skipped" +msgstr "пропущено" + +msgid "CiStatus|running" +msgstr "виконується" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "Комміт" +msgstr[1] "Комміта" +msgstr[2] "Коммітів" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "Комміт тривалість у хвилинах за останні 30 коммітів" + +msgid "Commit message" +msgstr "Комміт повідомлення" + +msgid "CommitBoxTitle|Commit" +msgstr "Комміт" + +msgid "CommitMessage|Add %{file_name}" +msgstr "Додати %{file_name}" + +msgid "Commits" +msgstr "Комміти" + +msgid "Commits feed" +msgstr "Канал коммітів" + +msgid "Commits|History" +msgstr "Історія" + +msgid "Committed by" +msgstr "Комміт від" + +msgid "Compare" +msgstr "Порівняти" + +msgid "Contribution guide" +msgstr "Керівництво по співробітництву" + +msgid "Contributors" +msgstr "Автори" + +msgid "Copy URL to clipboard" +msgstr "Скопіювати URL в буфер обміну" + +msgid "Copy commit SHA to clipboard" +msgstr "Скопіювати ідентифікатор в буфер обміну" + +msgid "Create New Directory" +msgstr "Створити новий каталог" + +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" +"Створити токен доступу для вашого аккауета, щоб відправляти або отримувати " +"через %{protocol}." + +msgid "Create directory" +msgstr "Створити каталог" + +msgid "Create empty bare repository" +msgstr "" + +msgid "Create merge request" +msgstr "Створити запит на злиття" + +msgid "Create new..." +msgstr "Створити..." + +msgid "CreateNewFork|Fork" +msgstr "" + +msgid "CreateTag|Tag" +msgstr "Тег" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "Створити токен для особистого доступу" + +msgid "Cron Timezone" +msgstr "Часовий пояс Cron" + +msgid "Cron syntax" +msgstr "Синтаксис для Cron" + +msgid "Custom notification events" +msgstr "Користувацькі налаштування повідомлень про події" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"Спеціальні рівні повідомлення співпадають з рівнем участі. За допомогою " +"спеціальних рівнів сповіщень ви також отримуватимете сповіщення про вибрані " +"події. Щоб дізнатись більше, перегляньте %{notification_link}." + +msgid "Cycle Analytics" +msgstr "Аналіз циклу" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "" +"Аналітика циклу дає огляд того, скільки часу потрібно, щоб перейти від ідеї " +"до виробництва у вашому проекті." + +msgid "CycleAnalyticsStage|Code" +msgstr "Код" + +msgid "CycleAnalyticsStage|Issue" +msgstr "Проблеми" + +msgid "CycleAnalyticsStage|Plan" +msgstr "Планування" + +msgid "CycleAnalyticsStage|Production" +msgstr "ПРОД" + +msgid "CycleAnalyticsStage|Review" +msgstr "Затвердження" + +msgid "CycleAnalyticsStage|Staging" +msgstr "ДЕВ" + +msgid "CycleAnalyticsStage|Test" +msgstr "Тестування" + +msgid "Define a custom pattern with cron syntax" +msgstr "Визначте власний шаблон за допомогою синтаксису cron" + +msgid "Delete" +msgstr "Видалити" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "Розгортання" +msgstr[1] "Розгортання" +msgstr[2] "Розгортань" + +msgid "Description" +msgstr "Опис" + +msgid "Directory name" +msgstr "Ім'я каталогу" + +msgid "Don't show again" +msgstr "Не показувати" + +msgid "Download" +msgstr "Завантажити" + +msgid "Download tar" +msgstr "Завантажити в форматі tar" + +msgid "Download tar.bz2" +msgstr "Завантажити в форматі tar.bz2" + +msgid "Download tar.gz" +msgstr "Завантажити в форматі tar.gz" + +msgid "Download zip" +msgstr "Завантажити в форматі zip" + +msgid "DownloadArtifacts|Download" +msgstr "Завантажити" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "Завантажити" + +msgid "Edit" +msgstr "Редагувати" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "Редагувати Розклад Конвеєра % {id}" + +msgid "Every day (at 4:00am)" +msgstr "Кожен день (в 4:00 ранку)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "Кожен місяць (1-го числа о 4:00 ранку)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "Щотижня (в неділю о 4:00 ранку)" + +msgid "Failed to change the owner" +msgstr "Не вдалося змінити власника" + +msgid "Failed to remove the pipeline schedule" +msgstr "Не вдалося видалити розклад Конвеєра" + +msgid "Files" +msgstr "Файли" + +msgid "Filter by commit message" +msgstr "Фільтрувати повідомлення коммітів " + +msgid "Find by path" +msgstr "" + +msgid "Find file" +msgstr "Знайти файл" + +msgid "FirstPushedBy|First" +msgstr "Перший" + +msgid "FirstPushedBy|pushed by" +msgstr "Надіслані зміни від" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "" + +msgid "From issue creation until deploy to production" +msgstr "З моменту створення задачі до розгортання на ПРОД" + +msgid "From merge request merge until deploy to production" +msgstr "З об'єднання запиту злиття до розгортання на ПРОД" + +msgid "Go to your fork" +msgstr "" + +msgid "GoToYourFork|Fork" +msgstr "" + +msgid "Home" +msgstr "Початок" + +msgid "Housekeeping successfully started" +msgstr "Очищення успішно розпочато" + +msgid "Import repository" +msgstr "Імпорт репозеторія" + +msgid "Interval Pattern" +msgstr "Шаблон інтервалу" + +msgid "Introducing Cycle Analytics" +msgstr "Представляємо аналітику циклу" + +msgid "Jobs for last month" +msgstr "Завдання за останній місяць" + +msgid "Jobs for last week" +msgstr "Завдання за останній тиждень" + +msgid "Jobs for last year" +msgstr "Завдання за останній рік" + +msgid "LFSStatus|Disabled" +msgstr "Вимкнено" + +msgid "LFSStatus|Enabled" +msgstr "Увімкнено" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "Останній %d день" +msgstr[1] "Останніх %d дні" +msgstr[2] "Останніх %d днів" + +msgid "Last Pipeline" +msgstr "Останній Конвеєр" + +msgid "Last Update" +msgstr "Останнє оновлення" + +msgid "Last commit" +msgstr "Останній комміт" + +msgid "Learn more in the" +msgstr "" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "Документація розкладів Конвеєра" + +msgid "Leave group" +msgstr "Залишити групу" + +msgid "Leave project" +msgstr "Залишити проект" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Median" +msgstr "Медіана" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "додати SSH ключ" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "Нова задача" +msgstr[1] "Нові задачі" +msgstr[2] "Новах задач" + +msgid "New Pipeline Schedule" +msgstr "Новий Розклад Конвеєра" + +msgid "New branch" +msgstr "Нова гілка" + +msgid "New directory" +msgstr "Новий каталог" + +msgid "New file" +msgstr "Новий файл" + +msgid "New issue" +msgstr "Нова задача" + +msgid "New merge request" +msgstr "Новий запит на злиття" + +msgid "New schedule" +msgstr "Новий Розклад" + +msgid "New snippet" +msgstr "Новий фрагмент" + +msgid "New tag" +msgstr "Новий тег" + +msgid "No repository" +msgstr "Немає репозеторія" + +msgid "No schedules" +msgstr "немає Розкладів" + +msgid "Not available" +msgstr "Недоступний" + +msgid "Not enough data" +msgstr "Недостатньо даних" + +msgid "Notification events" +msgstr "Повідомлення про події" + +msgid "NotificationEvent|Close issue" +msgstr "Закрити задачу" + +msgid "NotificationEvent|Close merge request" +msgstr "Закрити запит на злиття" + +msgid "NotificationEvent|Failed pipeline" +msgstr "Невдалий Конвеєр" + +msgid "NotificationEvent|Merge merge request" +msgstr "Запит на злиття" + +msgid "NotificationEvent|New issue" +msgstr "Нова задача" + +msgid "NotificationEvent|New merge request" +msgstr "Новий запит на злиття" + +msgid "NotificationEvent|New note" +msgstr "Нова нотатка" + +msgid "NotificationEvent|Reassign issue" +msgstr "Повторно призначити задачу" + +msgid "NotificationEvent|Reassign merge request" +msgstr "Повторне відкриття запит на злиття" + +msgid "NotificationEvent|Reopen issue" +msgstr "Повторне відкриття задачі" + +msgid "NotificationEvent|Successful pipeline" +msgstr "Успішний Конвеєр" + +msgid "NotificationLevel|Custom" +msgstr "Власні" + +msgid "NotificationLevel|Disabled" +msgstr "Вимкнено" + +msgid "NotificationLevel|Global" +msgstr "Загальні" + +msgid "NotificationLevel|On mention" +msgstr "Коли вас згадують" + +msgid "NotificationLevel|Participate" +msgstr "Берете участь" + +msgid "NotificationLevel|Watch" +msgstr "Спостереження" + +msgid "OfSearchInADropdown|Filter" +msgstr "Фільтр" + +msgid "OpenedNDaysAgo|Opened" +msgstr "Відкрито" + +msgid "Options" +msgstr "Параметри" + +msgid "Owner" +msgstr "Власник" + +msgid "Pipeline" +msgstr "Конвеєр" + +msgid "Pipeline Health" +msgstr "Стан Конвеєра" + +msgid "Pipeline Schedule" +msgstr "Розклад Конвеєра" + +msgid "Pipeline Schedules" +msgstr "Розклади Конвеєрів" + +msgid "PipelineCharts|Failed:" +msgstr "Не вдалося:" + +msgid "PipelineCharts|Overall statistics" +msgstr "Загальна статистика" + +msgid "PipelineCharts|Success ratio:" +msgstr "Коефіцієнт успіху:" + +msgid "PipelineCharts|Successful:" +msgstr "Успішні:" + +msgid "PipelineCharts|Total:" +msgstr "Всього:" + +msgid "PipelineSchedules|Activated" +msgstr "Активовано" + +msgid "PipelineSchedules|Active" +msgstr "Активні" + +msgid "PipelineSchedules|All" +msgstr "Всі" + +msgid "PipelineSchedules|Inactive" +msgstr "Неактивні" + +msgid "PipelineSchedules|Next Run" +msgstr "Наступний запуск" + +msgid "PipelineSchedules|None" +msgstr "Немає" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "Задайте короткий опис для цього Конвеєру" + +msgid "PipelineSchedules|Take ownership" +msgstr "" + +msgid "PipelineSchedules|Target" +msgstr "Ціль" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "Власні" + +msgid "Pipelines" +msgstr "Конвеєри" + +msgid "Pipelines charts" +msgstr "Чарти Конвеєрів" + +msgid "Pipeline|all" +msgstr "всі" + +msgid "Pipeline|success" +msgstr "успіх" + +msgid "Pipeline|with stage" +msgstr "" + +msgid "Pipeline|with stages" +msgstr "" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "" + +msgid "Project '%{project_name}' was successfully created." +msgstr "" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "" + +msgid "Project '%{project_name}' will be deleted." +msgstr "" + +msgid "Project access must be granted explicitly to each user." +msgstr "" + +msgid "Project export could not be deleted." +msgstr "" + +msgid "Project export has been deleted." +msgstr "" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "" + +msgid "Project export started. A download link will be sent by email." +msgstr "" + +msgid "Project home" +msgstr "Домашня сторінка проекту" + +msgid "ProjectFeature|Disabled" +msgstr "Вимкнено" + +msgid "ProjectFeature|Everyone with access" +msgstr "" + +msgid "ProjectFeature|Only team members" +msgstr "" + +msgid "ProjectFileTree|Name" +msgstr "Ім'я" + +msgid "ProjectLastActivity|Never" +msgstr "Ніколи" + +msgid "ProjectLifecycle|Stage" +msgstr "Етап" + +msgid "ProjectNetworkGraph|Graph" +msgstr "Графік" + +msgid "Read more" +msgstr "Докладніше" + +msgid "Readme" +msgstr "Прочитай Мене" + +msgid "RefSwitcher|Branches" +msgstr "Гілки" + +msgid "RefSwitcher|Tags" +msgstr "Теги" + +msgid "Related Commits" +msgstr "Схожі Комміти" + +msgid "Related Deployed Jobs" +msgstr "" + +msgid "Related Issues" +msgstr "" + +msgid "Related Jobs" +msgstr "" + +msgid "Related Merge Requests" +msgstr "" + +msgid "Related Merged Requests" +msgstr "" + +msgid "Remind later" +msgstr "Нагадати пізніше" + +msgid "Remove project" +msgstr "Видалити проект" + +msgid "Request Access" +msgstr "Запит доступу" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge request" +msgstr "" + +msgid "Save pipeline schedule" +msgstr "Зберегти Розклад Конвеєра" + +msgid "Schedule a new pipeline" +msgstr "Розклад нових конвеєрів" + +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Search branches and tags" +msgstr "Пошук гілок та тегів" + +msgid "Select Archive Format" +msgstr "Виберіть формат архіву" + +msgid "Select a timezone" +msgstr "Вибрати часовий пояс" + +msgid "Select target branch" +msgstr "Виберіть з якої гілки" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" + +msgid "Set up CI" +msgstr "Настроїти CI" + +msgid "Set up Koding" +msgstr "Настроїти Koding" + +msgid "Set up auto deploy" +msgstr "Настроїти автоматичне розгортання" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "встановити пароль" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "Source code" +msgstr "" + +msgid "StarProject|Star" +msgstr "Старт" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "" + +msgid "Switch branch/tag" +msgstr "тег" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "Тег" +msgstr[1] "Теги" +msgstr[2] "Тегів" + +msgid "Tags" +msgstr "Теги" + +msgid "Target Branch" +msgstr "" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "" + +msgid "The fork relationship has been removed." +msgstr "" + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" + +msgid "The phase of the development lifecycle." +msgstr "" + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "" + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "" + +msgid "The project can be accessed by any logged in user." +msgstr "" + +msgid "The project can be accessed without any authentication." +msgstr "" + +msgid "The repository for this project does not exist." +msgstr "" + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "" + +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "" + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "" + +msgid "Time before an issue gets scheduled" +msgstr "" + +msgid "Time before an issue starts implementation" +msgstr "" + +msgid "Time between merge request creation and merge/close" +msgstr "" + +msgid "Time until first merge request" +msgstr "Час до першого запиту на злиття" + +msgid "Timeago|%s days ago" +msgstr "%s днів тому" + +msgid "Timeago|%s days remaining" +msgstr "%s днів, що залишилися" + +msgid "Timeago|%s hours remaining" +msgstr "%s годин, що залишилися" + +msgid "Timeago|%s minutes ago" +msgstr "%s хвилин тому" + +msgid "Timeago|%s minutes remaining" +msgstr "%s хвилини залишитися" + +msgid "Timeago|%s months ago" +msgstr "%s місяців тому" + +msgid "Timeago|%s months remaining" +msgstr "%s місяці, що залишилися" + +msgid "Timeago|%s seconds remaining" +msgstr "%s секунд, що залишаються" + +msgid "Timeago|%s weeks ago" +msgstr "%s тижнів тому" + +msgid "Timeago|%s weeks remaining" +msgstr "%s тижнів залишилися" + +msgid "Timeago|%s years ago" +msgstr "%s років тому" + +msgid "Timeago|%s years remaining" +msgstr "%s роки, що залишилися" + +msgid "Timeago|1 day remaining" +msgstr "Залишився 1 день" + +msgid "Timeago|1 hour remaining" +msgstr "Залишилась 1 година" + +msgid "Timeago|1 minute remaining" +msgstr "Залишилась 1 хвилина" + +msgid "Timeago|1 month remaining" +msgstr "Залишився 1 місяць" + +msgid "Timeago|1 week remaining" +msgstr "Залишився 1 тиждень" + +msgid "Timeago|1 year remaining" +msgstr "Залишився 1 рік" + +msgid "Timeago|Past due" +msgstr "Прострочені" + +msgid "Timeago|a day ago" +msgstr "годин тому" + +msgid "Timeago|a month ago" +msgstr "місяць тому" + +msgid "Timeago|a week ago" +msgstr "тиждень тому" + +msgid "Timeago|a while" +msgstr "деякий час назад" + +msgid "Timeago|a year ago" +msgstr "рік тому" + +msgid "Timeago|about %s hours ago" +msgstr "Близько %s годин тому" + +msgid "Timeago|about a minute ago" +msgstr "Близько хвилини тому" + +msgid "Timeago|about an hour ago" +msgstr "Близько години тому" + +msgid "Timeago|in %s days" +msgstr "" + +msgid "Timeago|in %s hours" +msgstr "" + +msgid "Timeago|in %s minutes" +msgstr "" + +msgid "Timeago|in %s months" +msgstr "" + +msgid "Timeago|in %s seconds" +msgstr "" + +msgid "Timeago|in %s weeks" +msgstr "" + +msgid "Timeago|in %s years" +msgstr "" + +msgid "Timeago|in 1 day" +msgstr "" + +msgid "Timeago|in 1 hour" +msgstr "" + +msgid "Timeago|in 1 minute" +msgstr "" + +msgid "Timeago|in 1 month" +msgstr "" + +msgid "Timeago|in 1 week" +msgstr "" + +msgid "Timeago|in 1 year" +msgstr "" + +msgid "Timeago|less than a minute ago" +msgstr "менш хвилини тому" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "Година" +msgstr[1] "Годині" +msgstr[2] "Годин" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "хвилина" +msgstr[1] "хвилині" +msgstr[2] "хвилин" + +msgid "Time|s" +msgstr "секунда" + +msgid "Total Time" +msgstr "Загальний час" + +msgid "Total test time for all commits/merges" +msgstr "Загальний час, щоб перевірити всі фіксації/злиття" + +msgid "Unstar" +msgstr "Зняти позначку" + +msgid "Upload New File" +msgstr "Завантажити новий файл" + +msgid "Upload file" +msgstr "Завантажити файл" + +msgid "UploadLink|click to upload" +msgstr "Натисніть, щоб завантажити" + +msgid "Use your global notification setting" +msgstr "" + +msgid "View open merge request" +msgstr "Перегляд відкритих запитів на злиття" + +msgid "VisibilityLevel|Internal" +msgstr "Внутрішній" + +msgid "VisibilityLevel|Private" +msgstr "Приватний" + +msgid "VisibilityLevel|Public" +msgstr "Публічний" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "Хочете побачити дані? Будь ласка, попросить у адміністратора доступ." + +msgid "We don't have enough data to show this stage." +msgstr "Ми не маємо достатньо даних для показу цього етапу." + +msgid "Withdraw Access Request" +msgstr "" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You can only add files when you are on a branch" +msgstr "" + +msgid "You have reached your project limit" +msgstr "" + +msgid "You must sign in to star a project" +msgstr "" + +msgid "You need permission." +msgstr "Вам потрібен дозвіл" + +msgid "You will not get any notifications via email" +msgstr "" + +msgid "You will only receive notifications for the events you choose" +msgstr "" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "" + +msgid "You will receive notifications for any activity" +msgstr "" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "" + +msgid "Your name" +msgstr "Ваше ім'я" + +msgid "day" +msgid_plural "days" +msgstr[0] "день" +msgstr[1] "дні" +msgstr[2] "днів" + +msgid "new merge request" +msgstr "Новий запит на злиття" + +msgid "notification emails" +msgstr "Повідомлення електронною поштою" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + diff --git a/locale/uk/gitlab.po.time_stamp b/locale/uk/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d From 2676e867397c97d32574e9008c22519564399715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Wed, 12 Jul 2017 09:32:47 +0800 Subject: [PATCH 013/130] Synchronous zanata in uk translation --- locale/uk/gitlab.po | 231 +++++++++++++++++++++++++------------------- 1 file changed, 133 insertions(+), 98 deletions(-) diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index 6af35ccf082..4f50dd63d93 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -8,7 +8,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-05 08:40-0400\n" +"PO-Revision-Date: 2017-07-11 04:26-0400\n" "Last-Translator: Андрей Витюк \n" "Language-Team: Ukrainian\n" "Language: uk\n" @@ -20,8 +20,11 @@ msgid "%d additional commit has been omitted to prevent performance issues." msgid_plural "" "%d additional commits have been omitted to prevent performance issues." msgstr[0] "" +"%d доданий Комміт був виключений для запобігання проблем з продуктивністю." msgstr[1] "" +"%d доданих коммітів були виключені для запобігання проблем з продуктивністю." msgstr[2] "" +"%d доданих коммітів були виключені для запобігання проблем з продуктивністю." msgid "%d commit" msgid_plural "%d commits" @@ -30,7 +33,7 @@ msgstr[1] "%d комміта" msgstr[2] "%d коммітів" msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "" +msgstr "%{commit_author_link} комміт %{commit_timeago}" msgid "1 pipeline" msgid_plural "%d pipelines" @@ -48,13 +51,13 @@ msgid "Active" msgstr "Активні" msgid "Activity" -msgstr "Дії" +msgstr "Активність" msgid "Add Changelog" msgstr "Додати Changelog" msgid "Add Contribution guide" -msgstr "Додати керівництво по співробітництву" +msgstr "Додати керівництво для контрибуторів" msgid "Add License" msgstr "Додати ліцензію" @@ -68,7 +71,7 @@ msgid "Add new directory" msgstr "Додати новий каталог" msgid "Archived project! Repository is read-only" -msgstr "Заархівовані проект! Репозиторій буде доступний лише для читання" +msgstr "Заархівований проект! Репозиторій доступний лише для читання" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Ви впевнені, що хочете видалити цей розклад для Конвеєра?" @@ -87,6 +90,9 @@ msgid "" "choose a GitLab CI Yaml template and commit your changes. " "%{link_to_autodeploy_doc}" msgstr "" +"Гілка %{branch_name} створена. Для настройки автоматичного " +"розгортання виберіть GitLab CI Yaml-шаблон і закоммітьте зміни. " +"%{link_to_autodeploy_doc}" msgid "BranchSwitcherPlaceholder|Search branches" msgstr "Пошук гілок" @@ -113,19 +119,19 @@ msgid "ByAuthor|by" msgstr "від" msgid "CI configuration" -msgstr "Конфігурація CI" +msgstr "Налаштування CI" msgid "Cancel" msgstr "Скасувати" msgid "ChangeTypeActionLabel|Pick into branch" -msgstr "" +msgstr "Вибрати в гілці" msgid "ChangeTypeActionLabel|Revert in branch" -msgstr "Повернутися до гілки" +msgstr "Скасувати у гілці" msgid "ChangeTypeAction|Cherry-pick" -msgstr "" +msgstr "Cherry-pick" msgid "ChangeTypeAction|Revert" msgstr "Скасувати" @@ -137,19 +143,19 @@ msgid "Charts" msgstr "Графіки" msgid "Cherry-pick this commit" -msgstr "" +msgstr "Cherry-pick в цьому комміті" msgid "Cherry-pick this merge request" -msgstr "" +msgstr "Cherry-pick в цьому запиті на злиття" msgid "CiStatusLabel|canceled" msgstr "скасовано" msgid "CiStatusLabel|created" -msgstr "створений" +msgstr "створено" msgid "CiStatusLabel|failed" -msgstr "невдача" +msgstr "невдало" msgid "CiStatusLabel|manual action" msgstr "вручну" @@ -161,7 +167,7 @@ msgid "CiStatusLabel|passed with warnings" msgstr "виконано з попередженнями" msgid "CiStatusLabel|pending" -msgstr "очікування" +msgstr "в очікуванні" msgid "CiStatusLabel|skipped" msgstr "пропущено" @@ -179,7 +185,7 @@ msgid "CiStatusText|created" msgstr "створено" msgid "CiStatusText|failed" -msgstr "Невдача" +msgstr "невдало" msgid "CiStatusText|manual" msgstr "вручну" @@ -188,7 +194,7 @@ msgid "CiStatusText|passed" msgstr "виконано" msgid "CiStatusText|pending" -msgstr "очікування" +msgstr "в очікуванні" msgid "CiStatusText|skipped" msgstr "пропущено" @@ -230,10 +236,10 @@ msgid "Compare" msgstr "Порівняти" msgid "Contribution guide" -msgstr "Керівництво по співробітництву" +msgstr "Керівництво контрибуторів" msgid "Contributors" -msgstr "Автори" +msgstr "Контрибутори" msgid "Copy URL to clipboard" msgstr "Скопіювати URL в буфер обміну" @@ -255,7 +261,7 @@ msgid "Create directory" msgstr "Створити каталог" msgid "Create empty bare repository" -msgstr "" +msgstr "Створити порожній репозиторій" msgid "Create merge request" msgstr "Створити запит на злиття" @@ -264,7 +270,7 @@ msgid "Create new..." msgstr "Створити..." msgid "CreateNewFork|Fork" -msgstr "" +msgstr "Форк" msgid "CreateTag|Tag" msgstr "Тег" @@ -276,7 +282,7 @@ msgid "Cron Timezone" msgstr "Часовий пояс Cron" msgid "Cron syntax" -msgstr "Синтаксис для Cron" +msgstr "Синтаксис Cron" msgid "Custom notification events" msgstr "Користувацькі налаштування повідомлень про події" @@ -340,7 +346,7 @@ msgid "Directory name" msgstr "Ім'я каталогу" msgid "Don't show again" -msgstr "Не показувати" +msgstr "Не показувати знову" msgid "Download" msgstr "Завантажити" @@ -361,10 +367,10 @@ msgid "DownloadArtifacts|Download" msgstr "Завантажити" msgid "DownloadCommit|Email Patches" -msgstr "" +msgstr "Email-патчи" msgid "DownloadCommit|Plain Diff" -msgstr "" +msgstr "Plain Diff" msgid "DownloadSource|Download" msgstr "Завантажити" @@ -397,7 +403,7 @@ msgid "Filter by commit message" msgstr "Фільтрувати повідомлення коммітів " msgid "Find by path" -msgstr "" +msgstr "Пошук по шляху" msgid "Find file" msgstr "Знайти файл" @@ -410,12 +416,12 @@ msgstr "Надіслані зміни від" msgid "Fork" msgid_plural "Forks" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Форк" +msgstr[1] "Форки" +msgstr[2] "Форків" msgid "ForkedFromProjectPath|Forked from" -msgstr "" +msgstr "Форк від" msgid "From issue creation until deploy to production" msgstr "З моменту створення задачі до розгортання на ПРОД" @@ -424,10 +430,10 @@ msgid "From merge request merge until deploy to production" msgstr "З об'єднання запиту злиття до розгортання на ПРОД" msgid "Go to your fork" -msgstr "" +msgstr "Перейти до вашого форку" msgid "GoToYourFork|Fork" -msgstr "" +msgstr "Форк" msgid "Home" msgstr "Початок" @@ -475,10 +481,10 @@ msgid "Last commit" msgstr "Останній комміт" msgid "Learn more in the" -msgstr "" +msgstr "Дізнайтесь більше" msgid "Learn more in the|pipeline schedules documentation" -msgstr "Документація розкладів Конвеєра" +msgstr "Детальніше в документації по розкладами конвеєрів" msgid "Leave group" msgstr "Залишити групу" @@ -505,7 +511,7 @@ msgstr[1] "Нові задачі" msgstr[2] "Новах задач" msgid "New Pipeline Schedule" -msgstr "Новий Розклад Конвеєра" +msgstr "Новий розклад Конвеєра" msgid "New branch" msgstr "Нова гілка" @@ -526,7 +532,7 @@ msgid "New schedule" msgstr "Новий Розклад" msgid "New snippet" -msgstr "Новий фрагмент" +msgstr "Новий сніппет" msgid "New tag" msgstr "Новий тег" @@ -547,16 +553,16 @@ msgid "Notification events" msgstr "Повідомлення про події" msgid "NotificationEvent|Close issue" -msgstr "Закрити задачу" +msgstr "Задача закрита" msgid "NotificationEvent|Close merge request" -msgstr "Закрити запит на злиття" +msgstr "Запит на об'єднання закритий" msgid "NotificationEvent|Failed pipeline" -msgstr "Невдалий Конвеєр" +msgstr "Невдача в конвеєрі" msgid "NotificationEvent|Merge merge request" -msgstr "Запит на злиття" +msgstr "Об'єднати запит на злиття" msgid "NotificationEvent|New issue" msgstr "Нова задача" @@ -568,16 +574,16 @@ msgid "NotificationEvent|New note" msgstr "Нова нотатка" msgid "NotificationEvent|Reassign issue" -msgstr "Повторно призначити задачу" +msgstr "Перепризначити задачу" msgid "NotificationEvent|Reassign merge request" -msgstr "Повторне відкриття запит на злиття" +msgstr "Перепризначити запит на злиття" msgid "NotificationEvent|Reopen issue" msgstr "Повторне відкриття задачі" msgid "NotificationEvent|Successful pipeline" -msgstr "Успішний Конвеєр" +msgstr "Успішно в Конвеєрі" msgid "NotificationLevel|Custom" msgstr "Власні" @@ -595,7 +601,7 @@ msgid "NotificationLevel|Participate" msgstr "Берете участь" msgid "NotificationLevel|Watch" -msgstr "Спостереження" +msgstr "Відстежувати" msgid "OfSearchInADropdown|Filter" msgstr "Фільтр" @@ -658,7 +664,7 @@ msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Задайте короткий опис для цього Конвеєру" msgid "PipelineSchedules|Take ownership" -msgstr "" +msgstr "Стати власником" msgid "PipelineSchedules|Target" msgstr "Ціль" @@ -679,39 +685,43 @@ msgid "Pipeline|success" msgstr "успіх" msgid "Pipeline|with stage" -msgstr "" +msgstr "зі стадією" msgid "Pipeline|with stages" -msgstr "" +msgstr "зі стадіями" msgid "Project '%{project_name}' queued for deletion." -msgstr "" +msgstr "Проект '%{project_name}' доданий в чергу на видалення." msgid "Project '%{project_name}' was successfully created." -msgstr "" +msgstr "Проект '%{project_name}' успішно створений." msgid "Project '%{project_name}' was successfully updated." -msgstr "" +msgstr "Проект '%{project_name}' успішно оновлено." msgid "Project '%{project_name}' will be deleted." -msgstr "" +msgstr "Проект '%{project_name}' видалений." msgid "Project access must be granted explicitly to each user." -msgstr "" +msgstr "Доступ до проекту повинен надаватися кожному користувачеві." msgid "Project export could not be deleted." -msgstr "" +msgstr "Неможливо видалити експорт проекту." msgid "Project export has been deleted." -msgstr "" +msgstr "Експорт проекту видалений." msgid "" "Project export link has expired. Please generate a new export from your " "project settings." msgstr "" +"Закінчився термін дії посилання на проект. Створіть новий експорт в ваших " +"настройках проекту." msgid "Project export started. A download link will be sent by email." msgstr "" +"Розпочато експорт проекту. Посилання для скачування буде надіслана " +"електронною поштою." msgid "Project home" msgstr "Домашня сторінка проекту" @@ -720,10 +730,10 @@ msgid "ProjectFeature|Disabled" msgstr "Вимкнено" msgid "ProjectFeature|Everyone with access" -msgstr "" +msgstr "Все з доступом" msgid "ProjectFeature|Only team members" -msgstr "" +msgstr "Тільки члени команди" msgid "ProjectFileTree|Name" msgstr "Ім'я" @@ -762,10 +772,10 @@ msgid "Related Jobs" msgstr "" msgid "Related Merge Requests" -msgstr "" +msgstr "Пов'язані запити на злиття" msgid "Related Merged Requests" -msgstr "" +msgstr "Пов'язані об'єднані запити" msgid "Remind later" msgstr "Нагадати пізніше" @@ -777,19 +787,19 @@ msgid "Request Access" msgstr "Запит доступу" msgid "Revert this commit" -msgstr "" +msgstr "Скасувати цей комміт" msgid "Revert this merge request" -msgstr "" +msgstr "Скасувати цей запит на злиття" msgid "Save pipeline schedule" msgstr "Зберегти Розклад Конвеєра" msgid "Schedule a new pipeline" -msgstr "Розклад нових конвеєрів" +msgstr "Розклад нового конвеєра" msgid "Scheduling Pipelines" -msgstr "" +msgstr "Планування конвеєрів" msgid "Search branches and tags" msgstr "Пошук гілок та тегів" @@ -801,19 +811,21 @@ msgid "Select a timezone" msgstr "Вибрати часовий пояс" msgid "Select target branch" -msgstr "Виберіть з якої гілки" +msgstr "Вибір цільової гілки" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" +"Встановіть пароль свого облікового запису, щоб відправляти або отримувати " +"код через %{protocol}." msgid "Set up CI" -msgstr "Настроїти CI" +msgstr "Налаштування CI" msgid "Set up Koding" -msgstr "Настроїти Koding" +msgstr "Налаштування Koding" msgid "Set up auto deploy" -msgstr "Настроїти автоматичне розгортання" +msgstr "Налаштування автоматичне розгортання" msgid "SetPasswordToCloneLink|set a password" msgstr "встановити пароль" @@ -825,13 +837,13 @@ msgstr[1] "" msgstr[2] "" msgid "Source code" -msgstr "" +msgstr "Код" msgid "StarProject|Star" msgstr "Старт" msgid "Start a %{new_merge_request} with these changes" -msgstr "" +msgstr "Почати %{new_merge_request} з цих змін" msgid "Switch branch/tag" msgstr "тег" @@ -846,7 +858,7 @@ msgid "Tags" msgstr "Теги" msgid "Target Branch" -msgstr "" +msgstr "Цільова гілка" msgid "" "The coding stage shows the time from the first commit to creating the merge " @@ -858,22 +870,28 @@ msgid "The collection of events added to the data gathered for that stage." msgstr "" msgid "The fork relationship has been removed." -msgstr "" +msgstr "Зв'язок форка видалена." msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." msgstr "" +"Стадія звернення час, який буде потрібно з моменту створення звернення до " +"призначення зверненням віхи, або додавання звернення в вашу дошку звернень. " +"Почніть створювати звернення, щоб побачити подробиці для цієї стадії." msgid "The phase of the development lifecycle." -msgstr "" +msgstr "Фаза життєвого циклу розробки." msgid "" "The pipelines schedule runs pipelines in the future, repeatedly, for " "specific branches or tags. Those scheduled pipelines will inherit limited " "project access based on their associated user." msgstr "" +"Розклад конвеєрів запускає в майбутньому конвеєри, для певних гілок або " +"тегів. Заплановані конвеєри успадковують обмеження на доступ до проекту на " +"основі пов'язаного з ними користувача." msgid "" "The planning stage shows the time from the previous step to pushing your " @@ -888,13 +906,13 @@ msgid "" msgstr "" msgid "The project can be accessed by any logged in user." -msgstr "" +msgstr "Доступ до проекту можливий будь-яким зареєстрованим користувачем." msgid "The project can be accessed without any authentication." -msgstr "" +msgstr "Доступ до проекту можливий без будь-якої перевірки автентичності." msgid "The repository for this project does not exist." -msgstr "" +msgstr "Репозиторій для цього проекту не існує." msgid "" "The review stage shows the time from creating the merge request to merging " @@ -927,6 +945,8 @@ msgid "" "This means you can not push code until you create an empty repository or " "import existing one." msgstr "" +"Це означає, що ви не можете відправляти код, поки не створите порожній " +"репозиторій або НЕ імпортуєте існуючий." msgid "Time before an issue gets scheduled" msgstr "" @@ -935,7 +955,7 @@ msgid "Time before an issue starts implementation" msgstr "" msgid "Time between merge request creation and merge/close" -msgstr "" +msgstr "Час між створенням запиту злиття і злиттям або закриттям" msgid "Time until first merge request" msgstr "Час до першого запиту на злиття" @@ -1022,43 +1042,43 @@ msgid "Timeago|about an hour ago" msgstr "Близько години тому" msgid "Timeago|in %s days" -msgstr "" +msgstr "через %s днїв" msgid "Timeago|in %s hours" -msgstr "" +msgstr "через %s години" msgid "Timeago|in %s minutes" -msgstr "" +msgstr "через %s хвилини" msgid "Timeago|in %s months" -msgstr "" +msgstr "через %s місяців" msgid "Timeago|in %s seconds" -msgstr "" +msgstr "через %s секунд" msgid "Timeago|in %s weeks" -msgstr "" +msgstr "через %s тижні" msgid "Timeago|in %s years" -msgstr "" +msgstr "через %s років" msgid "Timeago|in 1 day" -msgstr "" +msgstr "через день" msgid "Timeago|in 1 hour" -msgstr "" +msgstr "через годину" msgid "Timeago|in 1 minute" -msgstr "" +msgstr "через хвилину" msgid "Timeago|in 1 month" -msgstr "" +msgstr "через місяць" msgid "Timeago|in 1 week" -msgstr "" +msgstr "через тиждень" msgid "Timeago|in 1 year" -msgstr "" +msgstr "через рік" msgid "Timeago|less than a minute ago" msgstr "менш хвилини тому" @@ -1097,7 +1117,7 @@ msgid "UploadLink|click to upload" msgstr "Натисніть, щоб завантажити" msgid "Use your global notification setting" -msgstr "" +msgstr "Використовуються глобальний налаштування повідомлень" msgid "View open merge request" msgstr "Перегляд відкритих запитів на злиття" @@ -1118,63 +1138,78 @@ msgid "We don't have enough data to show this stage." msgstr "Ми не маємо достатньо даних для показу цього етапу." msgid "Withdraw Access Request" -msgstr "" +msgstr "Скасувати запит доступу" msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" "Are you ABSOLUTELY sure?" msgstr "" +"Ви хочете видалити %{project_name_with_namespace}.\n" +"Видалений проект НЕ МОЖЕ бути відновлений!\n" +"Ви АБСОЛЮТНО впевнені?" msgid "" "You are going to remove the fork relationship to source project " "%{forked_from_project}. Are you ABSOLUTELY sure?" msgstr "" +"Ви збираєтеся видалити зв'язок з форка з вихідним проектом " +"%{forked_from_project}. Ви АБСОЛЮТНО впевнені?" msgid "" "You are going to transfer %{project_name_with_namespace} to another owner. " "Are you ABSOLUTELY sure?" msgstr "" +"Ви збираєтеся передати проект %{project_name_with_namespace} іншому власнику." +" Ви АБСОЛЮТНО впевнені?" msgid "You can only add files when you are on a branch" -msgstr "" +msgstr "Ви можете додавати тільки файли, коли перебуваєте в гілці" msgid "You have reached your project limit" -msgstr "" +msgstr "Ви досягли обмеження в вашому проекті" msgid "You must sign in to star a project" -msgstr "" +msgstr "Необхідно увійти, щоб оцінити проект" msgid "You need permission." msgstr "Вам потрібен дозвіл" msgid "You will not get any notifications via email" -msgstr "" +msgstr "Ви не отримаєте ніяких повідомлень по електронній пошті" msgid "You will only receive notifications for the events you choose" -msgstr "" +msgstr "Ви будете отримувати повідомлення тільки про обрані вами події" msgid "" "You will only receive notifications for threads you have participated in" msgstr "" +"Ви будете отримувати повідомлення тільки про тих темах, в яких ви брали " +"участь" msgid "You will receive notifications for any activity" -msgstr "" +msgstr "Ви будете отримувати повідомлення про будь-які дії" msgid "" "You will receive notifications only for comments in which you were " "@mentioned" msgstr "" +"Ви будете отримувати повідомлення тільки для коментарів, в яких ви були " +"@згадані" msgid "" "You won't be able to pull or push project code via %{protocol} until you " "%{set_password_link} on your account" msgstr "" +"Ви не зможете отримувати і відправляти код проекту через %{protocol} поки " +"%{set_password_link} в ваш аккаунт" msgid "" "You won't be able to pull or push project code via SSH until you " "%{add_ssh_key_link} to your profile" msgstr "" +"Ви не зможете отримувати і відправляти код проекту через SSH поки " +"%{add_ssh_key_link} в ваш профіль." msgid "Your name" msgstr "Ваше ім'я" @@ -1193,7 +1228,7 @@ msgstr "Повідомлення електронною поштою" msgid "parent" msgid_plural "parents" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "джерело" +msgstr[1] "джерела" +msgstr[2] "джерел" From 8bfa296a54f2da37fdf66712f0cfa8beebc59948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Wed, 12 Jul 2017 09:35:01 +0800 Subject: [PATCH 014/130] Synchronous zanata in ru translation --- locale/ru/gitlab.po | 234 +++++++++++++++++++++++++------------------- 1 file changed, 133 insertions(+), 101 deletions(-) diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po index 7856830987a..236d62ac78e 100644 --- a/locale/ru/gitlab.po +++ b/locale/ru/gitlab.po @@ -8,9 +8,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-07 08:11-0400\n" +"PO-Revision-Date: 2017-07-11 05:13-0400\n" "Last-Translator: SAS \n" -"Language-Team: Russian\n" +"Language-Team: Russian (https://translate.zanata.org/project/view/GitLab)\n" "Language: ru\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " @@ -20,17 +20,23 @@ msgid "%d additional commit has been omitted to prevent performance issues." msgid_plural "" "%d additional commits have been omitted to prevent performance issues." msgstr[0] "" +"%d добавленный коммит был исключен для предотвращения проблем с " +"производительностью." msgstr[1] "" +"%d добавленные коммиты были исключены для предотвращения проблем с " +"производительностью." msgstr[2] "" +"%d добавленные коммиты были исключены для предотвращения проблем с " +"производительностью." msgid "%d commit" msgid_plural "%d commits" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d коммит" +msgstr[1] "%d коммит(а|ов)" +msgstr[2] "%d коммит(а|ов)" msgid "%{commit_author_link} committed %{commit_timeago}" -msgstr "%{commit_author_link} зафиксировано %{commit_timeago}" +msgstr "%{commit_author_link} закоммичено %{commit_timeago}" msgid "1 pipeline" msgid_plural "%d pipelines" @@ -61,6 +67,8 @@ msgstr "Добавить лицензию" msgid "Add an SSH key to your profile to pull or push via SSH." msgstr "" +"Добавьте ключ SSH в свой профиль, чтобы отправлять или получать код через " +"SSH." msgid "Add new directory" msgstr "Добавить новую директорию" @@ -72,13 +80,13 @@ msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Вы действительно хотите удалить это расписание конвейера?" msgid "Attach a file by drag & drop or %{upload_link}" -msgstr "" +msgstr "Приложить файл через drag & drop или %{upload_link}" msgid "Branch" msgid_plural "Branches" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Ветка" +msgstr[1] "Ветки" +msgstr[2] "Ветки" msgid "" "Branch %{branch_name} was created. To set up auto deploy, " @@ -90,28 +98,28 @@ msgstr "" "%{link_to_autodeploy_doc}" msgid "BranchSwitcherPlaceholder|Search branches" -msgstr "" +msgstr "BranchSwitcherPlaceholder|Поиск веток" msgid "BranchSwitcherTitle|Switch branch" -msgstr "" +msgstr "BranchSwitcherTitle|Переключить ветку" msgid "Branches" msgstr "Ветки" msgid "Browse Directory" -msgstr "" +msgstr "Просмотр директории" msgid "Browse File" -msgstr "" +msgstr "Просмотр файла" msgid "Browse Files" -msgstr "" +msgstr "Просмотр файлов" msgid "Browse files" msgstr "Просмотр файлов" msgid "ByAuthor|by" -msgstr "" +msgstr "ByAuthor|по автору" msgid "CI configuration" msgstr "Настройка CI" @@ -120,16 +128,16 @@ msgid "Cancel" msgstr "Отмена" msgid "ChangeTypeActionLabel|Pick into branch" -msgstr "" +msgstr "ChangeTypeActionLabel|Выбрать в ветке" msgid "ChangeTypeActionLabel|Revert in branch" msgstr "ChangeTypeActionLabel|Отменить в ветке" msgid "ChangeTypeAction|Cherry-pick" -msgstr "" +msgstr "ChangeTypeAction|Подобрать" msgid "ChangeTypeAction|Revert" -msgstr "" +msgstr "ChangeTypeAction|Отменить" msgid "Changelog" msgstr "Журнал изменений" @@ -138,10 +146,10 @@ msgid "Charts" msgstr "Графики" msgid "Cherry-pick this commit" -msgstr "" +msgstr "Подобрать в этом коммите" msgid "Cherry-pick this merge request" -msgstr "" +msgstr "Побрать в этом запросе на слияние" msgid "CiStatusLabel|canceled" msgstr "CiStatusLabel|отменено" @@ -183,7 +191,7 @@ msgid "CiStatusText|failed" msgstr "CiStatusText|неудачно" msgid "CiStatusText|manual" -msgstr "" +msgstr "CiStatusText|ручное" msgid "CiStatusText|passed" msgstr "CiStatusText|пройдено" @@ -199,24 +207,24 @@ msgstr "CiStatus|выполняется" msgid "Commit" msgid_plural "Commits" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Коммит" +msgstr[1] "Коммиты" +msgstr[2] "Коммиты" msgid "Commit duration in minutes for last 30 commits" msgstr "" msgid "Commit message" -msgstr "" +msgstr "Описание коммита" msgid "CommitBoxTitle|Commit" -msgstr "" +msgstr "CommitBoxTitle|Коммит" msgid "CommitMessage|Add %{file_name}" msgstr "CommitMessage|Добавить %{file_name}" msgid "Commits" -msgstr "" +msgstr "Коммиты" msgid "Commits feed" msgstr "" @@ -225,7 +233,7 @@ msgid "Commits|History" msgstr "Commits|История" msgid "Committed by" -msgstr "" +msgstr "Коммит" msgid "Compare" msgstr "Сравнение" @@ -240,7 +248,7 @@ msgid "Copy URL to clipboard" msgstr "Копировать URL в буфер обмена" msgid "Copy commit SHA to clipboard" -msgstr "" +msgstr "Копировать SHA коммита в буфер обмена" msgid "Create New Directory" msgstr "Создать новую директорию" @@ -278,16 +286,20 @@ msgid "Cron syntax" msgstr "Синтаксис Cron" msgid "Custom notification events" -msgstr "Пользовательские уведомления о событиях" +msgstr " Настраиваемые уведомления о событиях" msgid "" "Custom notification levels are the same as participating levels. With custom " "notification levels you will also receive notifications for select events. " "To find out more, check out %{notification_link}." msgstr "" +"Настраиваемые уровни уведомлений аналогичны уровню уведомлений в " +"соответствии с участием. С настраиваемыми уровнями уведомлений вы также " +"будете получать уведомления о выбранных событиях. Чтобы узнать больше, " +"посмотрите %{notification_link}." msgid "Cycle Analytics" -msgstr "" +msgstr "Аналитика цикла разработки" msgid "" "Cycle Analytics gives an overview of how much time it takes to go from idea " @@ -295,10 +307,10 @@ msgid "" msgstr "" msgid "CycleAnalyticsStage|Code" -msgstr "" +msgstr "CycleAnalyticsStage|Код" msgid "CycleAnalyticsStage|Issue" -msgstr "" +msgstr "CycleAnalyticsStage|Обращение" msgid "CycleAnalyticsStage|Plan" msgstr "" @@ -307,7 +319,7 @@ msgid "CycleAnalyticsStage|Production" msgstr "" msgid "CycleAnalyticsStage|Review" -msgstr "" +msgstr "CycleAnalyticsStage|Ревьюв" msgid "CycleAnalyticsStage|Staging" msgstr "" @@ -316,7 +328,7 @@ msgid "CycleAnalyticsStage|Test" msgstr "" msgid "Define a custom pattern with cron syntax" -msgstr "Определить пользовательский шаблон с синтаксисом cron" +msgstr "Определить настраиваемый шаблон с синтаксисом cron" msgid "Delete" msgstr "Удалить" @@ -388,10 +400,10 @@ msgid "Files" msgstr "Файлы" msgid "Filter by commit message" -msgstr "" +msgstr "Фильтр по комментариями к коммитам" msgid "Find by path" -msgstr "" +msgstr "Поиск по пути" msgid "Find file" msgstr "Найти файл" @@ -404,9 +416,9 @@ msgstr "" msgid "Fork" msgid_plural "Forks" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Форк" +msgstr[1] "Форки" +msgstr[2] "Форки" msgid "ForkedFromProjectPath|Forked from" msgstr "ForkedFromProjectPath|Форк от " @@ -424,16 +436,16 @@ msgid "GoToYourFork|Fork" msgstr "GoToYourFork|Форк" msgid "Home" -msgstr "" +msgstr "Домашняя" msgid "Housekeeping successfully started" -msgstr "" +msgstr "Очистка успешно запущена" msgid "Import repository" -msgstr "" +msgstr "Импорт репозитория" msgid "Interval Pattern" -msgstr "" +msgstr "Шаблон интервала" msgid "Introducing Cycle Analytics" msgstr "" @@ -466,10 +478,10 @@ msgid "Last Update" msgstr "Последнее обновление" msgid "Last commit" -msgstr "" +msgstr "Последний коммит" msgid "Learn more in the" -msgstr "" +msgstr "Узнайте больше в" msgid "Learn more in the|pipeline schedules documentation" msgstr "Подробнее в|документации по расписаниям конвейеров" @@ -487,16 +499,16 @@ msgstr[1] "" msgstr[2] "" msgid "Median" -msgstr "" +msgstr "Медиана" msgid "MissingSSHKeyWarningLink|add an SSH key" -msgstr "" +msgstr "MissingSSHKeyWarningLink|добавить ключ SSH" msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Новое обращение" +msgstr[1] "Новые обращения" +msgstr[2] "Новые обращения" msgid "New Pipeline Schedule" msgstr "Новое расписание конвейера" @@ -574,19 +586,19 @@ msgid "NotificationEvent|Successful pipeline" msgstr "NotificationEvent|Успешно в конвейере" msgid "NotificationLevel|Custom" -msgstr "" +msgstr "NotificationLevel|Настраиваемый" msgid "NotificationLevel|Disabled" msgstr "NotificationLevel|Отключено" msgid "NotificationLevel|Global" -msgstr "" +msgstr "NotificationLevel|Глобальный" msgid "NotificationLevel|On mention" msgstr "NotificationLevel|С упоминанием" msgid "NotificationLevel|Participate" -msgstr "" +msgstr "NotificationLevel|По участию" msgid "NotificationLevel|Watch" msgstr "NotificationLevel|Отслеживать" @@ -595,7 +607,7 @@ msgid "OfSearchInADropdown|Filter" msgstr "OfSearchInADropdown|Фильтр" msgid "OpenedNDaysAgo|Opened" -msgstr "" +msgstr "OpenedNDaysAgo|Открыто" msgid "Options" msgstr "Настройки" @@ -658,7 +670,7 @@ msgid "PipelineSchedules|Target" msgstr "PipelineSchedules|Цель" msgid "PipelineSheduleIntervalPattern|Custom" -msgstr "PipelineSheduleIntervalPattern|Выборочно" +msgstr "PipelineSheduleIntervalPattern|Настраиваемый" msgid "Pipelines" msgstr "" @@ -679,7 +691,7 @@ msgid "Pipeline|with stages" msgstr "Pipeline|со стадиями" msgid "Project '%{project_name}' queued for deletion." -msgstr "" +msgstr "Проект '%{project_name}' добавлен в очередь на удаление." msgid "Project '%{project_name}' was successfully created." msgstr "Проект '%{project_name}' успешно создан." @@ -694,15 +706,17 @@ msgid "Project access must be granted explicitly to each user." msgstr "Доступ к проекту должен предоставляться явно каждому пользователю." msgid "Project export could not be deleted." -msgstr "" +msgstr "Невозможно удалить экспорт проекта." msgid "Project export has been deleted." -msgstr "" +msgstr "Экспорт проекта удален." msgid "" "Project export link has expired. Please generate a new export from your " "project settings." msgstr "" +"Истек срок действия ссылки на проект. Создайте новый экспорт в ваших " +"настройках проекта." msgid "Project export started. A download link will be sent by email." msgstr "" @@ -758,10 +772,10 @@ msgid "Related Jobs" msgstr "" msgid "Related Merge Requests" -msgstr "" +msgstr "Связанные запросы на слияние" msgid "Related Merged Requests" -msgstr "" +msgstr "Связанные объединенные запросы" msgid "Remind later" msgstr "Напомнить позже" @@ -801,6 +815,8 @@ msgstr "Выбор целевой ветки" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" +"Установите пароль в своем аккаунте, чтобы отправлять или получать код через " +"%{protocol}." msgid "Set up CI" msgstr "Настройка CI" @@ -827,16 +843,16 @@ msgid "StarProject|Star" msgstr "StarProject|Отметить" msgid "Start a %{new_merge_request} with these changes" -msgstr "" +msgstr "Начать %{new_merge_request} с этих изменений" msgid "Switch branch/tag" -msgstr "" +msgstr "Переключить ветка/тэг" msgid "Tag" msgid_plural "Tags" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Тэг" +msgstr[1] "Тэги" +msgstr[2] "Тэги" msgid "Tags" msgstr "Тэги" @@ -854,16 +870,19 @@ msgid "The collection of events added to the data gathered for that stage." msgstr "" msgid "The fork relationship has been removed." -msgstr "" +msgstr "Связь форка удалена." msgid "" "The issue stage shows the time it takes from creating an issue to assigning " "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." msgstr "" +"Стадия обращения время, которое потребуется с момента создания обращения до " +"назначения обращению вехи, или добавления обращения в вашу доску обращений. " +"Начните создавать обращения, чтобы увидеть сведения для этой стадии. " msgid "The phase of the development lifecycle." -msgstr "" +msgstr "Фаза жизненного цикла разработки." msgid "" "The pipelines schedule runs pipelines in the future, repeatedly, for " @@ -887,10 +906,10 @@ msgid "" msgstr "" msgid "The project can be accessed by any logged in user." -msgstr "" +msgstr "Доступ к проекту возможен любым зарегистрированным пользователем." msgid "The project can be accessed without any authentication." -msgstr "" +msgstr "Доступ к проекту возможен без какой-либо проверки подлинности." msgid "The repository for this project does not exist." msgstr "Репозиторий для этого проекта не существует." @@ -926,6 +945,8 @@ msgid "" "This means you can not push code until you create an empty repository or " "import existing one." msgstr "" +"Это означает, что вы не можете пушить код, пока не создадите пустой " +"репозиторий или не импортируете существующий." msgid "Time before an issue gets scheduled" msgstr "" @@ -934,7 +955,7 @@ msgid "Time before an issue starts implementation" msgstr "" msgid "Time between merge request creation and merge/close" -msgstr "" +msgstr "Время между созданием запроса слияния и слиянием / закрытием" msgid "Time until first merge request" msgstr "" @@ -1009,7 +1030,7 @@ msgid "Timeago|a while" msgstr "Timeago|какое-то время" msgid "Timeago|a year ago" -msgstr "" +msgstr "Timeago|год назад" msgid "Timeago|about %s hours ago" msgstr "Timeago|около %s часов назад" @@ -1064,21 +1085,21 @@ msgstr "Timeago|менее чем минуту назад" msgid "Time|hr" msgid_plural "Time|hrs" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "ч" +msgstr[1] "ч" +msgstr[2] "ч" msgid "Time|min" msgid_plural "Time|mins" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "мин" +msgstr[1] "мин" +msgstr[2] "мин" msgid "Time|s" -msgstr "" +msgstr "с" msgid "Total Time" -msgstr "" +msgstr "Общее время" msgid "Total test time for all commits/merges" msgstr "" @@ -1087,28 +1108,28 @@ msgid "Unstar" msgstr "Снять отметку" msgid "Upload New File" -msgstr "" +msgstr "Выгрузить новый файл" msgid "Upload file" -msgstr "" +msgstr "Выгрузить файл" msgid "UploadLink|click to upload" -msgstr "" +msgstr "UploadLink|кликните для выгрузки" msgid "Use your global notification setting" -msgstr "" +msgstr "Используются глобальный настройки уведомлений" msgid "View open merge request" -msgstr "" +msgstr "Просмотреть открытый запрос на слияние" msgid "VisibilityLevel|Internal" -msgstr "" +msgstr "VisibilityLevel|Ограниченный" msgid "VisibilityLevel|Private" -msgstr "" +msgstr "VisibilityLevel|Приватный" msgid "VisibilityLevel|Public" -msgstr "" +msgstr "VisibilityLevel|Публичный" msgid "Want to see the data? Please ask an administrator for access." msgstr "" @@ -1117,7 +1138,7 @@ msgid "We don't have enough data to show this stage." msgstr "" msgid "Withdraw Access Request" -msgstr "" +msgstr "Отменить запрос доступа" msgid "" "You are going to remove %{project_name_with_namespace}.\n" @@ -1132,33 +1153,38 @@ msgid "" "You are going to remove the fork relationship to source project " "%{forked_from_project}. Are you ABSOLUTELY sure?" msgstr "" +"Вы собираетесь удалить связь форка с исходным проектом " +"%{forked_from_project}. Вы АБСОЛЮТНО уверены?" msgid "" "You are going to transfer %{project_name_with_namespace} to another owner. " "Are you ABSOLUTELY sure?" msgstr "" +"Вы собираетесь передать проект %{project_name_with_namespace} другому " +"владельцу. Вы АБСОЛЮТНО уверены?" msgid "You can only add files when you are on a branch" -msgstr "" +msgstr "Вы можете добавлять только файлы, когда находитесь в ветке" msgid "You have reached your project limit" msgstr "Вы достигли ограничения в вашем проекте" msgid "You must sign in to star a project" -msgstr "" +msgstr "Необходимо войти, чтобы оценить проект" msgid "You need permission." -msgstr "" +msgstr "Вам нужно разрешение." msgid "You will not get any notifications via email" -msgstr "" +msgstr "Вы не получите никаких уведомлений по электронной почте" msgid "You will only receive notifications for the events you choose" -msgstr "" +msgstr "Вы будете получать уведомления только о выбранных вами событиях" msgid "" "You will only receive notifications for threads you have participated in" msgstr "" +"Вы будете получать уведомления только о тех тредах, в которых вы участвовали" msgid "You will receive notifications for any activity" msgstr "Вы будете получать уведомления о любых действиях" @@ -1167,25 +1193,31 @@ msgid "" "You will receive notifications only for comments in which you were " "@mentioned" msgstr "" +"Вы будете получать уведомления только для комментариев, в которых вы были " +"@упомянуты" msgid "" "You won't be able to pull or push project code via %{protocol} until you " "%{set_password_link} on your account" msgstr "" +"Вы не сможете получать и отправлять код проекта через %{protocol} пока " +"%{set_password_link} в ваш аккаунт" msgid "" "You won't be able to pull or push project code via SSH until you " "%{add_ssh_key_link} to your profile" msgstr "" +"Вы не сможете получать и отправлять код проекта через SSH пока " +"%{add_ssh_key_link} в ваш профиль." msgid "Your name" msgstr "Ваше имя" msgid "day" msgid_plural "days" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "день" +msgstr[1] "дни" +msgstr[2] "дни" msgid "new merge request" msgstr "новый запрос на слияние" @@ -1195,7 +1227,7 @@ msgstr "email для уведомлений" msgid "parent" msgid_plural "parents" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "источник" +msgstr[1] "источники" +msgstr[2] "источники" From 9fcc28d42850eb7aa891b701721f803ebb0a3d55 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Jul 2017 09:04:22 +0100 Subject: [PATCH 015/130] moved declaration of `breadcrumb_title` into `page_title` method `breadcrumb_title` is then overriden when needed --- app/helpers/page_layout_helper.rb | 4 ++++ app/views/admin/appearances/show.html.haml | 1 - app/views/admin/application_settings/show.html.haml | 1 - app/views/admin/applications/edit.html.haml | 2 ++ app/views/admin/applications/new.html.haml | 2 ++ app/views/admin/hooks/index.html.haml | 1 - app/views/admin/services/index.html.haml | 1 - app/views/dashboard/activity.html.haml | 1 - app/views/dashboard/issues.html.haml | 1 - app/views/dashboard/merge_requests.html.haml | 1 - app/views/dashboard/projects/index.html.haml | 1 - app/views/groups/activity.html.haml | 1 - app/views/groups/edit.html.haml | 1 - app/views/groups/group_members/index.html.haml | 1 - app/views/groups/issues.html.haml | 1 - app/views/groups/merge_requests.html.haml | 1 - app/views/profiles/accounts/show.html.haml | 1 - app/views/profiles/audit_log.html.haml | 1 - app/views/profiles/two_factor_auths/show.html.haml | 1 - app/views/projects/activity.html.haml | 1 - app/views/projects/boards/_show.html.haml | 1 - app/views/projects/edit.html.haml | 1 - app/views/projects/graphs/charts.html.haml | 1 - app/views/projects/graphs/show.html.haml | 1 - app/views/projects/network/show.html.haml | 1 - app/views/projects/pipelines/charts.html.haml | 1 - app/views/projects/project_members/index.html.haml | 1 - app/views/projects/settings/ci_cd/show.html.haml | 1 - 28 files changed, 8 insertions(+), 25 deletions(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 3286a92a8a7..c9f5166d0a4 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -4,6 +4,10 @@ module PageLayoutHelper @page_title.push(*titles.compact) if titles.any? + if show_new_nav? && titles.any? && !defined?(@breadcrumb_title) + @breadcrumb_title = @page_title.first + end + # Segments are seperated by middot @page_title.join(" \u00b7 ") end diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml index 3d1929a8b70..454b779842c 100644 --- a/app/views/admin/appearances/show.html.haml +++ b/app/views/admin/appearances/show.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Appearance" - page_title "Appearance" %h3.page-title diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml index 2e7f47e261a..ecc46d86afe 100644 --- a/app/views/admin/application_settings/show.html.haml +++ b/app/views/admin/application_settings/show.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Settings" - page_title "Settings" %h3.page-title Settings diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml index c596866bde2..c9ad44be1e9 100644 --- a/app/views/admin/applications/edit.html.haml +++ b/app/views/admin/applications/edit.html.haml @@ -1,4 +1,6 @@ - page_title "Edit", @application.name, "Applications" +- @breadcrumb_title = "Applications" + %h3.page-title Edit application - @url = admin_application_path(@application) = render 'form', application: @application diff --git a/app/views/admin/applications/new.html.haml b/app/views/admin/applications/new.html.haml index 6310d89bd6b..a5d89ed9857 100644 --- a/app/views/admin/applications/new.html.haml +++ b/app/views/admin/applications/new.html.haml @@ -1,4 +1,6 @@ - page_title "New Application" +- @breadcrumb_title = "Applications" + %h3.page-title New application - @url = admin_applications_path = render 'form', application: @application diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 89592d4464e..e92b8bc39f4 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "System Hooks" - page_title 'System Hooks' %h3.page-title System hooks diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml index 4b5147b9cac..50132572096 100644 --- a/app/views/admin/services/index.html.haml +++ b/app/views/admin/services/index.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Service Templates" - page_title "Service Templates" %h3.page-title Service templates %p.light Service template allows you to set default values for project services diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index 60dfa75351b..ad35d05c29a 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Activity" - @hide_top_links = true - @no_container = true diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index d2fb4ab101e..52e0012fd7d 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Issues" - @hide_top_links = true - page_title "Issues" - header_title "Issues", issues_dashboard_path(assignee_id: current_user.id) diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index df2470a0118..c3fe14da2b2 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Merge Requests" - @hide_top_links = true - page_title "Merge Requests" - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 7ac6cf06fb9..ec6cb1a9624 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -1,6 +1,5 @@ - @no_container = true - @hide_top_links = true -- @breadcrumb_title = "Projects" = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml index adc379e11d4..3969e56f937 100644 --- a/app/views/groups/activity.html.haml +++ b/app/views/groups/activity.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Activity" = content_for :meta_tags do = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 5cb4d05960d..9ebb3894c55 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Settings" = render "groups/settings_head" .panel.panel-default.prepend-top-default .panel-heading diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 9e6c0b27834..ad9d5562ded 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Members" - page_title "Members" .project-members-page.prepend-top-default diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index a7003f6cd33..735d9390699 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Issues" - page_title "Issues" - group_issues_exists = group_issues(@group).exists? = render "head_issues" diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index a43061dacfc..997c82c77d9 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Merge Requests" - page_title "Merge Requests" - if show_new_nav? && current_user diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index c5917ae5aeb..ed079ed7dfb 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Account" - page_title "Account" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index 00d61e5b925..1a392e29e2a 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Authentication log" - page_title "Authentication log" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 326a87f579f..037cb30efb9 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -3,7 +3,6 @@ - add_to_breadcrumbs("Account", profile_account_path) - else - header_title "Two-Factor Authentication", profile_two_factor_auth_path -- @breadcrumb_title = "Two-Factor Authentication" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index dc79be269c6..9e2688e492e 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -1,5 +1,4 @@ - @no_container = true -- @breadcrumb_title = "Activity" - if show_new_nav? - add_to_breadcrumbs("Project", project_path(@project)) diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml index 49a92240f91..2076e46fde8 100644 --- a/app/views/projects/boards/_show.html.haml +++ b/app/views/projects/boards/_show.html.haml @@ -1,6 +1,5 @@ - @no_container = true - @content_class = "issue-boards-content" -- @breadcrumb_title = "Board" - page_title "Boards" - if show_new_nav? diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 9f4d0faf3a2..087cb804449 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -1,5 +1,4 @@ - @content_class = "limit-container-width" unless fluid_layout -- @breadcrumb_title = "Settings" = render "projects/settings/head" .project-edit-container diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 36920260ae8..249b9d82ad9 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -1,5 +1,4 @@ - @no_container = true -- @breadcrumb_title = "Charts" - page_title "Charts" - if show_new_nav? - add_to_breadcrumbs("Repository", project_tree_path(@project)) diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 2d37963fb7d..4256a8c4d7e 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,5 +1,4 @@ - @no_container = true -- @breadcrumb_title = "Contributors" - page_title "Contributors" - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('common_d3') diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 314914fd7b2..f660c156297 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Graph" - page_title "Graph", @ref - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('network') diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index 7cc43501497..fd3ad69d85d 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,5 +1,4 @@ - @no_container = true -- @breadcrumb_title = "Charts" - page_title _("Charts"), _("Pipelines") - if show_new_nav? - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project)) diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 74b10574da0..9f7c5a315eb 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,4 +1,3 @@ -- @breadcrumb_title = "Members" - page_title "Members" - if show_new_nav? diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 3f118f8933d..0c4130857da 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -1,5 +1,4 @@ - @content_class = "limit-container-width" unless fluid_layout -- @breadcrumb_title = "Pipelines" - page_title "Pipelines" - if show_new_nav? From 13cc761a09a88138294300cdcc26a6db28f3d67d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Jul 2017 09:16:32 +0100 Subject: [PATCH 016/130] moved `@breadcrumb_title` out of the HAML & into a helper method --- app/helpers/breadcrumbs_helper.rb | 6 ++++++ app/helpers/page_layout_helper.rb | 2 +- app/views/admin/applications/edit.html.haml | 2 +- app/views/admin/applications/new.html.haml | 2 +- app/views/admin/broadcast_messages/edit.html.haml | 2 +- app/views/admin/broadcast_messages/index.html.haml | 2 +- app/views/admin/services/edit.html.haml | 2 +- app/views/groups/new.html.haml | 1 + app/views/groups/show.html.haml | 2 +- app/views/layouts/nav/_breadcrumbs.html.haml | 3 +-- app/views/profiles/show.html.haml | 2 +- app/views/projects/blob/edit.html.haml | 2 +- app/views/projects/blob/new.html.haml | 2 +- app/views/projects/blob/show.html.haml | 2 +- app/views/projects/issues/new.html.haml | 1 + app/views/projects/merge_requests/creations/new.html.haml | 2 +- app/views/projects/new.html.haml | 1 + app/views/projects/pipeline_schedules/index.html.haml | 2 +- app/views/projects/pipeline_schedules/new.html.haml | 2 +- app/views/projects/services/edit.html.haml | 2 +- app/views/projects/show.html.haml | 2 +- app/views/projects/tree/show.html.haml | 2 +- app/views/projects/wikis/show.html.haml | 2 +- app/views/snippets/new.html.haml | 1 + 24 files changed, 29 insertions(+), 20 deletions(-) diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb index 6eb1060ed4c..abe8edd6a8c 100644 --- a/app/helpers/breadcrumbs_helper.rb +++ b/app/helpers/breadcrumbs_helper.rb @@ -16,4 +16,10 @@ module BreadcrumbsHelper request.path end end + + def breadcrumb_title(title) + return if defined?(@breadcrumb_title) + + @breadcrumb_title = title + end end diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index c9f5166d0a4..73b2ec66ee0 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -5,7 +5,7 @@ module PageLayoutHelper @page_title.push(*titles.compact) if titles.any? if show_new_nav? && titles.any? && !defined?(@breadcrumb_title) - @breadcrumb_title = @page_title.first + @breadcrumb_title = @page_title[-1] end # Segments are seperated by middot diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml index c9ad44be1e9..1d28d1c6c56 100644 --- a/app/views/admin/applications/edit.html.haml +++ b/app/views/admin/applications/edit.html.haml @@ -1,5 +1,5 @@ - page_title "Edit", @application.name, "Applications" -- @breadcrumb_title = "Applications" +- breadcrumb_title "Applications" %h3.page-title Edit application - @url = admin_application_path(@application) diff --git a/app/views/admin/applications/new.html.haml b/app/views/admin/applications/new.html.haml index a5d89ed9857..af0cc639670 100644 --- a/app/views/admin/applications/new.html.haml +++ b/app/views/admin/applications/new.html.haml @@ -1,5 +1,5 @@ - page_title "New Application" -- @breadcrumb_title = "Applications" +- breadcrumb_title "Applications" %h3.page-title New application - @url = admin_applications_path diff --git a/app/views/admin/broadcast_messages/edit.html.haml b/app/views/admin/broadcast_messages/edit.html.haml index d0e4d4435dd..8cbc4597e32 100644 --- a/app/views/admin/broadcast_messages/edit.html.haml +++ b/app/views/admin/broadcast_messages/edit.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Messages" +- breadcrumb_title "Messages" - page_title "Broadcast Messages" = render 'form' diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml index 2e4390c3614..b806882eee3 100644 --- a/app/views/admin/broadcast_messages/index.html.haml +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Messages" +- breadcrumb_title "Messages" - page_title "Broadcast Messages" %h3.page-title diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml index 0a641c3f7a6..30a759f9913 100644 --- a/app/views/admin/services/edit.html.haml +++ b/app/views/admin/services/edit.html.haml @@ -1,3 +1,3 @@ -- @breadcrumb_title = "Service Templates" +- breadcrumb_title "Service Templates" - page_title @service.title, "Service Templates" = render 'form' diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 10eb8ce6cc7..e9daac95ca1 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -1,4 +1,5 @@ - @breadcrumb_link = dashboard_groups_path +- breadcrumb_title "Groups" - @hide_top_links = true - page_title 'New Group' - header_title "Groups", dashboard_groups_path diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index fcdd0a84990..e07f61c94e4 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- @breadcrumb_title = "Group" +- breadcrumb_title "Group" = content_for :meta_tags do = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index 0bdf7819547..9aed0efae1c 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -1,4 +1,3 @@ -- breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize - breadcrumb_link = breadcrumb_title_link - hide_top_links = @hide_top_links || false @@ -18,7 +17,7 @@ - if @breadcrumbs_extra_links - @breadcrumbs_extra_links.each do |extra| %li= link_to extra[:text], extra[:link] - %li= link_to breadcrumb_title, breadcrumb_link + %li= link_to @breadcrumb_title, breadcrumb_link - if content_for?(:breadcrumbs_extra) .breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra = yield :header_content diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 6d97d08ed12..a8ae0b92334 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Profile" +- breadcrumb_title "Profile" - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 43fef9f134f..992fe7f717f 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Repository" +- breadcrumb_title "Repository" - @no_container = true - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 4433aed2023..a4263774dfd 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Repository" +- breadcrumb_title "Repository" - page_title "New File", @path.presence, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index a3c6c57607c..7dd834e84b5 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Repository" +- breadcrumb_title "Repository" - @no_container = true - page_title @blob.path, @ref diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml index e8aae0f47e2..60fe442014f 100644 --- a/app/views/projects/issues/new.html.haml +++ b/app/views/projects/issues/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Issues" - page_title "New Issue" %h3.page-title diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml index 59a88c68009..3220512d60d 100644 --- a/app/views/projects/merge_requests/creations/new.html.haml +++ b/app/views/projects/merge_requests/creations/new.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Merge Requests" +- breadcrumb_title "Merge Requests" - page_title "New Merge Request" - if @merge_request.can_be_created && !params[:change_branches] diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 8faa49d8224..a2d7a21d5f6 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,4 +1,5 @@ - @breadcrumb_link = dashboard_projects_path +- breadcrumb_title "Projects" - @hide_top_links = true - page_title 'New Project' - header_title "Projects", dashboard_projects_path diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 89042ef4a03..8426b29bb14 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Schedules" +- breadcrumb_title "Schedules" - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' diff --git a/app/views/projects/pipeline_schedules/new.html.haml b/app/views/projects/pipeline_schedules/new.html.haml index 115c43a0aec..c7237cb96d8 100644 --- a/app/views/projects/pipeline_schedules/new.html.haml +++ b/app/views/projects/pipeline_schedules/new.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Schedules" +- breadcrumb_title "Schedules" - @breadcrumb_link = namespace_project_pipeline_schedules_path(@project.namespace, @project) - page_title _("New Pipeline Schedule") diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml index 3d1d62b886a..8056217bb1e 100644 --- a/app/views/projects/services/edit.html.haml +++ b/app/views/projects/services/edit.html.haml @@ -1,4 +1,4 @@ -- @breadcrumb_title = "Integrations" +- breadcrumb_title "Integrations" - page_title @service.title, "Services" - if show_new_nav? diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index c366fb334fe..49d0a6828fe 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- @breadcrumb_title = "Project" +- breadcrumb_title "Project" - @content_class = "limit-container-width" unless fluid_layout - flash_message_container = show_new_nav? ? :new_global_flash : :flash_message diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 2de6d50da3a..c8587245f88 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- @breadcrumb_title = _("Repository") +- breadcrumb_title _("Repository") - @content_class = "limit-container-width" unless fluid_layout - page_title @path.presence || _("Files"), @ref diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 42602c6366b..9dadd685ea2 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,5 +1,5 @@ - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout -- @breadcrumb_title = "Wiki" +- breadcrumb_title "Wiki" - page_title @page.title.capitalize, "Wiki" .wiki-page-header.has-sidebar-toggle diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml index 513e26e1d81..f01915107e3 100644 --- a/app/views/snippets/new.html.haml +++ b/app/views/snippets/new.html.haml @@ -1,4 +1,5 @@ - @hide_top_links = true +- breadcrumb_title "Snippets" - page_title "New Snippet" %h3.page-title New Snippet From 43cc8bd4583f7239657565675b8e5e48a2f3fd59 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Jul 2017 09:35:53 +0100 Subject: [PATCH 017/130] fixed some inconsistencies --- app/views/groups/labels/new.html.haml | 1 + app/views/groups/milestones/new.html.haml | 1 + app/views/projects/commits/show.html.haml | 1 + app/views/projects/compare/show.html.haml | 1 + app/views/projects/environments/new.html.haml | 1 + app/views/projects/labels/new.html.haml | 1 + app/views/projects/milestones/new.html.haml | 1 + app/views/projects/network/show.html.haml | 1 + app/views/projects/pipelines/new.html.haml | 1 + 9 files changed, 9 insertions(+) diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml index 2be87460b1d..ae240490bbd 100644 --- a/app/views/groups/labels/new.html.haml +++ b/app/views/groups/labels/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Labels" - page_title 'New Label' - header_title group_title(@group, 'Labels', group_labels_path(@group)) diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index e24844661ee..eca7fb9ddb1 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Milestones" - page_title "Milestones" - header_title group_title(@group, "Milestones", group_milestones_path(@group)) diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 61724e99bf2..844ebb65148 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title _("Commits") - page_title _("Commits"), @ref = content_for :meta_tags do diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 42a21d33013..8bc863f77b3 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title "Compare" - page_title "#{params[:from]}...#{params[:to]}" - if show_new_nav? - add_to_breadcrumbs("Repository", project_tree_path(@project)) diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index 24638c77cbb..88f43a1e7e4 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title "Environments" - page_title 'New Environment' = render "projects/pipelines/head" diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 79e90b7ca3b..562b6fb8d8c 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title "Labels" - page_title "New Label" = render "shared/mr_head" diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml index 586eb909afa..84ffbc0a926 100644 --- a/app/views/projects/milestones/new.html.haml +++ b/app/views/projects/milestones/new.html.haml @@ -1,4 +1,5 @@ - @no_container = true +- breadcrumb_title "Milestones" - page_title "New Milestone" = render "shared/mr_head" diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index f660c156297..ab948df4a3f 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Graph" - page_title "Graph", @ref - content_for :page_specific_javascripts do = page_specific_javascript_bundle_tag('network') diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index 308f2611e02..c966df62856 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -1,3 +1,4 @@ +- breadcrumb_title "Pipelines" - page_title "New Pipeline" %h3.page-title From 6acb708db7e1b5054f764ba8cef4c89566ee4c53 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Jul 2017 10:30:42 +0100 Subject: [PATCH 018/130] fixed admin breadcrumb titles --- app/views/admin/applications/edit.html.haml | 1 - app/views/admin/applications/new.html.haml | 2 +- app/views/admin/services/edit.html.haml | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml index 1d28d1c6c56..13b583e6072 100644 --- a/app/views/admin/applications/edit.html.haml +++ b/app/views/admin/applications/edit.html.haml @@ -1,5 +1,4 @@ - page_title "Edit", @application.name, "Applications" -- breadcrumb_title "Applications" %h3.page-title Edit application - @url = admin_application_path(@application) diff --git a/app/views/admin/applications/new.html.haml b/app/views/admin/applications/new.html.haml index af0cc639670..346c58877d9 100644 --- a/app/views/admin/applications/new.html.haml +++ b/app/views/admin/applications/new.html.haml @@ -1,5 +1,5 @@ -- page_title "New Application" - breadcrumb_title "Applications" +- page_title "New Application" %h3.page-title New application - @url = admin_applications_path diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml index 30a759f9913..53d970e33c1 100644 --- a/app/views/admin/services/edit.html.haml +++ b/app/views/admin/services/edit.html.haml @@ -1,3 +1,2 @@ -- breadcrumb_title "Service Templates" - page_title @service.title, "Service Templates" = render 'form' From 1663587ccd9cd12b11efff51a6744bc33a664055 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Jul 2017 11:44:24 +0100 Subject: [PATCH 019/130] restored scss for new_sidebar --- app/assets/stylesheets/new_sidebar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 3dff97b6f3d..07b487cd090 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -30,7 +30,7 @@ $new-sidebar-width: 220px; font-weight: 600; display: flex; align-items: center; - padding: 10px 16px 10px; + padding: 10px 16px 10px 10px; color: $gl-text-color; .avatar-container { From c4d35a415ac381a4a9f3dc6b164855cfb1f6c43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 13 Jul 2017 14:49:12 +0200 Subject: [PATCH 020/130] Don't install fog-aws and mime-types gems in scripts/prepare_build.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead, install them only for the 'update-knapsack' job. Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 1 + scripts/prepare_build.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a54b38d284d..fd3a9ce0986 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -180,6 +180,7 @@ update-knapsack: <<: *only-canonical-masters stage: post-test script: + - retry gem install fog-aws mime-types - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json - '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH' diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index 68114d149c4..39806901274 100644 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -10,7 +10,7 @@ fi # Only install knapsack after bundle install! Otherwise oddly some native # gems could not be found under some circumstance. No idea why, hours wasted. -retry gem install knapsack fog-aws mime-types +retry gem install knapsack cp config/gitlab.yml.example config/gitlab.yml From 2f6d02c4eed0027f0e319dbb7520138658c44d4f Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 13 Jul 2017 12:11:09 -0500 Subject: [PATCH 021/130] Always return the translated level name. There are many places that expect the `.level_name` method to return the translated level name. --- lib/gitlab/visibility_level.rb | 4 ++-- locale/es/gitlab.po | 5 ++++- locale/gitlab.pot | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index 48f3d950779..c60bd91ea6e 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -89,12 +89,12 @@ module Gitlab end def level_name(level) - level_name = 'Unknown' + level_name = N_('VisibilityLevel|Unknown') options.each do |name, lvl| level_name = name if lvl == level.to_i end - level_name + s_(level_name) end def level_value(level) diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index 760a60f89d4..5c669d51a68 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2017-07-12 12:35-0500\n" +"PO-Revision-Date: 2017-07-13 12:10-0500\n" "Language-Team: Spanish\n" "Language: es\n" "MIME-Version: 1.0\n" @@ -1059,6 +1059,9 @@ msgstr "Privado" msgid "VisibilityLevel|Public" msgstr "Público" +msgid "VisibilityLevel|Unknown" +msgstr "Desconocido" + msgid "Want to see the data? Please ask an administrator for access." msgstr "¿Quieres ver los datos? Por favor pide acceso al administrador." diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8f33c494de9..babef3ed0af 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-12 12:31-0500\n" -"PO-Revision-Date: 2017-07-12 12:31-0500\n" +"POT-Creation-Date: 2017-07-13 12:07-0500\n" +"PO-Revision-Date: 2017-07-13 12:07-0500\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -1060,6 +1060,9 @@ msgstr "" msgid "VisibilityLevel|Public" msgstr "" +msgid "VisibilityLevel|Unknown" +msgstr "" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" From 0666ceff1e5716c3c6002d8208c888f3309f0ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 14 Jul 2017 09:57:22 +0800 Subject: [PATCH 022/130] Synchronous zanata in uk translation again --- locale/uk/gitlab.po | 50 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index 4f50dd63d93..0494374c2c6 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -8,13 +8,13 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-11 04:26-0400\n" +"PO-Revision-Date: 2017-07-12 09:05-0400\n" "Last-Translator: Андрей Витюк \n" -"Language-Team: Ukrainian\n" +"Language-Team: Ukrainian (https://translate.zanata.org/project/view/GitLab)\n" "Language: uk\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" msgid "%d additional commit has been omitted to prevent performance issues." msgid_plural "" @@ -48,13 +48,13 @@ msgid "About auto deploy" msgstr "Про авто розгортання" msgid "Active" -msgstr "Активні" +msgstr "Активний" msgid "Activity" msgstr "Активність" msgid "Add Changelog" -msgstr "Додати Changelog" +msgstr "Додати список змін (Changelog)" msgid "Add Contribution guide" msgstr "Додати керівництво для контрибуторів" @@ -110,7 +110,7 @@ msgid "Browse File" msgstr "Переглянути файл" msgid "Browse Files" -msgstr "Переглянути файли" +msgstr "Перегляд файлів" msgid "Browse files" msgstr "Перегляд файлів" @@ -137,7 +137,7 @@ msgid "ChangeTypeAction|Revert" msgstr "Скасувати" msgid "Changelog" -msgstr "Список змін" +msgstr "Список змін (Changelog)" msgid "Charts" msgstr "Графіки" @@ -310,7 +310,7 @@ msgid "CycleAnalyticsStage|Code" msgstr "Код" msgid "CycleAnalyticsStage|Issue" -msgstr "Проблеми" +msgstr "Проблема" msgid "CycleAnalyticsStage|Plan" msgstr "Планування" @@ -400,7 +400,7 @@ msgid "Files" msgstr "Файли" msgid "Filter by commit message" -msgstr "Фільтрувати повідомлення коммітів " +msgstr "Фільтрувати повідомлення коммітів" msgid "Find by path" msgstr "Пошук по шляху" @@ -424,7 +424,7 @@ msgid "ForkedFromProjectPath|Forked from" msgstr "Форк від" msgid "From issue creation until deploy to production" -msgstr "З моменту створення задачі до розгортання на ПРОД" +msgstr "З моменту створення проблеми до розгортання на ПРОД" msgid "From merge request merge until deploy to production" msgstr "З об'єднання запиту злиття до розгортання на ПРОД" @@ -506,9 +506,9 @@ msgstr "додати SSH ключ" msgid "New Issue" msgid_plural "New Issues" -msgstr[0] "Нова задача" -msgstr[1] "Нові задачі" -msgstr[2] "Новах задач" +msgstr[0] "Нова проблема" +msgstr[1] "Нові проблеми" +msgstr[2] "Новах проблем" msgid "New Pipeline Schedule" msgstr "Новий розклад Конвеєра" @@ -523,7 +523,7 @@ msgid "New file" msgstr "Новий файл" msgid "New issue" -msgstr "Нова задача" +msgstr "Нова проблема" msgid "New merge request" msgstr "Новий запит на злиття" @@ -553,7 +553,7 @@ msgid "Notification events" msgstr "Повідомлення про події" msgid "NotificationEvent|Close issue" -msgstr "Задача закрита" +msgstr "Проблема закрита" msgid "NotificationEvent|Close merge request" msgstr "Запит на об'єднання закритий" @@ -565,7 +565,7 @@ msgid "NotificationEvent|Merge merge request" msgstr "Об'єднати запит на злиття" msgid "NotificationEvent|New issue" -msgstr "Нова задача" +msgstr "Нова проблема" msgid "NotificationEvent|New merge request" msgstr "Новий запит на злиття" @@ -574,13 +574,13 @@ msgid "NotificationEvent|New note" msgstr "Нова нотатка" msgid "NotificationEvent|Reassign issue" -msgstr "Перепризначити задачу" +msgstr "Перепризначити проблему" msgid "NotificationEvent|Reassign merge request" msgstr "Перепризначити запит на злиття" msgid "NotificationEvent|Reopen issue" -msgstr "Повторне відкриття задачі" +msgstr "Повторне відкриття проблему" msgid "NotificationEvent|Successful pipeline" msgstr "Успішно в Конвеєрі" @@ -760,16 +760,16 @@ msgid "RefSwitcher|Tags" msgstr "Теги" msgid "Related Commits" -msgstr "Схожі Комміти" +msgstr "Пов'язані Комміти" msgid "Related Deployed Jobs" -msgstr "" +msgstr "Пов’язані розгорнуті задачі (Jobs)" msgid "Related Issues" -msgstr "" +msgstr "Пов’язані Проблеми (Issues)" msgid "Related Jobs" -msgstr "" +msgstr "Пов’язані Задачі (Jobs)" msgid "Related Merge Requests" msgstr "Пов'язані запити на злиття" @@ -877,9 +877,9 @@ msgid "" "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." msgstr "" -"Стадія звернення час, який буде потрібно з моменту створення звернення до " -"призначення зверненням віхи, або додавання звернення в вашу дошку звернень. " -"Почніть створювати звернення, щоб побачити подробиці для цієї стадії." +"Етап випуску показує, скільки часу потрібно від створення проблеми до " +"присвоєння випуску, або додавання проблеми в вашу дошку проблем. Почніть " +"створювати проблеми, щоб переглядати дані для цього етапу." msgid "The phase of the development lifecycle." msgstr "Фаза життєвого циклу розробки." From 9b1b43b018a2774db7028a634bdf778c2bc54b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 14 Jul 2017 13:31:38 +0800 Subject: [PATCH 023/130] change msgid --- locale/uk/gitlab.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index 0494374c2c6..59a7eb6e1b3 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -16,15 +16,15 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." +"%s additional commits have been omitted to prevent performance issues." msgstr[0] "" -"%d доданий Комміт був виключений для запобігання проблем з продуктивністю." +"%s доданий Комміт був виключений для запобігання проблем з продуктивністю." msgstr[1] "" -"%d доданих коммітів були виключені для запобігання проблем з продуктивністю." +"%s доданих коммітів були виключені для запобігання проблем з продуктивністю." msgstr[2] "" -"%d доданих коммітів були виключені для запобігання проблем з продуктивністю." +"%s доданих коммітів були виключені для запобігання проблем з продуктивністю." msgid "%d commit" msgid_plural "%d commits" From 7df7dfb6a46a52d3d6480f2d5f446086fbcf124c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 14 Jul 2017 13:32:35 +0800 Subject: [PATCH 024/130] change msgid --- locale/ru/gitlab.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po index 236d62ac78e..4643bed98e2 100644 --- a/locale/ru/gitlab.po +++ b/locale/ru/gitlab.po @@ -16,17 +16,17 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" -msgid "%d additional commit has been omitted to prevent performance issues." +msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "" -"%d additional commits have been omitted to prevent performance issues." +"%s additional commits have been omitted to prevent performance issues." msgstr[0] "" -"%d добавленный коммит был исключен для предотвращения проблем с " +"%s добавленный коммит был исключен для предотвращения проблем с " "производительностью." msgstr[1] "" -"%d добавленные коммиты были исключены для предотвращения проблем с " +"%s добавленные коммиты были исключены для предотвращения проблем с " "производительностью." msgstr[2] "" -"%d добавленные коммиты были исключены для предотвращения проблем с " +"%s добавленные коммиты были исключены для предотвращения проблем с " "производительностью." msgid "%d commit" From 7b1affd426fbf24445df25e439cbda9b6bd4b046 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Jul 2017 08:56:06 +0100 Subject: [PATCH 025/130] use `.last` instead --- app/helpers/page_layout_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 73b2ec66ee0..b30b2eb1d03 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -5,7 +5,7 @@ module PageLayoutHelper @page_title.push(*titles.compact) if titles.any? if show_new_nav? && titles.any? && !defined?(@breadcrumb_title) - @breadcrumb_title = @page_title[-1] + @breadcrumb_title = @page_title.last end # Segments are seperated by middot From 46b38e4bbd08ecf9ce0c6ea7c39e3ab3c0045dc8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Jul 2017 18:17:43 +0100 Subject: [PATCH 026/130] fixed search & starred projets --- app/views/dashboard/projects/starred.html.haml | 2 +- app/views/search/show.html.haml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index da80515c17f..ae1d733a516 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true - @no_container = true - +- breadcrumb_title "Projects" - page_title "Starred Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 4834441f786..499697f2777 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,4 +1,5 @@ - @hide_top_links = true +- breadcrumb_title "Search" - page_title @search_term .prepend-top-default From 566454c6114031f3859e15449cfccdc3a2cfdbd4 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Fri, 14 Jul 2017 23:52:03 -0400 Subject: [PATCH 027/130] Prometheus docs WIP --- doc/user/project/integrations/prometheus.md | 61 ++++++--------------- 1 file changed, 16 insertions(+), 45 deletions(-) diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 86ceb14b965..d4254cbc39b 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -17,35 +17,30 @@ the settings page with a default template. To configure the template, see the Integration with Prometheus requires the following: 1. GitLab 9.0 or higher -1. The [Kubernetes integration must be enabled][kube] on your project -1. Your app must be deployed on [Kubernetes][] -1. Prometheus must be configured to collect Kubernetes metrics +1. Prometheus must be configured to collect one of the [supported metrics](prometheus_library/metrics.md) 1. Each metric must be have a label to indicate the environment -1. GitLab must have network connectivity to the Prometheus sever +1. GitLab must have network connectivity to the Prometheus server -There are a few steps necessary to set up integration between Prometheus and -GitLab. +## Getting started with Prometheus monitoring -## Configuring Prometheus to collect Kubernetes metrics +Depending on your deployment and where you have located your Prometheus server, there are a few options to get started with Prometheus monitoring. -In order for Prometheus to collect Kubernetes metrics, you first must have a -Prometheus server up and running. You have two options here: +* If both GitLab and your applications are installed in the same Kubernetes cluster, you can leveraged the [bundled Prometheus server within GitLab](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes). +* If your applications are deployed on Kubernetes, but GitLab is not in the same cluster, then you can [configure a Prometheus server in your Kubernetes cluster](#configuring-your-own-prometheus-server-within-kubernetes). +* If your applications are not running in Kubernetes, [get started with Prometheus](#getting-started-with-proemtheus-outside-of-kubernetes). -- If you installed Omnibus GitLab inside of Kubernetes, you can simply use the - [bundled version of Prometheus][promgldocs]. In that case, follow the info in the - [Omnibus GitLab section](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes) - below. -- If you are using GitLab.com or installed GitLab outside of Kubernetes, you - will likely need to run a Prometheus server within the Kubernetes cluster. - Once installed, the easiest way to monitor Kubernetes is to simply use - Prometheus' support for [Kubernetes Service Discovery][prometheus-k8s-sd]. - In that case, follow the instructions on - [configuring your own Prometheus server within Kubernetes](#configuring-your-own-prometheus-server-within-kubernetes). +### Getting started with Prometheus outside of Kubernetes -### Configuring Omnibus GitLab Prometheus to monitor Kubernetes +Installing and configuring Prometheus to monitor applications is fairly straight forward. + +1. [Install Prometheus](https://prometheus.io/docs/introduction/install/) +1. Set up one of the [supported monitoring targets](metrics.md) +1. Configure the Prometheus server to [collect their metrics](https://prometheus.io/docs/operating/configuration/#scrape_config) + +### Configuring Omnibus GitLab Prometheus to monitor Kubernetes deployments With Omnibus GitLab running inside of Kubernetes, you can leverage the bundled -version of Prometheus to collect the required metrics. +version of Prometheus to collect the required metrics. Once enabled, Prometheus will 1. Read how to configure the bundled Prometheus server in the [Administration guide][gitlab-prometheus-k8s-monitor]. @@ -133,30 +128,6 @@ to integrate with. ![Configure Prometheus Service](img/prometheus_service_configuration.png) -## Metrics and Labels - -GitLab retrieves performance data from two metrics, `container_cpu_usage_seconds_total` -and `container_memory_usage_bytes`. These metrics are collected from the -Kubernetes pods via Prometheus, and report CPU and Memory utilization of each -container or Pod running in the cluster. - -In order to isolate and only display relevant metrics for a given environment -however, GitLab needs a method to detect which pods are associated. To do that, -GitLab will specifically request metrics that have an `environment` tag that -matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug]. - -If you are using [GitLab Auto-Deploy][autodeploy] and one of the methods of -configuring Prometheus above, the `environment` will be automatically added. - -### GitLab Prometheus queries - -The queries utilized by GitLab are shown in the following table. - -| Metric | Query | -| ------ | ----- | -| Average Memory (MB) | `(sum(container_memory_usage_bytes{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}) / count(container_memory_usage_bytes{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"})) /1024/1024` | -| Average CPU Utilization (%) | `sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}) * 100` | - ## Monitoring CI/CD Environments Once configured, GitLab will attempt to retrieve performance metrics for any From a40fd9e9f33ad1ee94174b5ce743b71e5ea823f4 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Fri, 14 Jul 2017 23:52:21 -0400 Subject: [PATCH 028/130] WIP --- .../prometheus_library/kubernetes.md | 19 ++++++++++ .../prometheus_library/metrics.md | 35 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 doc/user/project/integrations/prometheus_library/kubernetes.md create mode 100644 doc/user/project/integrations/prometheus_library/metrics.md diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md new file mode 100644 index 00000000000..eb6b377dd62 --- /dev/null +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -0,0 +1,19 @@ +# Monitoring Kubernetes +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0 + +GitLab has support for automatically detecting and monitoring Kubernetes metrics. Kubernetes exposes Node level metrics out of the box via the built-in [Prometheus metrics support in cAdvisor](https://github.com/google/cadvisor). + +## Metrics supported + +| Name | Query | +| ---- | ----- | +| Average Memory Usage (MB) | (sum(container_memory_usage_bytes{container_name!="POD",%{environment_filter}}) / count(container_memory_usage_bytes{container_name!="POD",%{environment_filter}})) /1024/1024 | +| Average CPU Utilization (%) | sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}) * 100 | + +## Configuring Prometheus to monitor for Kubernetes node metrics + +Prometheus has internal support for discovering and monitoring of Kubernetes node metrics. + +If you have an Omnibus based GitLab installation within your Kubernetes cluster, you can leverage the bundled Prometheus server to [monitor Kubernetes](../../../../administration/monitoring/prometheus/index.md#configuring-prometheus-to-monitor-kubernetes). + +To configure your own Prometheus server diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md new file mode 100644 index 00000000000..f783b97c38f --- /dev/null +++ b/doc/user/project/integrations/prometheus_library/metrics.md @@ -0,0 +1,35 @@ +# Prometheus Metrics library +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0 + +GitLab offers + +## Metrics and Labels + +GitLab retrieves performance data from two metrics, `container_cpu_usage_seconds_total` +and `container_memory_usage_bytes`. These metrics are collected from the +Kubernetes pods via Prometheus, and report CPU and Memory utilization of each +container or Pod running in the cluster. + +In order to isolate and only display relevant metrics for a given environment +however, GitLab needs a method to detect which pods are associated. To do that, +GitLab will specifically request metrics that have an `environment` tag that +matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug]. + +If you are using [GitLab Auto-Deploy][autodeploy] and one of the methods of +configuring Prometheus above, the `environment` will be automatically added. + +## Configuring Prometheus to collect automatically collected metrics within Kubernetes + +In order for Prometheus to collect Kubernetes metrics, you first must have a +Prometheus server up and running. You have two options here: + +- If you installed Omnibus GitLab inside of Kubernetes, you can simply use the + [bundled version of Prometheus][promgldocs]. In that case, follow the info in the + [Omnibus GitLab section](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes) + below. +- If you are using GitLab.com or installed GitLab outside of Kubernetes, you + will likely need to run a Prometheus server within the Kubernetes cluster. + Once installed, the easiest way to monitor Kubernetes is to simply use + Prometheus' support for [Kubernetes Service Discovery][prometheus-k8s-sd]. + In that case, follow the instructions on + [configuring your own Prometheus server within Kubernetes](../prometheus.md#configuring-your-own-prometheus-server-within-kubernetes). From ef17dd1855e57cc129f4c504c0c89a50be287fa3 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Sun, 16 Jul 2017 22:09:56 -0400 Subject: [PATCH 029/130] Prometheus doc updates for 9.4 --- doc/ci/environments.md | 3 +- doc/ci/img/environments_monitoring.png | Bin 94408 -> 243491 bytes doc/user/project/integrations/prometheus.md | 9 +++-- .../prometheus_library/kubernetes.md | 15 +++++-- .../prometheus_library/metrics.md | 38 +++++++----------- .../integrations/samples/prometheus.yml | 38 ++++++++++++++++++ 6 files changed, 69 insertions(+), 34 deletions(-) diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 3393030210e..371622d3331 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -602,9 +602,8 @@ exist, you should see something like: >**Notes:** > - For the monitor dashboard to appear, you need to: - - Have enabled the [Kubernetes integration][kube] - - Have your app deployed on Kubernetes - Have enabled the [Prometheus integration][prom] + - Configured Prometheus to collect at least one [supported metric](prometheus_library/metrics.md) - With GitLab 9.2, all deployments to an environment are shown directly on the monitoring dashboard diff --git a/doc/ci/img/environments_monitoring.png b/doc/ci/img/environments_monitoring.png index 387b6c54b61fec3ea8c236ce756889059e8b929f..d9c46ea4c950eb28f90f78feed0becd097f475dc 100644 GIT binary patch literal 243491 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 z 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 literal 94408 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

yS#QG0Ao7rO2%LUApC*`NTT3gH$q8)y`Gfs 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 diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index d4254cbc39b..6b73452168a 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -23,11 +23,11 @@ Integration with Prometheus requires the following: ## Getting started with Prometheus monitoring -Depending on your deployment and where you have located your Prometheus server, there are a few options to get started with Prometheus monitoring. +Depending on your deployment and where you have located your GitLab server, there are a few options to get started with Prometheus monitoring. * If both GitLab and your applications are installed in the same Kubernetes cluster, you can leveraged the [bundled Prometheus server within GitLab](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes). * If your applications are deployed on Kubernetes, but GitLab is not in the same cluster, then you can [configure a Prometheus server in your Kubernetes cluster](#configuring-your-own-prometheus-server-within-kubernetes). -* If your applications are not running in Kubernetes, [get started with Prometheus](#getting-started-with-proemtheus-outside-of-kubernetes). +* If your applications are not running in Kubernetes, [get started with Prometheus](#getting-started-with-prometheus-outside-of-kubernetes). ### Getting started with Prometheus outside of Kubernetes @@ -69,7 +69,7 @@ kubectl apply -f path/to/prometheus.yml Once deployed, you should see the Prometheus service, deployment, and pod start within the `prometheus` namespace. The server will begin to collect metrics from each Kubernetes Node in the cluster, based on the configuration -provided in the template. +provided in the template. It will also attempt to collect metrics from any Kubernetes Pods that have been [annotated for Prometheus](https://prometheus.io/docs/operating/configuration/#pod). Since GitLab is not running within Kubernetes, the template provides external network access via a `NodePort` running on `30090`. This method allows access @@ -139,8 +139,9 @@ environment which has had a successful deployment. > [Introduced][ce-10408] in GitLab 9.2. > GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages. +> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics -Developers can view the performance impact of their changes within the merge +Developers can view theperformance impact of their changes within the merge request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot indicates when the current changes were deployed, with up to 30 minutes of performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md index eb6b377dd62..eb8cd821ddc 100644 --- a/doc/user/project/integrations/prometheus_library/kubernetes.md +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -1,7 +1,7 @@ # Monitoring Kubernetes > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0 -GitLab has support for automatically detecting and monitoring Kubernetes metrics. Kubernetes exposes Node level metrics out of the box via the built-in [Prometheus metrics support in cAdvisor](https://github.com/google/cadvisor). +GitLab has support for automatically detecting and monitoring Kubernetes metrics. Kubernetes exposes Node level metrics out of the box via the built-in [Prometheus metrics support in cAdvisor](https://github.com/google/cadvisor). No additional services or exporters are needed. ## Metrics supported @@ -12,8 +12,15 @@ GitLab has support for automatically detecting and monitoring Kubernetes metrics ## Configuring Prometheus to monitor for Kubernetes node metrics -Prometheus has internal support for discovering and monitoring of Kubernetes node metrics. +In order for Prometheus to collect Kubernetes metrics, you first must have a +Prometheus server up and running. You have two options here: -If you have an Omnibus based GitLab installation within your Kubernetes cluster, you can leverage the bundled Prometheus server to [monitor Kubernetes](../../../../administration/monitoring/prometheus/index.md#configuring-prometheus-to-monitor-kubernetes). +- If you have an Omnibus based GitLab installation within your Kubernetes cluster, you can leverage the bundled Prometheus server to [monitor Kubernetes](../../../../administration/monitoring/prometheus/index.md#configuring-prometheus-to-monitor-kubernetes). +- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/) or [our guide](../../../../administration/monitoring/prometheus/index.md#configuring-your-own-prometheus-server-within-kubernetes). -To configure your own Prometheus server +## Specifying the Environment label + +In order to isolate and only display relevant metrics for a given environment +however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments). + +If you are using [GitLab Auto-Deploy][autodeploy] and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added. diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md index f783b97c38f..2a1831c94ef 100644 --- a/doc/user/project/integrations/prometheus_library/metrics.md +++ b/doc/user/project/integrations/prometheus_library/metrics.md @@ -1,35 +1,25 @@ # Prometheus Metrics library > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0 -GitLab offers +GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are: +* [Kubernetes](kubernetes.md) +* [NGINX](nginx.md) +* [Amazon Cloud Watch](cloudwatch.md) -## Metrics and Labels +We have tried to surface the most important metrics for each exporter, and will be continuing to add support for additional exporters in future releases. If you would like to add support for other official exporters, [contributions](#adding-to-the-library) are welcome. -GitLab retrieves performance data from two metrics, `container_cpu_usage_seconds_total` -and `container_memory_usage_bytes`. These metrics are collected from the -Kubernetes pods via Prometheus, and report CPU and Memory utilization of each -container or Pod running in the cluster. +## Identifying Environments -In order to isolate and only display relevant metrics for a given environment -however, GitLab needs a method to detect which pods are associated. To do that, -GitLab will specifically request metrics that have an `environment` tag that +GitLab retrieves performance data from the configured Prometheus server, and attempts to identifying the presence of known metrics. Once identified, GitLab then needs to be able to map the data to a particular environment. + +In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do that, +GitLab will look for the required metrics which have a label that matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug]. -If you are using [GitLab Auto-Deploy][autodeploy] and one of the methods of -configuring Prometheus above, the `environment` will be automatically added. +For example if you are deploying to an environment named `production`, there must be a label for the metric with the value of `production`. -## Configuring Prometheus to collect automatically collected metrics within Kubernetes +## Adding to the library -In order for Prometheus to collect Kubernetes metrics, you first must have a -Prometheus server up and running. You have two options here: +We strive to support the 2-4 most important metrics for each common system service that supports Prometheus. If you are looking for support for a particular exporter which has not yet been added to the library, additions can be made [to the `additional_metrics.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/prometheus/additional_metrics.yml) file. -- If you installed Omnibus GitLab inside of Kubernetes, you can simply use the - [bundled version of Prometheus][promgldocs]. In that case, follow the info in the - [Omnibus GitLab section](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes) - below. -- If you are using GitLab.com or installed GitLab outside of Kubernetes, you - will likely need to run a Prometheus server within the Kubernetes cluster. - Once installed, the easiest way to monitor Kubernetes is to simply use - Prometheus' support for [Kubernetes Service Discovery][prometheus-k8s-sd]. - In that case, follow the instructions on - [configuring your own Prometheus server within Kubernetes](../prometheus.md#configuring-your-own-prometheus-server-within-kubernetes). +> Note: The library is only for monitoring public, common, system services which all customers can benefit from. Support for monitoring [customer proprietary metrics](https://gitlab.com/gitlab-org/gitlab-ee/issues/2273) will be added in an subsequent release. diff --git a/doc/user/project/integrations/samples/prometheus.yml b/doc/user/project/integrations/samples/prometheus.yml index 01bbcaffe1e..30b59e172a1 100644 --- a/doc/user/project/integrations/samples/prometheus.yml +++ b/doc/user/project/integrations/samples/prometheus.yml @@ -24,6 +24,44 @@ data: target_label: environment regex: (.+)-.+-.+ replacement: $1 + - job_name: kubernetes-pods + tls_config: + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + insecure_skip_verify: true + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" + kubernetes_sd_configs: + - role: pod + api_server: https://kubernetes.default.svc:443 + tls_config: + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" + relabel_configs: + - source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_scrape + action: keep + regex: 'true' + - source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_path + action: replace + target_label: __metrics_path__ + regex: "(.+)" + - source_labels: + - __address__ + - __meta_kubernetes_pod_annotation_prometheus_io_port + action: replace + regex: "([^:]+)(?::[0-9]+)?;([0-9]+)" + replacement: "$1:$2" + target_label: __address__ + - action: labelmap + regex: __meta_kubernetes_pod_label_(.+) + - source_labels: + - __meta_kubernetes_namespace + action: replace + target_label: kubernetes_namespace + - source_labels: + - __meta_kubernetes_pod_name + action: replace + target_label: kubernetes_pod_name --- apiVersion: v1 kind: Service From 971bd6779e2a207f23db97f782c81c9cec3f35bc Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Sun, 16 Jul 2017 22:10:22 -0400 Subject: [PATCH 030/130] Doc updates for 9.4 --- .../prometheus_library/cloudwatch.md | 25 ++++++++++++++++++ .../integrations/prometheus_library/nginx.md | 23 ++++++++++++++++ .../integrations/samples/cloudwatch.yml | 26 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 doc/user/project/integrations/prometheus_library/cloudwatch.md create mode 100644 doc/user/project/integrations/prometheus_library/nginx.md create mode 100644 doc/user/project/integrations/samples/cloudwatch.yml diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md new file mode 100644 index 00000000000..cc5cee36d28 --- /dev/null +++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md @@ -0,0 +1,25 @@ +# Monitoring AWS Resources +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4 + +GitLab has support for automatically detecting and monitoring AWS resources, starting with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). This is provided by leveraging the official [Cloudwatch exporter](https://github.com/prometheus/cloudwatch_exporter), which translates [Cloudwatch metrics](https://aws.amazon.com/cloudwatch/) into a Prometheus readable form. + +## Metrics supported + +| Name | Query | +| ---- | ----- | +| Throughput (req/sec) | sum(aws_elb_request_count_sum{%{environment_filter}}) / 60 | +| Latency (ms) | avg(aws_elb_latency_average{%{environment_filter}}) * 1000 | +| HTTP Error Rate (%) | sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}}) | + +## Configuring Prometheus to monitor for Cloudwatch metrics + +To get started with Cloudwatch monitoring, you should install and configure the [Cloudwatch exporter](https://github.com/hnlq715/nginx-vts-exporter) which retrieves and parses the specified Cloudwatch metrics and translates them into a Prometheus monitoring endpoint. + +Right now, the only AWS resource supported is the Elastic Load Balancer, whose Cloudwatch metrics can be found [here](http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-cloudwatch-metrics.html). + +A sample Cloudwatch Exporter configuration file, configured for basic AWS ELB monitoring, is [available for download](../samples/cloudwatch.yml). + +## Specifying the Environment label + +In order to isolate and only display relevant metrics for a given environment +however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments). diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md new file mode 100644 index 00000000000..fe238e74e36 --- /dev/null +++ b/doc/user/project/integrations/prometheus_library/nginx.md @@ -0,0 +1,23 @@ +# Monitoring NGINX +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4 + +GitLab has support for automatically detecting and monitoring NGINX. This is provided by leveraging the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates [VTS statistics](https://github.com/vozlt/nginx-module-vts) into a Prometheus readable form. + +## Metrics supported + +| Name | Query | +| ---- | ----- | +| Throughput (req/sec) | sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) | +| Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000 | +| HTTP Error Rate (%) | sum(nginx_responses_total{status_code="5xx", %{environment_filter}}) / sum(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}}) | + +## Configuring Prometheus to monitor for NGINX metrics + +To get started with NGINX monitoring, you should first enable the [VTS statistics](https://github.com/vozlt/nginx-module-vts)) module for your NGINX server. This will capture and display statistics in an HTML readable form. Next, you should install and configure the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter) which parses these statistics and translates them into a Prometheus monitoring endpoint. + +If you are using NGINX as your Kubernetes ingress, there is [upcoming direct support](https://github.com/kubernetes/ingress/pull/423) for enabling Prometheus monitoring in the 0.9.0 release. + +## Specifying the Environment label + +In order to isolate and only display relevant metrics for a given environment +however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments). diff --git a/doc/user/project/integrations/samples/cloudwatch.yml b/doc/user/project/integrations/samples/cloudwatch.yml new file mode 100644 index 00000000000..d9b58f52c32 --- /dev/null +++ b/doc/user/project/integrations/samples/cloudwatch.yml @@ -0,0 +1,26 @@ +region: us-east-1 + metrics: + - aws_namespace: AWS/ELB + aws_metric_name: RequestCount + aws_dimensions: [AvailabilityZone, LoadBalancerName] + aws_dimension_select: + LoadBalancerName: [gitlab-ha-lb] + aws_statistics: [Sum] + - aws_namespace: AWS/ELB + aws_metric_name: Latency + aws_dimensions: [AvailabilityZone, LoadBalancerName] + aws_dimension_select: + LoadBalancerName: [gitlab-ha-lb] + aws_statistics: [Average] + - aws_namespace: AWS/ELB + aws_metric_name: HTTPCode_Backend_2XX + aws_dimensions: [AvailabilityZone, LoadBalancerName] + aws_dimension_select: + LoadBalancerName: [gitlab-ha-lb] + aws_statistics: [Sum] + - aws_namespace: AWS/ELB + aws_metric_name: HTTPCode_Backend_5XX + aws_dimensions: [AvailabilityZone, LoadBalancerName] + aws_dimension_select: + LoadBalancerName: [gitlab-ha-lb] + aws_statistics: [Sum] From df410faa3d5820f0d597281b59fcd4bb849c8c0e Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Sun, 16 Jul 2017 22:35:59 -0400 Subject: [PATCH 031/130] Fix links --- doc/ci/environments.md | 2 +- doc/user/project/integrations/prometheus.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 371622d3331..df5c66a4c85 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -603,7 +603,7 @@ exist, you should see something like: > - For the monitor dashboard to appear, you need to: - Have enabled the [Prometheus integration][prom] - - Configured Prometheus to collect at least one [supported metric](prometheus_library/metrics.md) + - Configured Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/metrics.md) - With GitLab 9.2, all deployments to an environment are shown directly on the monitoring dashboard diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 6b73452168a..a23635d1999 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -34,7 +34,7 @@ Depending on your deployment and where you have located your GitLab server, ther Installing and configuring Prometheus to monitor applications is fairly straight forward. 1. [Install Prometheus](https://prometheus.io/docs/introduction/install/) -1. Set up one of the [supported monitoring targets](metrics.md) +1. Set up one of the [supported monitoring targets](prometheus_library/metrics.md) 1. Configure the Prometheus server to [collect their metrics](https://prometheus.io/docs/operating/configuration/#scrape_config) ### Configuring Omnibus GitLab Prometheus to monitor Kubernetes deployments From 9f85578c0a40f16ed4f1842f53a360d7d3ac0751 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Mon, 17 Jul 2017 00:29:21 -0400 Subject: [PATCH 032/130] Update admin docs --- doc/administration/monitoring/prometheus/index.md | 3 ++- doc/user/project/integrations/prometheus.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 695fdf09a87..c61c4d56443 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -95,8 +95,9 @@ Sample Prometheus queries: ## Configuring Prometheus to monitor Kubernetes > Introduced in GitLab 9.0. +> Pod monitoring introduced in GitLAb 9.4. -If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. +If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes and [annotated Pods](https://prometheus.io/docs/operating/configuration/#) in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. To disable the monitoring of Kubernetes: diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 86ceb14b965..4ccc13b16ab 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -45,7 +45,7 @@ Prometheus server up and running. You have two options here: ### Configuring Omnibus GitLab Prometheus to monitor Kubernetes With Omnibus GitLab running inside of Kubernetes, you can leverage the bundled -version of Prometheus to collect the required metrics. +version of Prometheus to collect the supported metrics. Once enabled, Prometheus will automatically begin monitoring Kubernetes Nodes and any [annotated Pods](https://prometheus.io/docs/operating/configuration/#). 1. Read how to configure the bundled Prometheus server in the [Administration guide][gitlab-prometheus-k8s-monitor]. From 1c84f668bc25179848c002d52a4738d379da2a62 Mon Sep 17 00:00:00 2001 From: Alexander Randa Date: Fri, 14 Jul 2017 16:36:37 +0300 Subject: [PATCH 033/130] Replaces dashboard/dashboard.feature spinach with rspec --- .../23036-replace-dashboard-spinach.yml | 4 + features/dashboard/dashboard.feature | 70 ---------------- features/steps/dashboard/dashboard.rb | 83 ------------------- features/steps/shared/project.rb | 5 -- spec/features/dashboard/groups_list_spec.rb | 4 +- .../issues_filter_spec.rb} | 64 +++++++++++--- .../features/dashboard/merge_requests_spec.rb | 17 ++++ .../milestones_spec.rb} | 0 spec/features/dashboard/projects_spec.rb | 47 ++++++++++- .../projects/merge_request_button_spec.rb | 14 ++-- spec/support/sorting_helper.rb | 18 ++++ 11 files changed, 145 insertions(+), 181 deletions(-) create mode 100644 changelogs/unreleased/23036-replace-dashboard-spinach.yml delete mode 100644 features/dashboard/dashboard.feature delete mode 100644 features/steps/dashboard/dashboard.rb rename spec/features/{dashboard_issues_spec.rb => dashboard/issues_filter_spec.rb} (58%) rename spec/features/{dashboard_milestones_spec.rb => dashboard/milestones_spec.rb} (100%) create mode 100644 spec/support/sorting_helper.rb diff --git a/changelogs/unreleased/23036-replace-dashboard-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-spinach.yml new file mode 100644 index 00000000000..b3197c4cfa6 --- /dev/null +++ b/changelogs/unreleased/23036-replace-dashboard-spinach.yml @@ -0,0 +1,4 @@ +--- +title: Replaces dashboard/dashboard.feature spinach with rspec +merge_request: 12876 +author: Alexander Randa (@randaalex) diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature deleted file mode 100644 index 1af4d46dec9..00000000000 --- a/features/dashboard/dashboard.feature +++ /dev/null @@ -1,70 +0,0 @@ -@dashboard -Feature: Dashboard - Background: - Given I sign in as a user - And I own project "Shop" - And project "Shop" has push event - And project "Shop" has CI enabled - And project "Shop" has CI build - And project "Shop" has labels: "bug", "feature", "enhancement" - And project "Shop" has issue: "bug report" - And I visit dashboard page - - Scenario: I should see projects list - Then I should see "New Project" link - Then I should see "Shop" project link - Then I should see "Shop" project CI status - - @javascript - Scenario: I should see activity list - And I visit dashboard activity page - Then I should see project "Shop" activity feed - - Scenario: I should see groups list - Given I have group with projects - And I visit dashboard page - Then I should see groups list - - @javascript - Scenario: I should see last push widget - Then I should see last push widget - And I click "Create Merge Request" link - Then I see prefilled new Merge Request page - - @javascript - Scenario: Sorting Issues - Given I visit dashboard issues page - And I sort the list by "Oldest updated" - And I visit dashboard activity page - And I visit dashboard issues page - Then The list should be sorted by "Oldest updated" - - @javascript - Scenario: Filtering Issues by label - Given project "Shop" has issue "Bugfix1" with label "feature" - When I visit dashboard issues page - And I filter the list by label "feature" - Then I should see "Bugfix1" in issues list - - @javascript - Scenario: Visiting Project's issues after sorting - Given I visit dashboard issues page - And I sort the list by "Oldest updated" - And I visit project "Shop" issues page - Then The list should be sorted by "Oldest updated" - - @javascript - Scenario: Sorting Merge Requests - Given I visit dashboard merge requests page - And I sort the list by "Oldest updated" - And I visit dashboard activity page - And I visit dashboard merge requests page - Then The list should be sorted by "Oldest updated" - - @javascript - Scenario: Visiting Project's merge requests after sorting - Given project "Shop" has a "Bugfix MR" merge request open - And I visit dashboard merge requests page - And I sort the list by "Oldest updated" - And I visit project "Shop" merge requests page - Then The list should be sorted by "Oldest updated" diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb deleted file mode 100644 index 0960f49aad3..00000000000 --- a/features/steps/dashboard/dashboard.rb +++ /dev/null @@ -1,83 +0,0 @@ -class Spinach::Features::Dashboard < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - include SharedIssuable - - step 'I should see "New Project" link' do - expect(page).to have_link "New project" - end - - step 'I should see "Shop" project link' do - expect(page).to have_link "Shop" - end - - step 'I should see "Shop" project CI status' do - expect(page).to have_link "Commit: skipped" - end - - step 'I should see last push widget' do - expect(page).to have_content "You pushed to fix" - expect(page).to have_link "Create merge request" - end - - step 'I click "Create merge request" link' do - find_link("Create merge request", visible: false).trigger('click') - end - - step 'I see prefilled new Merge Request page' do - expect(page).to have_selector('.merge-request-form') - expect(current_path).to eq project_new_merge_request_path(@project) - expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s - expect(find("input#merge_request_source_branch").value).to eq "fix" - expect(find("input#merge_request_target_branch").value).to eq "master" - end - - step 'I have group with projects' do - @group = create(:group) - @project = create(:empty_project, namespace: @group) - @event = create(:closed_issue_event, project: @project) - - @project.team << [current_user, :master] - end - - step 'I should see projects list' do - @user.authorized_projects.all.each do |project| - expect(page).to have_link project.name_with_namespace - end - end - - step 'I should see groups list' do - Group.all.each do |group| - expect(page).to have_link group.name - end - end - - step 'group has a projects that does not belongs to me' do - @forbidden_project1 = create(:empty_project, group: @group) - @forbidden_project2 = create(:empty_project, group: @group) - end - - step 'I should see 1 project at group list' do - expect(find('span.last_activity/span')).to have_content('1') - end - - step 'I filter the list by label "feature"' do - page.within ".labels-filter" do - find('.dropdown').click - click_link "feature" - end - end - - step 'I should see "Bugfix1" in issues list' do - page.within "ul.content-list" do - expect(page).to have_content "Bugfix1" - end - end - - step 'project "Shop" has issue "Bugfix1" with label "feature"' do - project = Project.find_by(name: "Shop") - issue = create(:issue, title: "Bugfix1", project: project, assignees: [current_user]) - issue.labels << project.labels.find_by(title: 'feature') - end -end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 729e2b8982c..da1cdd9f897 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -239,11 +239,6 @@ module SharedProject create(:label, project: project, title: 'enhancement') end - step 'project "Shop" has issue: "bug report"' do - project = Project.find_by(name: "Shop") - create(:issue, project: project, title: "bug report") - end - step 'project "Shop" has CI enabled' do project = Project.find_by(name: "Shop") project.enable_ci diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index 54a01e837de..533df7a325c 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Dashboard Groups page', js: true, feature: true do +feature 'Dashboard Groups page', :js do let!(:user) { create :user } let!(:group) { create(:group) } let!(:nested_group) { create(:group, :nested) } @@ -41,7 +41,7 @@ describe 'Dashboard Groups page', js: true, feature: true do fill_in 'filter_groups', with: group.name wait_for_requests - fill_in 'filter_groups', with: "" + fill_in 'filter_groups', with: '' wait_for_requests expect(page).to have_content(group.full_name) diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard/issues_filter_spec.rb similarity index 58% rename from spec/features/dashboard_issues_spec.rb rename to spec/features/dashboard/issues_filter_spec.rb index f235fef1aa4..9b84f67b555 100644 --- a/spec/features/dashboard_issues_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -1,21 +1,23 @@ require 'spec_helper' -describe "Dashboard Issues filtering", feature: true, js: true do +feature 'Dashboard Issues filtering', js: true do + include SortingHelper + let(:user) { create(:user) } let(:project) { create(:empty_project) } let(:milestone) { create(:milestone, project: project) } + let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + let!(:issue2) { create(:issue, project: project, author: user, assignees: [user], milestone: milestone) } + + before do + project.add_master(user) + sign_in(user) + + visit_issues + end + context 'filtering by milestone' do - before do - project.team << [user, :master] - sign_in(user) - - create(:issue, project: project, author: user, assignees: [user]) - create(:issue, project: project, author: user, assignees: [user], milestone: milestone) - - visit_issues - end - it 'shows all issues with no milestone' do show_milestone_dropdown @@ -62,6 +64,46 @@ describe "Dashboard Issues filtering", feature: true, js: true do end end + context 'filtering by label' do + let(:label) { create(:label, project: project) } + let!(:label_link) { create(:label_link, label: label, target: issue) } + + it 'shows all issues without filter' do + page.within 'ul.content-list' do + expect(page).to have_content issue.title + expect(page).to have_content issue2.title + end + end + + it 'shows all issues with the selected label' do + page.within '.labels-filter' do + find('.dropdown').click + click_link label.title + end + + page.within 'ul.content-list' do + expect(page).to have_content issue.title + expect(page).not_to have_content issue2.title + end + end + end + + context 'sorting' do + it 'shows sorted issues' do + sorting_by('Oldest updated') + visit_issues + + expect(find('.issues-filters')).to have_content('Oldest updated') + end + + it 'keeps sorting issues after visiting Projects Issues page' do + sorting_by('Oldest updated') + visit project_issues_path(project) + + expect(find('.issues-filters')).to have_content('Oldest updated') + end + end + def show_milestone_dropdown click_button 'Milestone' expect(page).to have_selector('.dropdown-content', visible: true) diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index bb1fb5b3feb..42d6fadc0c1 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' feature 'Dashboard Merge Requests' do include FilterItemSelectHelper + include SortingHelper let(:current_user) { create :user } let(:project) { create(:empty_project) } @@ -109,5 +110,21 @@ feature 'Dashboard Merge Requests' do expect(page).to have_content(assigned_merge_request_from_fork.title) expect(page).to have_content(other_merge_request.title) end + + it 'shows sorted merge requests' do + sorting_by('Oldest updated') + + visit merge_requests_dashboard_path(assignee_id: current_user.id) + + expect(find('.issues-filters')).to have_content('Oldest updated') + end + + it 'keeps sorting merge requests after visiting Projects MR page' do + sorting_by('Oldest updated') + + visit project_merge_requests_path(project) + + expect(find('.issues-filters')).to have_content('Oldest updated') + end end end diff --git a/spec/features/dashboard_milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb similarity index 100% rename from spec/features/dashboard_milestones_spec.rb rename to spec/features/dashboard/milestones_spec.rb diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index bdba22fe9a9..abb9e5eef96 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -61,7 +61,7 @@ feature 'Dashboard Projects' do end end - describe "with a pipeline", clean_gitlab_redis_shared_state: true do + describe 'with a pipeline', clean_gitlab_redis_shared_state: true do let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) } before do @@ -74,7 +74,50 @@ feature 'Dashboard Projects' do it 'shows that the last pipeline passed' do visit dashboard_projects_path - expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']") + page.within('.controls') do + expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']") + expect(page).to have_css('.ci-status-link') + expect(page).to have_css('.ci-status-icon-success') + expect(page).to have_link('Commit: passed') + end + end + end + + context 'last push widget' do + let(:push_event_data) do + { + before: Gitlab::Git::BLANK_SHA, + after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e', + ref: 'refs/heads/feature', + user_id: user.id, + user_name: user.name, + repository: { + name: project.name, + url: 'localhost/rubinius', + description: '', + homepage: 'localhost/rubinius', + private: true + } + } + end + let!(:push_event) { create(:event, :pushed, data: push_event_data, project: project, author: user) } + + before do + visit dashboard_projects_path + end + + scenario 'shows "Create merge request" button' do + expect(page).to have_content 'You pushed to feature' + + within('#content-body') do + find_link('Create merge request', visible: false).click + end + + expect(page).to have_selector('.merge-request-form') + expect(current_path).to eq project_new_merge_request_path(project) + expect(find('#merge_request_target_project_id').value).to eq project.id.to_s + expect(find('input#merge_request_source_branch').value).to eq 'feature' + expect(find('input#merge_request_target_branch').value).to eq 'master' end end end diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 12b4747602d..8cbd26551bc 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Merge Request button', feature: true do +feature 'Merge Request button' do shared_examples 'Merge request button only shown when allowed' do let(:user) { create(:user) } let(:project) { create(:project, :public) } @@ -10,16 +10,14 @@ feature 'Merge Request button', feature: true do it 'does not show Create merge request button' do visit url - within("#content-body") do - expect(page).not_to have_link(label) - end + expect(page).not_to have_link(label) end end context 'logged in as developer' do before do sign_in(user) - project.team << [user, :developer] + project.add_developer(user) end it 'shows Create merge request button' do @@ -29,7 +27,7 @@ feature 'Merge Request button', feature: true do visit url - within("#content-body") do + within('#content-body') do expect(page).to have_link(label, href: href) end end @@ -42,7 +40,7 @@ feature 'Merge Request button', feature: true do it 'does not show Create merge request button' do visit url - within("#content-body") do + within('#content-body') do expect(page).not_to have_link(label) end end @@ -57,7 +55,7 @@ feature 'Merge Request button', feature: true do it 'does not show Create merge request button' do visit url - within("#content-body") do + within('#content-body') do expect(page).not_to have_link(label) end end diff --git a/spec/support/sorting_helper.rb b/spec/support/sorting_helper.rb new file mode 100644 index 00000000000..577518d726c --- /dev/null +++ b/spec/support/sorting_helper.rb @@ -0,0 +1,18 @@ +# Helper allows you to sort items +# +# Params +# value - value for sorting +# +# Usage: +# include SortingHelper +# +# sorting_by('Oldest updated') +# +module SortingHelper + def sorting_by(value) + find('button.dropdown-toggle').click + page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + click_link value + end + end +end From 591cb609339466459d66c4ae178a8c9b3130042b Mon Sep 17 00:00:00 2001 From: Alexander Randa Date: Wed, 5 Jul 2017 14:46:51 +0300 Subject: [PATCH 034/130] Replaces 'dashboard/activity.feature' spinach with rspec --- ...eplace-dashboard-event-filters-spinach.yml | 4 + features/dashboard/event_filters.feature | 58 ------- features/steps/dashboard/event_filters.rb | 92 ---------- spec/features/dashboard/activity_spec.rb | 157 +++++++++++++++++- 4 files changed, 157 insertions(+), 154 deletions(-) create mode 100644 changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml delete mode 100644 features/dashboard/event_filters.feature delete mode 100644 features/steps/dashboard/event_filters.rb diff --git a/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml new file mode 100644 index 00000000000..807cd097178 --- /dev/null +++ b/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml @@ -0,0 +1,4 @@ +--- +title: Replaces dashboard/event_filters.feature spinach with rspec +merge_request: 12651 +author: Alexander Randa (@randaalex) diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature deleted file mode 100644 index 8c3ff64164f..00000000000 --- a/features/dashboard/event_filters.feature +++ /dev/null @@ -1,58 +0,0 @@ -@dashboard -Feature: Event Filters - Background: - Given I sign in as a user - And I own a project - And this project has push event - And this project has new member event - And this project has merge request event - And I visit dashboard activity page - - @javascript - Scenario: I should see all events - Then I should see push event - And I should see new member event - And I should see merge request event - - @javascript - Scenario: I should see only pushed events - When I click "push" event filter - Then I should see push event - And I should not see new member event - And I should not see merge request event - - @javascript - Scenario: I should see only joined events - When I click "team" event filter - Then I should see new member event - And I should not see push event - And I should not see merge request event - - @javascript - Scenario: I should see only merged events - When I click "merge" event filter - Then I should see merge request event - And I should not see push event - And I should not see new member event - - @javascript - Scenario: I should see only selected events while page reloaded - When I click "push" event filter - And I visit dashboard activity page - Then I should see push event - And I should not see new member event - When I click "team" event filter - And I visit dashboard activity page - Then I should not see push event - And I should see new member event - And I should not see merge request event - When I click "push" event filter - And I visit dashboard activity page - Then I should see push event - And I should not see new member event - And I should not see merge request event - When I click "merge" event filter - And I visit dashboard activity page - Then I should see merge request event - And I should not see push event - And I should not see new member event diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb deleted file mode 100644 index a745254cc31..00000000000 --- a/features/steps/dashboard/event_filters.rb +++ /dev/null @@ -1,92 +0,0 @@ -class Spinach::Features::EventFilters < Spinach::FeatureSteps - include WaitForRequests - include SharedAuthentication - include SharedPaths - include SharedProject - - step 'I should see push event' do - expect(page).to have_selector('span.pushed') - end - - step 'I should not see push event' do - expect(page).not_to have_selector('span.pushed') - end - - step 'I should see new member event' do - expect(page).to have_selector('span.joined') - end - - step 'I should not see new member event' do - expect(page).not_to have_selector('span.joined') - end - - step 'I should see merge request event' do - expect(page).to have_selector('span.accepted') - end - - step 'I should not see merge request event' do - expect(page).not_to have_selector('span.accepted') - end - - step 'this project has push event' do - data = { - before: Gitlab::Git::BLANK_SHA, - after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", - ref: "refs/heads/new_design", - user_id: @user.id, - user_name: @user.name, - repository: { - name: @project.name, - url: "localhost/rubinius", - description: "", - homepage: "localhost/rubinius", - private: true - } - } - - @event = Event.create( - project: @project, - action: Event::PUSHED, - data: data, - author_id: @user.id - ) - end - - step 'this project has new member event' do - user = create(:user, { name: "John Doe" }) - Event.create( - project: @project, - author_id: user.id, - action: Event::JOINED - ) - end - - step 'this project has merge request event' do - merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project - Event.create( - project: @project, - action: Event::MERGED, - target_id: merge_request.id, - target_type: "MergeRequest", - author_id: @user.id - ) - end - - When 'I click "push" event filter' do - wait_for_requests - click_link("Push events") - wait_for_requests - end - - When 'I click "team" event filter' do - wait_for_requests - click_link("Team") - wait_for_requests - end - - When 'I click "merge" event filter' do - wait_for_requests - click_link("Merge events") - wait_for_requests - end -end diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index ebfe7340eb7..a96270c9147 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -1,13 +1,162 @@ require 'spec_helper' -RSpec.describe 'Dashboard Activity', feature: true do +feature 'Dashboard > Activity' do let(:user) { create(:user) } before do sign_in(user) - visit activity_dashboard_path end - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + context 'rss' do + before do + visit activity_dashboard_path + end + + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + end + + context 'event filters', :js do + let(:project) { create(:empty_project) } + + let(:merge_request) do + create(:merge_request, author: user, source_project: project, target_project: project) + end + + let(:push_event_data) do + { + before: Gitlab::Git::BLANK_SHA, + after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e', + ref: 'refs/heads/new_design', + user_id: user.id, + user_name: user.name, + repository: { + name: project.name, + url: 'localhost/rubinius', + description: '', + homepage: 'localhost/rubinius', + private: true + } + } + end + + let(:note) { create(:note, project: project, noteable: merge_request) } + + let!(:push_event) do + create(:event, :pushed, data: push_event_data, project: project, author: user) + end + + let!(:merged_event) do + create(:event, :merged, project: project, target: merge_request, author: user) + end + + let!(:joined_event) do + create(:event, :joined, project: project, author: user) + end + + let!(:closed_event) do + create(:event, :closed, project: project, target: merge_request, author: user) + end + + let!(:comments_event) do + create(:event, :commented, project: project, target: note, author: user) + end + + before do + project.add_master(user) + + visit activity_dashboard_path + wait_for_requests + end + + scenario 'user should see all events' do + within '.content_list' do + expect(page).to have_content('pushed new branch') + expect(page).to have_content('joined') + expect(page).to have_content('accepted') + expect(page).to have_content('closed') + expect(page).to have_content('commented on') + end + end + + scenario 'user should see only pushed events' do + click_link('Push events') + wait_for_requests + + within '.content_list' do + expect(page).to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + + scenario 'user should see only merged events' do + click_link('Merge events') + wait_for_requests + + within '.content_list' do + expect(page).not_to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + + scenario 'user should see only issues events' do + click_link('Issue events') + wait_for_requests + + within '.content_list' do + expect(page).not_to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + + scenario 'user should see only comments events' do + click_link('Comments') + wait_for_requests + + within '.content_list' do + expect(page).not_to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).to have_content('commented on') + end + end + + scenario 'user should see only joined events' do + click_link('Team') + wait_for_requests + + within '.content_list' do + expect(page).not_to have_content('pushed new branch') + expect(page).to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + + scenario 'user see selected event after page reloading' do + click_link('Push events') + wait_for_requests + visit activity_dashboard_path + wait_for_requests + + within '.content_list' do + expect(page).to have_content('pushed new branch') + expect(page).not_to have_content('joined') + expect(page).not_to have_content('accepted') + expect(page).not_to have_content('closed') + expect(page).not_to have_content('commented on') + end + end + end end From c1918fb10b333593837c15bf4a6fa161ca502b4b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Jul 2017 12:05:07 +0200 Subject: [PATCH 035/130] Add a new `retry` CI/CD configuration keyword --- lib/ci/gitlab_ci_yaml_processor.rb | 3 +- lib/gitlab/ci/config/entry/job.rb | 10 +++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 22 +++++++++++ spec/lib/gitlab/ci/config/entry/job_spec.rb | 39 ++++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index cf3a0336792..3a4911b23b0 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -83,7 +83,8 @@ module Ci before_script: job[:before_script], script: job[:script], after_script: job[:after_script], - environment: job[:environment] + environment: job[:environment], + retry: job[:retry] }.compact } end diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 176301bcca1..5f1144894b5 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -11,7 +11,7 @@ module Gitlab ALLOWED_KEYS = %i[tags script only except type image services allow_failure type stage when artifacts cache dependencies before_script - after_script variables environment coverage].freeze + after_script variables environment coverage retry].freeze validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -23,6 +23,9 @@ module Gitlab with_options allow_nil: true do validates :tags, array_of_strings: true validates :allow_failure, boolean: true + validates :retry, numericality: { only_integer: true, + greater_than_or_equal_to: 0, + less_than: 10 } validates :when, inclusion: { in: %w[on_success on_failure always manual], message: 'should be on_success, on_failure, ' \ @@ -76,9 +79,9 @@ module Gitlab helpers :before_script, :script, :stage, :type, :after_script, :cache, :image, :services, :only, :except, :variables, - :artifacts, :commands, :environment, :coverage + :artifacts, :commands, :environment, :coverage, :retry - attributes :script, :tags, :allow_failure, :when, :dependencies + attributes :script, :tags, :allow_failure, :when, :dependencies, :retry def compose!(deps = nil) super do @@ -142,6 +145,7 @@ module Gitlab environment: environment_defined? ? environment_value : nil, environment_name: environment_defined? ? environment_value[:name] : nil, coverage: coverage_defined? ? coverage_value : nil, + retry: retry_defined? ? retry_value.to_i : nil, artifacts: artifacts_value, after_script: after_script_value, ignore: ignored? } diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ea79389e67e..e50f799a6e9 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -32,6 +32,28 @@ module Ci end end + describe 'retry entry' do + context 'when retry count is specified' do + let(:config) do + YAML.dump(rspec: { script: 'rspec', retry: 3 }) + end + + it 'includes retry count in build options attribute' do + expect(subject[:options]).to include(retry: 3) + end + end + + context 'when retry count is not specified' do + let(:config) do + YAML.dump(rspec: { script: 'rspec' }) + end + + it 'does not persist retry count in the database' do + expect(subject[:options]).not_to have_key(:retry) + end + end + end + describe 'allow failure entry' do context 'when job is a manual action' do context 'when allow_failure is defined' do diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index c5cad887b64..f8ed59a3a44 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -80,6 +80,45 @@ describe Gitlab::Ci::Config::Entry::Job do expect(entry.errors).to include "job script can't be blank" end end + + context 'when retry value is not correct' do + context 'when it is not a numeric value' do + let(:config) { { retry: true } } + + it 'returns error about invalid type' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'job retry is not a number' + end + end + + context 'when it is lower than zero' do + let(:config) { { retry: -1 } } + + it 'returns error about value too low' do + expect(entry).not_to be_valid + expect(entry.errors) + .to include 'job retry must be greater than or equal to 0' + end + end + + context 'when it is not an integer' do + let(:config) { { retry: 1.5 } } + + it 'returns error about wrong value' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'job retry must be an integer' + end + end + + context 'when the value is too high' do + let(:config) { { retry: 10 } } + + it 'returns error about value too high' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'job retry must be less than 10' + end + end + end end end From 9bb7f19d15ac5412a1d4c816f4b3eebcb3c5a840 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Jul 2017 12:38:21 +0200 Subject: [PATCH 036/130] Make it possible to count a number of job retries --- app/models/ci/build.rb | 8 +++++++ spec/factories/ci/builds.rb | 4 ++++ spec/models/ci/build_spec.rb | 41 ++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 432f3f242eb..24ba4a881f5 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -130,6 +130,14 @@ module Ci success? || failed? || canceled? end + def retries_count + pipeline.builds.retried.where(name: self.name).count + end + + def retries_max + self.options.fetch(:retry, 0).to_i + end + def latest? !retried? end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index a77f01ecb00..678cebe365b 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -84,6 +84,10 @@ FactoryGirl.define do success end + trait :retried do + retried true + end + trait :cancelable do pending end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 154b6759f46..615d1e09a11 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -802,6 +802,47 @@ describe Ci::Build, :models do end end + describe 'build auto retry feature' do + describe '#retries_count' do + subject { create(:ci_build, name: 'test', pipeline: pipeline) } + + context 'when build has been retried several times' do + before do + create(:ci_build, :retried, name: 'test', pipeline: pipeline) + create(:ci_build, :retried, name: 'test', pipeline: pipeline) + end + + it 'reports a correct retry count value' do + expect(subject.retries_count).to eq 2 + end + end + + context 'when build has not been retried' do + it 'returns zero' do + expect(subject.retries_count).to eq 0 + end + end + end + + describe '#retries_max' do + context 'when max retries value is defined' do + subject { create(:ci_build, options: { retry: 3 }) } + + it 'returns a number of configured max retries' do + expect(subject.retries_max).to eq 3 + end + end + + context 'when max retries value is not defined' do + subject { create(:ci_build) } + + it 'returns zero' do + expect(subject.retries_max).to eq 0 + end + end + end + end + describe '#keep_artifacts!' do let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) } From 7fde7012c9126172097fae57969f1694f7cf5f05 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Jul 2017 13:00:32 +0200 Subject: [PATCH 037/130] Make it possible to auto retry a failed CI/CD job --- app/models/ci/build.rb | 10 ++++++++++ spec/models/ci/build_spec.rb | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 24ba4a881f5..a1b1fbefa42 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -96,6 +96,16 @@ module Ci BuildSuccessWorker.perform_async(id) end end + + after_transition any => [:failed] do |build| + build.run_after_commit do + next if build.retries_max.zero? + + if build.retries_count < build.retries_max + Ci::Build.retry(build, build.user) + end + end + end end def detailed_status(current_user) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 615d1e09a11..acfc888d944 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1624,7 +1624,7 @@ describe Ci::Build, :models do end end - describe 'State transition: any => [:pending]' do + describe 'state transition: any => [:pending]' do let(:build) { create(:ci_build, :created) } it 'queues BuildQueueWorker' do @@ -1633,4 +1633,35 @@ describe Ci::Build, :models do build.enqueue end end + + describe 'state transition when build fails' do + context 'when build is configured to be retried' do + subject { create(:ci_build, :running, options: { retry: 3 }) } + + it 'retries builds and assigns a same user to it' do + expect(described_class).to receive(:retry) + .with(subject, subject.user) + + subject.drop! + end + end + + context 'when build is not configured to be retried' do + subject { create(:ci_build, :running) } + + it 'does not retry build' do + expect(described_class).not_to receive(:retry) + + subject.drop! + end + + it 'does not count retries when not necessary' do + expect(described_class).not_to receive(:retry) + expect_any_instance_of(described_class) + .not_to receive(:retries_count) + + subject.drop! + end + end + end end From a4d301eed047afb800b810432680b8c9134fa40a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Jul 2017 13:10:07 +0200 Subject: [PATCH 038/130] Add specs seeding jobs with auto-retries configured --- spec/services/ci/create_pipeline_service_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 77c07b71c68..69f52b06980 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -320,5 +320,19 @@ describe Ci::CreatePipelineService, :services do end.not_to change { Environment.count } end end + + context 'when builds with auto-retries are configured' do + before do + config = YAML.dump(rspec: { script: 'rspec', retry: 3 }) + stub_ci_pipeline_yaml_file(config) + end + + it 'correctly creates builds with auto-retry value configured' do + pipeline = execute_service + + expect(pipeline).to be_persisted + expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 3 + end + end end end From 63137d4e5bd8e42d5b95ffac44be71f998148494 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Jul 2017 13:30:49 +0200 Subject: [PATCH 039/130] Add specs for pipeline process with auto-retries This also resolve a possible race condition - a next stage should not start until are retries are done in a previous one. --- app/models/ci/build.rb | 10 +++---- .../ci/process_pipeline_service_spec.rb | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index a1b1fbefa42..416a2a33378 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -97,13 +97,11 @@ module Ci end end - after_transition any => [:failed] do |build| - build.run_after_commit do - next if build.retries_max.zero? + before_transition any => [:failed] do |build| + next if build.retries_max.zero? - if build.retries_count < build.retries_max - Ci::Build.retry(build, build.user) - end + if build.retries_count < build.retries_max + Ci::Build.retry(build, build.user) end end end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index efcaccc254e..0934833a4fa 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -463,6 +463,35 @@ describe Ci::ProcessPipelineService, '#execute', :services do end end + context 'when builds with auto-retries are configured' do + before do + create_build('build:1', stage_idx: 0, user: user, options: { retry: 2 }) + create_build('test:1', stage_idx: 1, user: user, when: :on_failure) + create_build('test:2', stage_idx: 1, user: user, options: { retry: 1 }) + end + + it 'automatically retries builds in a valid order' do + expect(process_pipeline).to be_truthy + + fail_running_or_pending + + expect(builds_names).to eq %w[build:1 build:1] + expect(builds_statuses).to eq %w[failed pending] + + succeed_running_or_pending + + expect(builds_names).to eq %w[build:1 build:1 test:2] + expect(builds_statuses).to eq %w[failed success pending] + + succeed_running_or_pending + + expect(builds_names).to eq %w[build:1 build:1 test:2] + expect(builds_statuses).to eq %w[failed success success] + + expect(pipeline.reload).to be_success + end + end + def process_pipeline described_class.new(pipeline.project, user).execute(pipeline) end From 7f2b7bcdcca3c691a8991dec5adc50c172429b86 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Fri, 14 Jul 2017 16:04:01 -0700 Subject: [PATCH 040/130] 35087 Fix alignment of controls in mr issuable list --- app/assets/stylesheets/framework/lists.scss | 8 ++++++++ changelogs/unreleased/35087-mr-status-misaligned.yml | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 changelogs/unreleased/35087-mr-status-misaligned.yml diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index e59cd0eea82..868e65a8f46 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -236,6 +236,8 @@ ul.content-list { ul.controls { float: right; list-style: none; + display: flex; + align-items: center; .btn { padding: 10px 14px; @@ -259,6 +261,12 @@ ul.controls { } } } + + .issuable-pipeline-broken a, + .issuable-pipeline-status a, + .author_link { + display: flex; + } } ul.indent-list { diff --git a/changelogs/unreleased/35087-mr-status-misaligned.yml b/changelogs/unreleased/35087-mr-status-misaligned.yml new file mode 100644 index 00000000000..3be43125a61 --- /dev/null +++ b/changelogs/unreleased/35087-mr-status-misaligned.yml @@ -0,0 +1,4 @@ +--- +title: Fix alignment of controls in mr issuable list +merge_request: +author: From ebe989ca09fbc205bcb1f23d7e4128e5f89f7586 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 17 Jul 2017 15:59:42 -0500 Subject: [PATCH 041/130] Remove double new issue button on group issue dashboard empty state --- app/views/shared/empty_states/_issues.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index 046b127f73c..b0c0ab523c7 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -16,7 +16,8 @@ Also, issues are searchable and filterable. - if project_select_button = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue' - = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' + - else + = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' - else .text-center %h4 There are no issues to show. From 6bd94f20e30dfcba5273cd6e7415fb90914ab5a7 Mon Sep 17 00:00:00 2001 From: Alexandros Keramidas Date: Mon, 17 Jul 2017 15:16:49 +0300 Subject: [PATCH 042/130] Added authentiq provider to tests and updated documentation, gem and config file. --- Gemfile | 2 +- Gemfile.lock | 4 ++-- config/gitlab.yml.example | 6 +++--- doc/administration/auth/authentiq.md | 9 ++++----- spec/controllers/profiles/accounts_controller_spec.rb | 2 +- spec/helpers/auth_helper_spec.rb | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Gemfile b/Gemfile index 2c248a45175..68881c42cb6 100644 --- a/Gemfile +++ b/Gemfile @@ -38,7 +38,7 @@ gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' -gem 'omniauth-authentiq', '~> 0.3.0' +gem 'omniauth-authentiq', '~> 0.3.1' gem 'rack-oauth2', '~> 1.2.1' gem 'jwt', '~> 1.5.6' diff --git a/Gemfile.lock b/Gemfile.lock index f356024506c..9e5ef0fb5ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -495,7 +495,7 @@ GEM rack (>= 1.0, < 3) omniauth-auth0 (1.4.1) omniauth-oauth2 (~> 1.1) - omniauth-authentiq (0.3.0) + omniauth-authentiq (0.3.1) omniauth-oauth2 (~> 1.3, >= 1.3.1) omniauth-azure-oauth2 (0.0.6) jwt (~> 1.0) @@ -1024,7 +1024,7 @@ DEPENDENCIES oj (~> 2.17.4) omniauth (~> 1.4.2) omniauth-auth0 (~> 1.4.1) - omniauth-authentiq (~> 0.3.0) + omniauth-authentiq (~> 0.3.1) omniauth-azure-oauth2 (~> 0.0.6) omniauth-cas3 (~> 1.1.2) omniauth-facebook (~> 4.0.0) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 221e3d6e03b..5387c138c0c 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -383,13 +383,13 @@ production: &base # service_validate_url: '/cas/p3/serviceValidate', # logout_url: '/cas/logout'} } # - { name: 'authentiq', - # # for client credentials (client ID and secret), go to https://www.authentiq.com/ + # # for client credentials (client ID and secret), go to https://www.authentiq.com/developers # app_id: 'YOUR_CLIENT_ID', # app_secret: 'YOUR_CLIENT_SECRET', # args: { # scope: 'aq:name email~rs address aq:push' - # # redirect_uri parameter is optional except when 'gitlab.host' in this file is set to 'localhost' - # # redirect_uri: 'YOUR_REDIRECT_URI' + # # callback_url parameter is optional except when 'gitlab.host' in this file is set to 'localhost' + # # callback_url: 'YOUR_CALLBACK_URL' # } # } # - { name: 'github', diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md index fb1a16b0f96..1528f1d2b17 100644 --- a/doc/administration/auth/authentiq.md +++ b/doc/administration/auth/authentiq.md @@ -32,7 +32,7 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t "app_id" => "YOUR_CLIENT_ID", "app_secret" => "YOUR_CLIENT_SECRET", "args" => { - scope: 'aq:name email~rs aq:push' + "scope": 'aq:name email~rs address aq:push' } } ] @@ -45,21 +45,20 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t app_id: 'YOUR_CLIENT_ID', app_secret: 'YOUR_CLIENT_SECRET', args: { - scope: 'aq:name email~rs aq:push' + scope: 'aq:name email~rs address aq:push' } } ``` 5. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits. -See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq#scopes-and-redirect-uri-configuration) for more information on scopes and modifiers. +See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers. 6. Change `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` to the Client credentials you received in step 1. 7. Save the configuration file. -8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source) - for the changes to take effect if you installed GitLab via Omnibus or from source respectively. +8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect if you installed GitLab via Omnibus or from source respectively. On the sign in page there should now be an Authentiq icon below the regular sign in form. Click the icon to begin the authentication process. diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb index 2f9d18e3a0e..d387aba227b 100644 --- a/spec/controllers/profiles/accounts_controller_spec.rb +++ b/spec/controllers/profiles/accounts_controller_spec.rb @@ -29,7 +29,7 @@ describe Profiles::AccountsController do end end - [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider| + [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider| describe "#{provider} provider" do let(:user) { create(:omniauth_user, provider: provider.to_s) } diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index a0e1265efff..c94fedd615b 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -70,7 +70,7 @@ describe AuthHelper do end end - [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider| + [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider| it "returns false if the provider is #{provider}" do expect(helper.unlink_allowed?(provider)).to be true end From f4eb2ff725ae4f05679b2d1d41dbc3e033e7d3ac Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 11 Jul 2017 14:40:18 +0200 Subject: [PATCH 043/130] Implement build stage_id reference migration clean up --- ...55_build_stage_id_ref_migration_cleanup.rb | 21 +++++++++++++++++++ db/schema.rb | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20170710083355_build_stage_id_ref_migration_cleanup.rb diff --git a/db/migrate/20170710083355_build_stage_id_ref_migration_cleanup.rb b/db/migrate/20170710083355_build_stage_id_ref_migration_cleanup.rb new file mode 100644 index 00000000000..be8efb140d9 --- /dev/null +++ b/db/migrate/20170710083355_build_stage_id_ref_migration_cleanup.rb @@ -0,0 +1,21 @@ +require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background') + +class BuildStageIdRefMigrationCleanup < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + ## + # `MigrateStageIdReferenceInBackground` background migration cleanup. + # + def up + Gitlab::BackgroundMigration + .steal(MigrateStageIdReferenceInBackground::MIGRATION) + end + + def down + # noop + end +end diff --git a/db/schema.rb b/db/schema.rb index 5264fc99557..44bc0109e66 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: 20170707184244) do +ActiveRecord::Schema.define(version: 20170711145558) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From bd6406c406e9894d1d3073978d42fcefa8631497 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 11 Jul 2017 15:19:44 +0200 Subject: [PATCH 044/130] Rename stage_id reference clean up migration --- ....rb => 20170710083355_clean_stage_id_reference_migration.rb} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename db/migrate/{20170710083355_build_stage_id_ref_migration_cleanup.rb => 20170710083355_clean_stage_id_reference_migration.rb} (86%) diff --git a/db/migrate/20170710083355_build_stage_id_ref_migration_cleanup.rb b/db/migrate/20170710083355_clean_stage_id_reference_migration.rb similarity index 86% rename from db/migrate/20170710083355_build_stage_id_ref_migration_cleanup.rb rename to db/migrate/20170710083355_clean_stage_id_reference_migration.rb index be8efb140d9..f75cbb4a712 100644 --- a/db/migrate/20170710083355_build_stage_id_ref_migration_cleanup.rb +++ b/db/migrate/20170710083355_clean_stage_id_reference_migration.rb @@ -1,6 +1,6 @@ require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background') -class BuildStageIdRefMigrationCleanup < ActiveRecord::Migration +class CleanStageIdReferenceMigration < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers DOWNTIME = false From fa3acb3bb662bef9d16a072f78d0048365a0f1dc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 11 Jul 2017 15:19:57 +0200 Subject: [PATCH 045/130] Add pending set of specs for stage_id cleanup migration --- ...clean_stage_id_reference_migration_spec.rb | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 spec/migrations/clean_stage_id_reference_migration_spec.rb diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb new file mode 100644 index 00000000000..17be549ddd3 --- /dev/null +++ b/spec/migrations/clean_stage_id_reference_migration_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb') + +describe CleanStageIdReferenceMigration, :migration, :sidekiq do + context 'when there are enqueued background migrations' do + pending 'processes enqueued jobs synchronously' do + fail + end + end + + context 'when there are scheduled background migrations' do + pending 'immediately processes scheduled jobs' do + fail + end + end + + context 'when there are no background migrations pending' do + pending 'does nothing' do + fail + end + end +end From 5c3fd67075782b0cf1ef81254e81ae21b38a2012 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Jul 2017 10:31:22 +0200 Subject: [PATCH 046/130] Add specs for stage_id reference cleanup migration --- ...clean_stage_id_reference_migration_spec.rb | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb index 17be549ddd3..0518c4de799 100644 --- a/spec/migrations/clean_stage_id_reference_migration_spec.rb +++ b/spec/migrations/clean_stage_id_reference_migration_spec.rb @@ -1,22 +1,29 @@ require 'spec_helper' require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb') -describe CleanStageIdReferenceMigration, :migration, :sidekiq do - context 'when there are enqueued background migrations' do - pending 'processes enqueued jobs synchronously' do - fail - end - end +describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do + let(:migration) { MigrateStageIdReferenceInBackground::MIGRATION } - context 'when there are scheduled background migrations' do - pending 'immediately processes scheduled jobs' do - fail + context 'when there are pending background migrations' do + it 'processes enqueued jobs synchronously' do + Sidekiq::Testing.disable! do + BackgroundMigrationWorker.perform_in(2.minutes, migration, [1]) + BackgroundMigrationWorker.perform_async(migration, [1]) + + expect(Gitlab::BackgroundMigration).to receive(:perform).twice + + migrate! + end end end context 'when there are no background migrations pending' do - pending 'does nothing' do - fail + it 'does nothing' do + Sidekiq::Testing.disable! do + expect(Gitlab::BackgroundMigration).not_to receive(:perform) + + migrate! + end end end end From 2930c0e3d090d5b33133160d847153a215ab059a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Jul 2017 10:43:12 +0200 Subject: [PATCH 047/130] Remove obsolete argument from bg migrations code --- lib/gitlab/background_migration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb index b0741b1fba7..d3f66877672 100644 --- a/lib/gitlab/background_migration.rb +++ b/lib/gitlab/background_migration.rb @@ -26,7 +26,7 @@ module Gitlab next unless migration_class == steal_class begin - perform(migration_class, migration_args, retries: 3) if job.delete + perform(migration_class, migration_args) if job.delete rescue Exception # rubocop:disable Lint/RescueException BackgroundMigrationWorker # enqueue this migration again .perform_async(migration_class, migration_args) From 5cd6f8c76ec74c85e6ff6dae9736b27baeb55988 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Jul 2017 13:25:11 +0200 Subject: [PATCH 048/130] Add a foreign key to `merge_requests.head_pipeline_id` --- ...04829_add_foreign_key_to_merge_requests.rb | 28 +++++++++++++++++++ db/schema.rb | 3 +- lib/gitlab/database/migration_helpers.rb | 4 ++- .../gitlab/database/migration_helpers_spec.rb | 12 +++++++- 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb diff --git a/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb new file mode 100644 index 00000000000..78ff7527967 --- /dev/null +++ b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb @@ -0,0 +1,28 @@ +class AddForeignKeyToMergeRequests < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + unless foreign_key_exists?(:merge_requests, :head_pipeline_id) + add_concurrent_foreign_key(:merge_requests, :ci_pipelines, + column: :head_pipeline_id, on_delete: :nullify) + end + end + + def down + if foreign_key_exists?(:merge_requests, :head_pipeline_id) + remove_foreign_key(:merge_requests, column: :head_pipeline_id) + end + end + + private + + def foreign_key_exists?(table, column) + foreign_keys(table).any? do |key| + key.options[:column] == column.to_s + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 5264fc99557..9c8c64fe2d0 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: 20170707184244) do +ActiveRecord::Schema.define(version: 20170713104829) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1615,6 +1615,7 @@ ActiveRecord::Schema.define(version: 20170707184244) do add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade + add_foreign_key "merge_requests", "ci_pipelines", column: "head_pipeline_id", name: "fk_fd82eae0b9", on_delete: :nullify add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 0643c56db9b..69ca9aa596b 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -140,6 +140,8 @@ module Gitlab return add_foreign_key(source, target, column: column, on_delete: on_delete) + else + on_delete = 'SET NULL' if on_delete == :nullify end disable_statement_timeout @@ -155,7 +157,7 @@ module Gitlab ADD CONSTRAINT #{key_name} FOREIGN KEY (#{column}) REFERENCES #{target} (id) - #{on_delete ? "ON DELETE #{on_delete}" : ''} + #{on_delete ? "ON DELETE #{on_delete.upcase}" : ''} NOT VALID; EOF diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 4259be3f522..a2acd15c8fb 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -174,13 +174,23 @@ describe Gitlab::Database::MigrationHelpers, lib: true do allow(Gitlab::Database).to receive(:mysql?).and_return(false) end - it 'creates a concurrent foreign key' do + it 'creates a concurrent foreign key and validates it' do expect(model).to receive(:disable_statement_timeout) expect(model).to receive(:execute).ordered.with(/NOT VALID/) expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) model.add_concurrent_foreign_key(:projects, :users, column: :user_id) end + + it 'appends a valid ON DELETE statement' do + expect(model).to receive(:disable_statement_timeout) + expect(model).to receive(:execute).with(/ON DELETE SET NULL/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) + + model.add_concurrent_foreign_key(:projects, :users, + column: :user_id, + on_delete: :nullify) + end end end end From 4aab52b20f9af44f03f2452875eb43bb40f2a6c6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Jul 2017 16:01:05 +0200 Subject: [PATCH 049/130] Nullify orphaned head_pipeline_ids in merge_requests --- .../20170713104829_add_foreign_key_to_merge_requests.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb index 78ff7527967..8a4e1062314 100644 --- a/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb +++ b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb @@ -6,6 +6,14 @@ class AddForeignKeyToMergeRequests < ActiveRecord::Migration disable_ddl_transaction! def up + execute <<-SQL.strip_heredoc + UPDATE merge_requests SET head_pipeline_id = null + WHERE NOT EXISTS ( + SELECT 1 FROM ci_pipelines + WHERE ci_pipelines.id = merge_requests.head_pipeline_id + ) + SQL + unless foreign_key_exists?(:merge_requests, :head_pipeline_id) add_concurrent_foreign_key(:merge_requests, :ci_pipelines, column: :head_pipeline_id, on_delete: :nullify) From 73c7b968850b77dd2d740b494b4a98adb1222d41 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Jul 2017 11:51:47 +0200 Subject: [PATCH 050/130] Remove migration dependency from stage_id migration --- .../20170710083355_clean_stage_id_reference_migration.rb | 5 +---- spec/migrations/clean_stage_id_reference_migration_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/db/migrate/20170710083355_clean_stage_id_reference_migration.rb b/db/migrate/20170710083355_clean_stage_id_reference_migration.rb index f75cbb4a712..681203eaf40 100644 --- a/db/migrate/20170710083355_clean_stage_id_reference_migration.rb +++ b/db/migrate/20170710083355_clean_stage_id_reference_migration.rb @@ -1,5 +1,3 @@ -require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background') - class CleanStageIdReferenceMigration < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers @@ -11,8 +9,7 @@ class CleanStageIdReferenceMigration < ActiveRecord::Migration # `MigrateStageIdReferenceInBackground` background migration cleanup. # def up - Gitlab::BackgroundMigration - .steal(MigrateStageIdReferenceInBackground::MIGRATION) + Gitlab::BackgroundMigration.steal('MigrateBuildStageIdReference') end def down diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb index 0518c4de799..1b8d044ed61 100644 --- a/spec/migrations/clean_stage_id_reference_migration_spec.rb +++ b/spec/migrations/clean_stage_id_reference_migration_spec.rb @@ -1,11 +1,12 @@ require 'spec_helper' require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb') +require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background') describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do let(:migration) { MigrateStageIdReferenceInBackground::MIGRATION } context 'when there are pending background migrations' do - it 'processes enqueued jobs synchronously' do + it 'processes pending jobs synchronously' do Sidekiq::Testing.disable! do BackgroundMigrationWorker.perform_in(2.minutes, migration, [1]) BackgroundMigrationWorker.perform_async(migration, [1]) From a65f64dfe645da893e92a061fd86437a55726873 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Jul 2017 11:55:43 +0200 Subject: [PATCH 051/130] Fix background migrations module specs --- spec/lib/gitlab/background_migration_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb index cfa59280139..4ad69aeba43 100644 --- a/spec/lib/gitlab/background_migration_spec.rb +++ b/spec/lib/gitlab/background_migration_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::BackgroundMigration do expect(queue[0]).to receive(:delete).and_return(true) expect(described_class).to receive(:perform) - .with('Foo', [10, 20], anything) + .with('Foo', [10, 20]) described_class.steal('Foo') end @@ -93,9 +93,9 @@ describe Gitlab::BackgroundMigration do it 'steals from the scheduled sets queue first' do Sidekiq::Testing.disable! do expect(described_class).to receive(:perform) - .with('Object', [1], anything).ordered + .with('Object', [1]).ordered expect(described_class).to receive(:perform) - .with('Object', [2], anything).ordered + .with('Object', [2]).ordered BackgroundMigrationWorker.perform_async('Object', [2]) BackgroundMigrationWorker.perform_in(10.minutes, 'Object', [1]) From a468c3a3179d7c7b84a0f7d1e6253238e40b9100 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Jul 2017 12:03:56 +0200 Subject: [PATCH 052/130] Fix database schema version number --- db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 44bc0109e66..623f22289ba 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: 20170711145558) do +ActiveRecord::Schema.define(version: 20170710083355) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From 2deeac56438a5056444563596433b7bb448160fe Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Jul 2017 11:36:37 +0200 Subject: [PATCH 053/130] Use batching to clear orphans in head_pipeline migration --- ...04829_add_foreign_key_to_merge_requests.rb | 15 +++++-- .../add_foreign_key_to_merge_requests_spec.rb | 39 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 spec/migrations/add_foreign_key_to_merge_requests_spec.rb diff --git a/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb index 8a4e1062314..c25d4fd5986 100644 --- a/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb +++ b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb @@ -5,15 +5,24 @@ class AddForeignKeyToMergeRequests < ActiveRecord::Migration disable_ddl_transaction! + class MergeRequest < ActiveRecord::Base + self.table_name = 'merge_requests' + include ::EachBatch + end + def up - execute <<-SQL.strip_heredoc - UPDATE merge_requests SET head_pipeline_id = null - WHERE NOT EXISTS ( + scope = <<-SQL.strip_heredoc + head_pipeline_id IS NOT NULL + AND NOT EXISTS ( SELECT 1 FROM ci_pipelines WHERE ci_pipelines.id = merge_requests.head_pipeline_id ) SQL + MergeRequest.where(scope).each_batch(of: 1000) do |merge_requests| + merge_requests.update_all(head_pipeline_id: nil) + end + unless foreign_key_exists?(:merge_requests, :head_pipeline_id) add_concurrent_foreign_key(:merge_requests, :ci_pipelines, column: :head_pipeline_id, on_delete: :nullify) diff --git a/spec/migrations/add_foreign_key_to_merge_requests_spec.rb b/spec/migrations/add_foreign_key_to_merge_requests_spec.rb new file mode 100644 index 00000000000..d9ad9a585f0 --- /dev/null +++ b/spec/migrations/add_foreign_key_to_merge_requests_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20170713104829_add_foreign_key_to_merge_requests.rb') + +describe AddForeignKeyToMergeRequests, :migration do + let(:projects) { table(:projects) } + let(:merge_requests) { table(:merge_requests) } + let(:pipelines) { table(:ci_pipelines) } + + before do + projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') + pipelines.create!(project_id: projects.first.id, + ref: 'some-branch', + sha: 'abc12345') + + # merge request without a pipeline + create_merge_request(head_pipeline_id: nil) + + # merge request with non-existent pipeline + create_merge_request(head_pipeline_id: 1234) + + # merge reqeust with existing pipeline assigned + create_merge_request(head_pipeline_id: pipelines.first.id) + end + + it 'correctly adds a foreign key to head_pipeline_id' do + migrate! + + expect(merge_requests.first.head_pipeline_id).to be_nil + expect(merge_requests.second.head_pipeline_id).to be_nil + expect(merge_requests.third.head_pipeline_id).to eq pipelines.first.id + end + + def create_merge_request(**opts) + merge_requests.create!(source_project_id: projects.first.id, + target_project_id: projects.first.id, + source_branch: 'some-branch', + target_branch: 'master', **opts) + end +end From 67157d81149856b90c5f0034e5e4bb05a35fbfcc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Jul 2017 12:54:24 +0200 Subject: [PATCH 054/130] Add changelog entry for auto-retry of CI/CD job feature --- changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml diff --git a/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml b/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml new file mode 100644 index 00000000000..abde2397331 --- /dev/null +++ b/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml @@ -0,0 +1,4 @@ +--- +title: Make it possible to configure auto-retry of CI/CD job +merge_request: 12909 +author: From 883e8f8691af573246aced279b48cae46d8f2a1c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Jul 2017 13:01:29 +0200 Subject: [PATCH 055/130] Add basic docs for CI/CD job auto-retry feature --- doc/ci/yaml/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 724843a4d56..6b17af1394a 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -395,6 +395,7 @@ job_name: | after_script | no | Override a set of commands that are executed after job | | environment | no | Defines a name of environment to which deployment is done by this job | | coverage | no | Define code coverage settings for a given job | +| retry | no | Define how many times a job can be auto-retried in case of a failure | ### script @@ -1129,9 +1130,33 @@ A simple example: ```yaml job1: + script: rspec coverage: '/Code coverage: \d+\.\d+/' ``` +### retry + +**Notes:** +- [Introduced][ce-3442] in GitLab 9.5. + +`retry` allows you to configure how many times a job is going to be retried in +case of a failure. + +When a job fails, and has `retry` configured it is going to be processed again +up to the amount of times specified by the `retry` keyword. + +If `retry` is set to 3, and a job succeeds in a second run, it won't be retried +again. `retry` value has to be a positive integer, equal or larger than 0, but +lower than 10. + +A simple example: + +```yaml +test: + script: rspec + retry: 3 +``` + ## Git Strategy > Introduced in GitLab 8.9 as an experimental feature. May change or be removed @@ -1506,3 +1531,4 @@ CI with various languages. [variables]: ../variables/README.md [ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983 [ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447 +[ce-3442]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3442 From 66c7f518974cf3e3c9d9914fe7dd5b89f216889b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 18 Jul 2017 13:56:36 +0200 Subject: [PATCH 056/130] Bump peek-performance_bar to 1.3.0 and get rid of a monkey-patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- Gemfile | 2 +- Gemfile.lock | 4 ++-- config/initializers/peek.rb | 4 ---- .../peek_performance_bar_with_rack_body.rb | 22 ------------------- 4 files changed, 3 insertions(+), 29 deletions(-) delete mode 100644 lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb diff --git a/Gemfile b/Gemfile index abf9f323fb4..263ddf7c748 100644 --- a/Gemfile +++ b/Gemfile @@ -268,7 +268,7 @@ gem 'peek', '~> 1.0.1' gem 'peek-gc', '~> 0.0.2' gem 'peek-host', '~> 1.0.0' gem 'peek-mysql2', '~> 1.1.0', group: :mysql -gem 'peek-performance_bar', '~> 1.2.1' +gem 'peek-performance_bar', '~> 1.3.0' gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-redis', '~> 1.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index a24636ad512..df14fbec2c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -553,7 +553,7 @@ GEM atomic (>= 1.0.0) mysql2 peek - peek-performance_bar (1.2.1) + peek-performance_bar (1.3.0) peek (>= 0.1.0) peek-pg (1.3.0) concurrent-ruby @@ -1029,7 +1029,7 @@ DEPENDENCIES peek-gc (~> 0.0.2) peek-host (~> 1.0.0) peek-mysql2 (~> 1.1.0) - peek-performance_bar (~> 1.2.1) + peek-performance_bar (~> 1.3.0) peek-pg (~> 1.3.0) peek-rblineprof (~> 0.2.0) peek-redis (~> 1.2.0) diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb index da8282ec924..a54d53cbbe2 100644 --- a/config/initializers/peek.rb +++ b/config/initializers/peek.rb @@ -26,7 +26,3 @@ class PEEK_DB_CLIENT end PEEK_DB_VIEW.prepend ::Gitlab::PerformanceBar::PeekQueryTracker - -class Peek::Views::PerformanceBar::ProcessUtilization - prepend ::Gitlab::PerformanceBar::PeekPerformanceBarWithRackBody -end diff --git a/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb b/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb deleted file mode 100644 index d939a6ea18d..00000000000 --- a/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb +++ /dev/null @@ -1,22 +0,0 @@ -# This solves a bug with a X-Senfile header that wouldn't be set properly, see -# https://github.com/peek/peek-performance_bar/pull/27 -module Gitlab - module PerformanceBar - module PeekPerformanceBarWithRackBody - def call(env) - @env = env - reset_stats - - @total_requests += 1 - first_request if @total_requests == 1 - - env['process.request_start'] = @start.to_f - env['process.total_requests'] = total_requests - - status, headers, body = @app.call(env) - body = Rack::BodyProxy.new(body) { record_request } - [status, headers, body] - end - end - end -end From e73cfbd46afed00d27cf76603e495921552b001f Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Tue, 13 Jun 2017 14:53:40 -0400 Subject: [PATCH 057/130] Fix date bug in JS that makes dates not work with strings --- app/assets/javascripts/due_date_select.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index a8fc5b41fb4..2212b5719bc 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -168,6 +168,7 @@ class DueDateSelectors { initMilestoneDatePicker() { $('.datepicker').each(function() { const $datePicker = $(this); + const [y, m, d] = $datePicker.val().split('-'); const calendar = new Pikaday({ field: $datePicker.get(0), theme: 'gitlab-theme animate-picker', @@ -177,7 +178,8 @@ class DueDateSelectors { $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); } }); - calendar.setDate(new Date($datePicker.val())); + + calendar.setDate(new Date(y, m-1, d)); $datePicker.data('pikaday', calendar); }); From a56ca172a2061c46c0498a8ba731e309457c0d57 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 10 Jul 2017 08:15:24 -0400 Subject: [PATCH 058/130] Fixes ` Infix operators must be spaced space-infix-ops`. --- app/assets/javascripts/due_date_select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 2212b5719bc..7a47a85c4fd 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -179,7 +179,7 @@ class DueDateSelectors { } }); - calendar.setDate(new Date(y, m-1, d)); + calendar.setDate(new Date(y, m - 1, d)); $datePicker.data('pikaday', calendar); }); From 91d28b1b70647a5668b540cd256310ad2e2daf7d Mon Sep 17 00:00:00 2001 From: Andrew Newdigate Date: Tue, 18 Jul 2017 12:59:14 +0000 Subject: [PATCH 059/130] Update Gitaly Server Version to 0.21.2 --- GITALY_SERVER_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index c5523bd09b1..59dad104b0b 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.17.0 +0.21.2 From 27a6aa4f515cb46ef3f72cad962b25ceee52986a Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 17 Jul 2017 09:16:33 +0200 Subject: [PATCH 060/130] Move system-uploads to `-/system` --- app/uploaders/gitlab_uploader.rb | 2 +- changelogs/unreleased/bvl-free-system-namespace.yml | 4 ++++ config/routes/uploads.rb | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/bvl-free-system-namespace.yml diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb index 0da7a025591..05a2091633a 100644 --- a/app/uploaders/gitlab_uploader.rb +++ b/app/uploaders/gitlab_uploader.rb @@ -16,7 +16,7 @@ class GitlabUploader < CarrierWave::Uploader::Base def self.base_dir return root_dir unless file_storage? - File.join(root_dir, 'system') + File.join(root_dir, '-', 'system') end def self.file_storage? diff --git a/changelogs/unreleased/bvl-free-system-namespace.yml b/changelogs/unreleased/bvl-free-system-namespace.yml new file mode 100644 index 00000000000..6c2d1e0e61f --- /dev/null +++ b/changelogs/unreleased/bvl-free-system-namespace.yml @@ -0,0 +1,4 @@ +--- +title: "Move uploads from `uploads/system` to `uploads/-/system` to free up `system` as a group name" +merge_request: 11713 +author: diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb index a49e244af1a..ed5476c8f71 100644 --- a/config/routes/uploads.rb +++ b/config/routes/uploads.rb @@ -1,6 +1,6 @@ scope path: :uploads do # Note attachments and User/Group/Project avatars - get "system/:model/:mounted_as/:id/:filename", + get "-/system/:model/:mounted_as/:id/:filename", to: "uploads#show", constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } @@ -15,7 +15,7 @@ scope path: :uploads do constraints: { filename: /[^\/]+/ } # Appearance - get "system/:model/:mounted_as/:id/:filename", + get "-/system/:model/:mounted_as/:id/:filename", to: "uploads#show", constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } From 79f591df4dfd6577c55d3bb843e423bba859e9b9 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 17 Jul 2017 13:02:28 +0200 Subject: [PATCH 061/130] Move the `uploads/system` folder to `uploads/-/system` Without downtime, so we need the symlinks --- ...0170717074009_move_system_upload_folder.rb | 60 ++++++++++++++++++ ...eanup_move_system_upload_folder_symlink.rb | 40 ++++++++++++ db/schema.rb | 2 +- ..._move_system_upload_folder_symlink_spec.rb | 35 +++++++++++ .../move_system_upload_folder_spec.rb | 62 +++++++++++++++++++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20170717074009_move_system_upload_folder.rb create mode 100644 db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb create mode 100644 spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb create mode 100644 spec/migrations/move_system_upload_folder_spec.rb diff --git a/db/migrate/20170717074009_move_system_upload_folder.rb b/db/migrate/20170717074009_move_system_upload_folder.rb new file mode 100644 index 00000000000..cce31794115 --- /dev/null +++ b/db/migrate/20170717074009_move_system_upload_folder.rb @@ -0,0 +1,60 @@ +class MoveSystemUploadFolder < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + unless file_storage? + say 'Using object storage, no need to move.' + return + end + + unless File.directory?(old_directory) + say "#{old_directory} doesn't exist, no need to move it." + return + end + + FileUtils.mkdir_p(File.join(base_directory, '-')) + + say "Moving #{old_directory} -> #{new_directory}" + FileUtils.mv(old_directory, new_directory) + FileUtils.ln_s(new_directory, old_directory) + end + + def down + unless file_storage? + say 'Using object storage, no need to move.' + return + end + + unless File.directory?(new_directory) + say "#{new_directory} doesn't exist, no need to move it." + return + end + + if File.symlink?(old_directory) + say "Removing #{old_directory} -> #{new_directory} symlink" + FileUtils.rm(old_directory) + end + + say "Moving #{new_directory} -> #{old_directory}" + FileUtils.mv(new_directory, old_directory) + end + + def new_directory + File.join(base_directory, '-', 'system') + end + + def old_directory + File.join(base_directory, 'system') + end + + def base_directory + File.join(Rails.root, 'public', 'uploads') + end + + def file_storage? + CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File + end +end diff --git a/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb b/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb new file mode 100644 index 00000000000..26b99b61424 --- /dev/null +++ b/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb @@ -0,0 +1,40 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CleanupMoveSystemUploadFolderSymlink < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + if File.symlink?(old_directory) + say "Removing #{old_directory} -> #{new_directory} symlink" + FileUtils.rm(old_directory) + else + say "Symlink #{old_directory} non existant, nothing to do." + end + end + + def down + if File.directory?(new_directory) + say "Symlinking #{old_directory} -> #{new_directory}" + FileUtils.ln_s(new_directory, old_directory) + else + say "#{new_directory} doesn't exist, skipping." + end + end + + def new_directory + File.join(base_directory, '-', 'system') + end + + def old_directory + File.join(base_directory, 'system') + end + + def base_directory + File.join(Rails.root, 'public', 'uploads') + end +end diff --git a/db/schema.rb b/db/schema.rb index 9c8c64fe2d0..0195d73db39 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: 20170713104829) do +ActiveRecord::Schema.define(version: 20170717111152) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb b/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb new file mode 100644 index 00000000000..3a9fa8c7113 --- /dev/null +++ b/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' +require Rails.root.join("db", "post_migrate", "20170717111152_cleanup_move_system_upload_folder_symlink.rb") + +describe CleanupMoveSystemUploadFolderSymlink do + let(:migration) { described_class.new } + let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') } + let(:test_folder) { File.join(test_base, '-', 'system') } + + before do + allow(migration).to receive(:base_directory).and_return(test_base) + FileUtils.rm_rf(test_base) + FileUtils.mkdir_p(test_folder) + allow(migration).to receive(:say) + end + + describe '#up' do + before do + FileUtils.ln_s(test_folder, File.join(test_base, 'system')) + end + + it 'removes the symlink' do + migration.up + + expect(File.exist?(File.join(test_base, 'system'))).to be_falsey + end + end + + describe '#down' do + it 'creates the symlink' do + migration.down + + expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy + end + end +end diff --git a/spec/migrations/move_system_upload_folder_spec.rb b/spec/migrations/move_system_upload_folder_spec.rb new file mode 100644 index 00000000000..b622b4e9536 --- /dev/null +++ b/spec/migrations/move_system_upload_folder_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' +require Rails.root.join("db", "migrate", "20170717074009_move_system_upload_folder.rb") + +describe MoveSystemUploadFolder do + let(:migration) { described_class.new } + let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') } + + before do + allow(migration).to receive(:base_directory).and_return(test_base) + FileUtils.rm_rf(test_base) + FileUtils.mkdir_p(test_base) + allow(migration).to receive(:say) + end + + describe '#up' do + let(:test_folder) { File.join(test_base, 'system') } + let(:test_file) { File.join(test_folder, 'file') } + + before do + FileUtils.mkdir_p(test_folder) + FileUtils.touch(test_file) + end + + it 'moves the related folder' do + migration.up + + expect(File.exist?(File.join(test_base, '-', 'system', 'file'))).to be_truthy + end + + it 'creates a symlink linking making the new folder available on the old path' do + migration.up + + expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy + expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy + end + end + + describe '#down' do + let(:test_folder) { File.join(test_base, '-', 'system') } + let(:test_file) { File.join(test_folder, 'file') } + + before do + FileUtils.mkdir_p(test_folder) + FileUtils.touch(test_file) + end + + it 'moves the system folder back to the old location' do + migration.down + + expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy + end + + it 'removes the symlink if it existed' do + FileUtils.ln_s(test_folder, File.join(test_base, 'system')) + + migration.down + + expect(File.directory?(File.join(test_base, 'system'))).to be_truthy + expect(File.symlink?(File.join(test_base, 'system'))).to be_falsey + end + end +end From c156030ef965bed019def3993ee21d214fe2f2ba Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 17 Jul 2017 15:23:59 +0200 Subject: [PATCH 062/130] Add a background migration to rename `uploads` in the uploads table --- ...ue_migrate_system_uploads_to_new_folder.rb | 20 ++++++++ db/schema.rb | 2 +- .../migrate_system_uploads_to_new_folder.rb | 47 +++++++++++++++++++ ...grate_system_uploads_to_new_folder_spec.rb | 19 ++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb create mode 100644 lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb create mode 100644 spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb diff --git a/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb b/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb new file mode 100644 index 00000000000..87069dce006 --- /dev/null +++ b/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb @@ -0,0 +1,20 @@ +class EnqueueMigrateSystemUploadsToNewFolder < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + OLD_FOLDER = 'uploads/system/' + NEW_FOLDER = 'uploads/-/system/' + + disable_ddl_transaction! + + def up + BackgroundMigrationWorker.perform_async('MigrateSystemUploadsToNewFolder', + [OLD_FOLDER, NEW_FOLDER]) + end + + def down + BackgroundMigrationWorker.perform_async('MigrateSystemUploadsToNewFolder', + [NEW_FOLDER, OLD_FOLDER]) + end +end diff --git a/db/schema.rb b/db/schema.rb index 0195d73db39..284b2068166 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: 20170717111152) do +ActiveRecord::Schema.define(version: 20170717150329) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb new file mode 100644 index 00000000000..601c874bc9b --- /dev/null +++ b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb @@ -0,0 +1,47 @@ +module Gitlab + module BackgroundMigration + class MigrateSystemUploadsToNewFolder + include Gitlab::Database::MigrationHelpers + attr_reader :old_folder, :new_folder + + def perform(old_folder, new_folder) + @old_folder = old_folder + @new_folder = new_folder + + replace_sql = replace_sql(uploads[:path], old_folder, new_folder) + + while remaining_rows > 0 + sql = "UPDATE uploads "\ + "SET path = #{replace_sql} "\ + "WHERE uploads.id IN "\ + " (SELECT uploads.id FROM uploads "\ + " WHERE #{affected_uploads.to_sql} LIMIT 1000)" + connection.execute(sql) + end + end + + def uploads + Arel::Table.new('uploads') + end + + def remaining_rows + remaining_result = connection.exec_query("SELECT count(id) FROM uploads WHERE #{affected_uploads.to_sql}") + remaining = remaining_result.first['count'].to_i + logger.info "#{remaining} uploads remaining" + remaining + end + + def affected_uploads + uploads[:path].matches("#{old_folder}%") + end + + def connection + ActiveRecord::Base.connection + end + + def logger + Sidekiq.logger || Rails.logger || Logger.new(STDOUT) + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb new file mode 100644 index 00000000000..a910fb105a5 --- /dev/null +++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do + let(:migration) { described_class.new } + + before do + allow(migration).to receive(:logger).and_return(Logger.new(nil)) + end + + describe '#perform' do + it 'renames the path of system-uploads', truncate: true do + upload = create(:upload, model: create(:empty_project), path: 'uploads/system/project/avatar.jpg') + + migration.perform('uploads/system/', 'uploads/-/system/') + + expect(upload.reload.path).to eq('uploads/-/system/project/avatar.jpg') + end + end +end From 458f3cf9b0a4f3571b9df8ae76e8fcabdc23c96b Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 17 Jul 2017 15:46:59 +0200 Subject: [PATCH 063/130] Update specs for new upload path --- features/steps/groups.rb | 2 +- features/steps/profile/profile.rb | 2 +- features/steps/project/project.rb | 2 +- spec/factories/uploads.rb | 2 +- spec/features/admin/admin_appearance_spec.rb | 4 ++-- .../uploads/user_uploads_avatar_to_group_spec.rb | 2 +- .../user_uploads_avatar_to_profile_spec.rb | 2 +- spec/helpers/application_helper_spec.rb | 16 ++++++++-------- spec/helpers/emails_helper_spec.rb | 2 +- spec/helpers/groups_helper_spec.rb | 2 +- spec/helpers/page_layout_helper_spec.rb | 2 +- .../vue_shared/components/commit_spec.js | 4 ++-- spec/models/group_spec.rb | 2 +- spec/models/project_spec.rb | 2 +- spec/models/user_spec.rb | 2 +- spec/requests/api/projects_spec.rb | 2 +- spec/requests/openid_connect_spec.rb | 2 +- .../projects/participants_service_spec.rb | 4 ++-- spec/uploaders/attachment_uploader_spec.rb | 2 +- spec/uploaders/avatar_uploader_spec.rb | 2 +- 20 files changed, 30 insertions(+), 30 deletions(-) diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 0aedc422563..6b288b47da4 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -81,7 +81,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'I should see new group "Owned" avatar' do expect(owned_group.avatar).to be_instance_of AvatarUploader - expect(owned_group.avatar.url).to eq "/uploads/system/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif" + expect(owned_group.avatar.url).to eq "/uploads/-/system/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif" end step 'I should see the "Remove avatar" button' do diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 254c26bb6af..4b88cb5e27f 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -36,7 +36,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps step 'I should see new avatar' do expect(@user.avatar).to be_instance_of AvatarUploader - expect(@user.avatar.url).to eq "/uploads/system/user/avatar/#{@user.id}/banana_sample.gif" + expect(@user.avatar.url).to eq "/uploads/-/system/user/avatar/#{@user.id}/banana_sample.gif" end step 'I should see the "Remove avatar" button' do diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 7d34331db46..170e2f16c80 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -38,7 +38,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps step 'I should see new project avatar' do expect(@project.avatar).to be_instance_of AvatarUploader url = @project.avatar.url - expect(url).to eq "/uploads/system/project/avatar/#{@project.id}/banana_sample.gif" + expect(url).to eq "/uploads/-/system/project/avatar/#{@project.id}/banana_sample.gif" end step 'I should see the "Remove avatar" button' do diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index 1383420fb44..3222c41c3d8 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :upload do model { build(:project) } - path { "uploads/system/project/avatar/avatar.jpg" } + path { "uploads/-/system/project/avatar/avatar.jpg" } size 100.kilobytes uploader "AvatarUploader" end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 1e2cb8569ec..b9e361328df 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do end def logo_selector - '//img[@src^="/uploads/system/appearance/logo"]' + '//img[@src^="/uploads/-/system/appearance/logo"]' end def header_logo_selector - '//img[@src^="/uploads/system/appearance/header_logo"]' + '//img[@src^="/uploads/-/system/appearance/header_logo"]' end def logo_fixture diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index 32784de1613..5843f18d89f 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do visit group_path(group) - expect(page).to have_selector(%Q(img[src$="/uploads/system/group/avatar/#{group.id}/dk.png"])) + expect(page).to have_selector(%Q(img[src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"])) # Cheating here to verify something that isn't user-facing, but is important expect(group.reload.avatar.file).to exist diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index 82c356735b9..e8171dcaeb0 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do visit user_path(user) - expect(page).to have_selector(%Q(img[src$="/uploads/system/user/avatar/#{user.id}/dk.png"])) + expect(page).to have_selector(%Q(img[src$="/uploads/-/system/user/avatar/#{user.id}/dk.png"])) # Cheating here to verify something that isn't user-facing, but is important expect(user.reload.avatar.file).to exist diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index e0cad1da86a..f5e139685e8 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -59,13 +59,13 @@ describe ApplicationHelper do describe 'project_icon' do it 'returns an url for the avatar' do project = create(:empty_project, avatar: File.open(uploaded_image_temp_path)) - avatar_url = "/uploads/system/project/avatar/#{project.id}/banana_sample.gif" + avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "\"Banana" allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) - avatar_url = "#{gitlab_host}/uploads/system/project/avatar/#{project.id}/banana_sample.gif" + avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "\"Banana" @@ -88,7 +88,7 @@ describe ApplicationHelper do context 'when there is a matching user' do it 'returns a relative URL for the avatar' do expect(helper.avatar_icon(user.email).to_s) - .to eq("/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end context 'when an asset_host is set in the config' do @@ -100,14 +100,14 @@ describe ApplicationHelper do it 'returns an absolute URL on that asset host' do expect(helper.avatar_icon(user.email, only_path: false).to_s) - .to eq("#{asset_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("#{asset_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end context 'when only_path is set to false' do it 'returns an absolute URL for the avatar' do expect(helper.avatar_icon(user.email, only_path: false).to_s) - .to eq("#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("#{gitlab_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end @@ -120,7 +120,7 @@ describe ApplicationHelper do it 'returns a relative URL with the correct prefix' do expect(helper.avatar_icon(user.email).to_s) - .to eq("/gitlab/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("/gitlab/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end end @@ -138,14 +138,14 @@ describe ApplicationHelper do context 'when only_path is true' do it 'returns a relative URL for the avatar' do expect(helper.avatar_icon(user, only_path: true).to_s) - .to eq("/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end context 'when only_path is false' do it 'returns an absolute URL for the avatar' do expect(helper.avatar_icon(user, only_path: false).to_s) - .to eq("#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif") + .to eq("#{gitlab_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif") end end end diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index c68e4f56b05..2390c1f3e5d 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -52,7 +52,7 @@ describe EmailsHelper do ) expect(header_logo).to eq( - %{Dk} + %{Dk} ) end end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index e3f9d9db9eb..3a246f10283 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -11,7 +11,7 @@ describe GroupsHelper do group.avatar = fixture_file_upload(avatar_file_path) group.save! expect(group_icon(group.path).to_s) - .to match("/uploads/system/group/avatar/#{group.id}/banana_sample.gif") + .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") end it 'gives default avatar_icon when no avatar is present' do diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 95b4032616e..9aca3987657 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -60,7 +60,7 @@ describe PageLayoutHelper do %w(project user group).each do |type| context "with @#{type} assigned" do it "uses #{type.titlecase} avatar if available" do - object = double(avatar_url: 'http://example.com/uploads/system/avatar.png') + object = double(avatar_url: 'http://example.com/uploads/-/system/avatar.png') assign(type, object) expect(helper.page_image).to eq object.avatar_url diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js index 1c3188cdda2..d5754aaa9e7 100644 --- a/spec/javascripts/vue_shared/components/commit_spec.js +++ b/spec/javascripts/vue_shared/components/commit_spec.js @@ -22,7 +22,7 @@ describe('Commit component', () => { shortSha: 'b7836edd', title: 'Commit message', author: { - avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png', + avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png', web_url: 'https://gitlab.com/jschatz1', path: '/jschatz1', username: 'jschatz1', @@ -45,7 +45,7 @@ describe('Commit component', () => { shortSha: 'b7836edd', title: 'Commit message', author: { - avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png', + avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png', web_url: 'https://gitlab.com/jschatz1', path: '/jschatz1', username: 'jschatz1', diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 066d7b9307f..770176451fe 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -189,7 +189,7 @@ describe Group, models: true do let!(:group) { create(:group, :access_requestable, :with_avatar) } let(:user) { create(:user) } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } - let(:avatar_path) { "/uploads/system/group/avatar/#{group.id}/dk.png" } + let(:avatar_path) { "/uploads/-/system/group/avatar/#{group.id}/dk.png" } context 'when avatar file is uploaded' do before do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e636250c37d..90769b580cd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -807,7 +807,7 @@ describe Project, models: true do context 'when avatar file is uploaded' do let(:project) { create(:empty_project, :with_avatar) } - let(:avatar_path) { "/uploads/system/project/avatar/#{project.id}/dk.png" } + let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } it 'shows correct url' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 69f2570eec2..a1d6d7e6e0b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1028,7 +1028,7 @@ describe User, models: true do context 'when avatar file is uploaded' do let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } - let(:avatar_path) { "/uploads/system/user/avatar/#{user.id}/dk.png" } + let(:avatar_path) { "/uploads/-/system/user/avatar/#{user.id}/dk.png" } it 'shows correct avatar url' do expect(user.avatar_url).to eq(avatar_path) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index fa704f23857..6dbde8bad31 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -442,7 +442,7 @@ describe API::Projects do post api('/projects', user), project project_id = json_response['id'] - expect(json_response['avatar_url']).to eq("http://localhost/uploads/system/project/avatar/#{project_id}/banana_sample.gif") + expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif") end it 'sets a project as allowing merge even if build fails' do diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index ebba28ba8ce..a927de952d0 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -79,7 +79,7 @@ describe 'OpenID Connect requests' do 'email_verified' => true, 'website' => 'https://example.com', 'profile' => 'http://localhost/alice', - 'picture' => "http://localhost/uploads/system/user/avatar/#{user.id}/dk.png" + 'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png" }) end end diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index d75851134ee..3688f6d4e23 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -13,7 +13,7 @@ describe Projects::ParticipantsService, services: true do groups = participants.groups expect(groups.size).to eq 1 - expect(groups.first[:avatar_url]).to eq("/uploads/system/group/avatar/#{group.id}/dk.png") + expect(groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png") end it 'should return an url for the avatar with relative url' do @@ -24,7 +24,7 @@ describe Projects::ParticipantsService, services: true do groups = participants.groups expect(groups.size).to eq 1 - expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/system/group/avatar/#{group.id}/dk.png") + expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png") end end end diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb index d82dbe871d5..04ee6e9bfad 100644 --- a/spec/uploaders/attachment_uploader_spec.rb +++ b/spec/uploaders/attachment_uploader_spec.rb @@ -5,7 +5,7 @@ describe AttachmentUploader do describe "#store_dir" do it "stores in the system dir" do - expect(uploader.store_dir).to start_with("uploads/system/user") + expect(uploader.store_dir).to start_with("uploads/-/system/user") end it "uses the old path when using object storage" do diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb index 201fe6949aa..1dc574699d8 100644 --- a/spec/uploaders/avatar_uploader_spec.rb +++ b/spec/uploaders/avatar_uploader_spec.rb @@ -5,7 +5,7 @@ describe AvatarUploader do describe "#store_dir" do it "stores in the system dir" do - expect(uploader.store_dir).to start_with("uploads/system/user") + expect(uploader.store_dir).to start_with("uploads/-/system/user") end it "uses the old path when using object storage" do From 6a10626f981e1c4cd76e5d18e78880deba1b7fe3 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 17 Jul 2017 16:37:40 +0200 Subject: [PATCH 064/130] Invalidate project list cache So the avatars would be reloaded from their new path --- app/helpers/projects_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 26d11f9ab46..9a8d296d514 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -195,7 +195,7 @@ module ProjectsHelper controller.controller_name, controller.action_name, current_application_settings.cache_key, - 'v2.4' + 'v2.5' ] key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status? From 6f26f6f79f33fdb7e4e556b7f6b37999b78cd323 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 17 Jul 2017 18:11:07 +0200 Subject: [PATCH 065/130] Allow groups with the name system --- .../migrate_system_uploads_to_new_folder.rb | 37 ++++--------------- lib/gitlab/path_regex.rb | 1 - spec/models/namespace_spec.rb | 2 +- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb index 601c874bc9b..0881244ed49 100644 --- a/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb +++ b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb @@ -4,44 +4,23 @@ module Gitlab include Gitlab::Database::MigrationHelpers attr_reader :old_folder, :new_folder + class Upload < ActiveRecord::Base + self.table_name = 'uploads' + include EachBatch + end + def perform(old_folder, new_folder) - @old_folder = old_folder - @new_folder = new_folder - replace_sql = replace_sql(uploads[:path], old_folder, new_folder) + affected_uploads = Upload.where(uploads[:path].matches("#{old_folder}%")) - while remaining_rows > 0 - sql = "UPDATE uploads "\ - "SET path = #{replace_sql} "\ - "WHERE uploads.id IN "\ - " (SELECT uploads.id FROM uploads "\ - " WHERE #{affected_uploads.to_sql} LIMIT 1000)" - connection.execute(sql) + affected_uploads.each_batch do |batch| + batch.update_all("path = #{replace_sql}") end end def uploads Arel::Table.new('uploads') end - - def remaining_rows - remaining_result = connection.exec_query("SELECT count(id) FROM uploads WHERE #{affected_uploads.to_sql}") - remaining = remaining_result.first['count'].to_i - logger.info "#{remaining} uploads remaining" - remaining - end - - def affected_uploads - uploads[:path].matches("#{old_folder}%") - end - - def connection - ActiveRecord::Base.connection - end - - def logger - Sidekiq.logger || Rails.logger || Logger.new(STDOUT) - end end end end diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index d81f825ef96..60a32d5d5ea 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -49,7 +49,6 @@ module Gitlab sent_notifications services snippets - system teams u unicorn_test diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 89ea5ceda95..a4090b37f65 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -44,7 +44,7 @@ describe Namespace, models: true do end context "is case insensitive" do - let(:group) { build(:group, path: "System") } + let(:group) { build(:group, path: "Groups") } it { expect(group).not_to be_valid } end From f10c4c72e221a709f3a32a994d04a147196db8a7 Mon Sep 17 00:00:00 2001 From: goshkob Date: Tue, 18 Jul 2017 14:43:05 +0000 Subject: [PATCH 066/130] =?UTF-8?q?reset=20text-align=20to=20initial=20to?= =?UTF-8?q?=20let=20elements=20with=20dir=3D"auto"=20align=20texts=20to=20?= =?UTF-8?q?right=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/stylesheets/framework/typography.scss | 4 ++++ .../12892-reset-css-text-align-to-initial-for-rtl.md | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 785b09e622f..77b7d901f9a 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -2,6 +2,10 @@ color: $gl-text-color; word-wrap: break-word; + [dir="auto"] { + text-align: initial; + } + a { color: $md-link-color; } diff --git a/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md b/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md new file mode 100644 index 00000000000..87e95240bba --- /dev/null +++ b/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md @@ -0,0 +1,4 @@ +--- +title: "reset text-align to initial to let elements with dir="auto" align texts to right in RTL languages ( default is left )" +merge_request: 12892 +author: goshhob From 3715c1cfb5bf3d904e7d2b0f40c8b64f09a7e231 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 17 Jul 2017 15:56:35 -0300 Subject: [PATCH 067/130] Fix external issue trackers redirect --- app/controllers/projects/issues_controller.rb | 2 +- .../gitlab_issue_tracker_service.rb | 2 +- .../project_services/issue_tracker_service.rb | 4 ++-- .../projects/issues_controller_spec.rb | 16 ++++++++++------ .../gitlab_issue_tracker_service_spec.rb | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 13f03e7e63e..0ac9da2ff0f 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -266,7 +266,7 @@ class Projects::IssuesController < Projects::ApplicationController if action_name == 'new' redirect_to external.new_issue_path else - redirect_to external.project_path + redirect_to external.issue_tracker_path end end diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 420102875a5..88c428b4aae 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -23,7 +23,7 @@ class GitlabIssueTrackerService < IssueTrackerService project_issue_url(project, id: iid) end - def project_path + def issue_tracker_path project_issues_path(project) end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 1fa4cd4db30..6d6a3ae3647 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -20,8 +20,8 @@ class IssueTrackerService < Service self.issues_url.gsub(':id', iid.to_s) end - def project_path - read_attribute(:project_url) + def issue_tracker_path + project_url end def new_issue_path diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 22aad0b3225..1f9ca765233 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -7,14 +7,16 @@ describe Projects::IssuesController do describe "GET #index" do context 'external issue tracker' do + let!(:service) do + create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', project_url: 'http://test.com') + end + it 'redirects to the external issue tracker' do - external = double(project_path: 'https://example.com/project') - allow(project).to receive(:external_issue_tracker).and_return(external) controller.instance_variable_set(:@project, project) get :index, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to('https://example.com/project') + expect(response).to redirect_to(service.issue_tracker_path) end end @@ -139,19 +141,21 @@ describe Projects::IssuesController do end context 'external issue tracker' do + let!(:service) do + create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', new_issue_url: 'http://test.com') + end + before do sign_in(user) project.team << [user, :developer] end it 'redirects to the external issue tracker' do - external = double(new_issue_path: 'https://example.com/issues/new') - allow(project).to receive(:external_issue_tracker).and_return(external) controller.instance_variable_set(:@project, project) get :new, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to('https://example.com/issues/new') + expect(response).to redirect_to('http://test.com') end end end diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 6ee30e86495..d45e0a441d4 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -43,7 +43,7 @@ describe GitlabIssueTrackerService, models: true do end it 'gives the correct path' do - expect(service.project_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues") + expect(service.issue_tracker_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues") expect(service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new") expect(service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432") end From 4dff5dbca0a2a0cd365866c1dfe963f47743c07f Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Tue, 18 Jul 2017 10:55:32 -0400 Subject: [PATCH 068/130] Fix typos --- doc/user/project/integrations/prometheus.md | 2 +- doc/user/project/integrations/prometheus_library/metrics.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index a23635d1999..0e194fcb826 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -25,7 +25,7 @@ Integration with Prometheus requires the following: Depending on your deployment and where you have located your GitLab server, there are a few options to get started with Prometheus monitoring. -* If both GitLab and your applications are installed in the same Kubernetes cluster, you can leveraged the [bundled Prometheus server within GitLab](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes). +* If both GitLab and your applications are installed in the same Kubernetes cluster, you can leverage the [bundled Prometheus server within GitLab](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes). * If your applications are deployed on Kubernetes, but GitLab is not in the same cluster, then you can [configure a Prometheus server in your Kubernetes cluster](#configuring-your-own-prometheus-server-within-kubernetes). * If your applications are not running in Kubernetes, [get started with Prometheus](#getting-started-with-prometheus-outside-of-kubernetes). diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md index 2a1831c94ef..55146e57370 100644 --- a/doc/user/project/integrations/prometheus_library/metrics.md +++ b/doc/user/project/integrations/prometheus_library/metrics.md @@ -22,4 +22,4 @@ For example if you are deploying to an environment named `production`, there mus We strive to support the 2-4 most important metrics for each common system service that supports Prometheus. If you are looking for support for a particular exporter which has not yet been added to the library, additions can be made [to the `additional_metrics.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/prometheus/additional_metrics.yml) file. -> Note: The library is only for monitoring public, common, system services which all customers can benefit from. Support for monitoring [customer proprietary metrics](https://gitlab.com/gitlab-org/gitlab-ee/issues/2273) will be added in an subsequent release. +> Note: The library is only for monitoring public, common, system services which all customers can benefit from. Support for monitoring [customer proprietary metrics](https://gitlab.com/gitlab-org/gitlab-ee/issues/2273) will be added in a subsequent release. From 811108dce92f8fe9abcb43f8d94c49327474b475 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Tue, 18 Jul 2017 10:58:19 -0400 Subject: [PATCH 069/130] Fixed typos --- doc/administration/monitoring/prometheus/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index c61c4d56443..f43c89dad87 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -95,9 +95,9 @@ Sample Prometheus queries: ## Configuring Prometheus to monitor Kubernetes > Introduced in GitLab 9.0. -> Pod monitoring introduced in GitLAb 9.4. +> Pod monitoring introduced in GitLab 9.4. -If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes and [annotated Pods](https://prometheus.io/docs/operating/configuration/#) in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. +If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes and [annotated Pods](https://prometheus.io/docs/operating/configuration/#) in the cluster, including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. To disable the monitoring of Kubernetes: From a80583a9d24d0ecd08d91dbfcb5198f7becca02c Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 18 Jul 2017 09:58:12 -0500 Subject: [PATCH 070/130] Update avatar border to be opaque for better stacking Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/32561 --- app/assets/stylesheets/framework/avatar.scss | 4 ++-- app/assets/stylesheets/framework/variables.scss | 2 +- app/assets/stylesheets/pages/issuable.scss | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 4ae2b164d2e..06f7af33f94 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -60,7 +60,7 @@ } &:not([href]):hover { - border-color: rgba($avatar-border, .2); + border-color: darken($avatar-border, 10%); } } @@ -99,7 +99,7 @@ .avatar-counter { background-color: $gray-darkest; color: $white-light; - border: 1px solid $border-color; + border: 1px solid $avatar-border; border-radius: 1em; font-family: $regular_font; font-size: 9px; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 8bd69faf84c..7016208f624 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -379,7 +379,7 @@ $issue-boards-card-shadow: rgba(186, 186, 186, 0.5); * Avatar */ $avatar_radius: 50%; -$avatar-border: rgba(0, 0, 0, .1); +$avatar-border: $border-color; $gl-avatar-size: 40px; /* diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index e858ef427b0..aa04e490649 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -346,13 +346,9 @@ display: none; } - .avatar:hover, - .avatar-counter:hover { - border-color: $issuable-sidebar-color; - } - .avatar-counter:hover { color: $issuable-sidebar-color; + border-color: $issuable-sidebar-color; } .btn-clipboard { From 4a392feb09487e918060a16b83fa6b3066546da0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 18 Jul 2017 11:31:13 -0500 Subject: [PATCH 071/130] Fix download artifacts button alignment --- app/views/projects/artifacts/browse.html.haml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index 576e5b385af..a33743c2f57 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -5,12 +5,6 @@ .tree-holder .nav-block - .tree-controls - = link_to download_project_job_artifacts_path(@project, @build), - rel: 'nofollow', download: '', class: 'btn btn-default download' do - = icon('download') - Download artifacts archive - %ul.breadcrumb.repo-breadcrumb %li = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build) @@ -18,6 +12,12 @@ %li = link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path) + .tree-controls + = link_to download_project_job_artifacts_path(@project, @build), + rel: 'nofollow', download: '', class: 'btn btn-default download' do + = icon('download') + Download artifacts archive + .tree-content-holder %table.table.tree-table %thead From fc76ff1051681a7cabc1203043217b617e85f068 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 13 Jul 2017 16:51:37 +0200 Subject: [PATCH 072/130] Disable Rails logging in CI test environments See https://jtway.co/speed-up-your-rails-test-suite-by-6-in-1-line-13fedb869ec4 --- config/environments/test.rb | 5 +++++ doc/development/testing.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/config/environments/test.rb b/config/environments/test.rb index c3b788c038e..986107150cf 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -43,4 +43,9 @@ Rails.application.configure do config.cache_store = :null_store config.active_job.queue_adapter = :test + + if ENV['CI'] && !ENV['RAILS_ENABLE_TEST_LOG'] + config.logger = Logger.new(nil) + config.log_level = :fatal + end end diff --git a/doc/development/testing.md b/doc/development/testing.md index cf3ea2ccfc2..fc84932354b 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -479,6 +479,11 @@ slowest test files and try to improve them. run the suite against MySQL for tags, `master`, and any branch that includes `mysql` in the name. - On EE, the test suite always runs both PostgreSQL and MySQL. +- Rails logging to `log/test.log` is disabled by default in CI [for + performance reasons][logging]. To override this setting, provide the + `RAILS_ENABLE_TEST_LOG` environment variable. + +[logging]: https://jtway.co/speed-up-your-rails-test-suite-by-6-in-1-line-13fedb869ec4 ## Spinach (feature) tests From e2b1c16ade09c0afde11d8b39bac9f1974c9b057 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 17 Jul 2017 15:54:13 -0700 Subject: [PATCH 073/130] Add structured logging for Rails processes This introduces JSON logging for Rails views saved to a file called `development_json.log`, `production_json.log`, etc. For example, instead of this unparsable log: ``` Started GET "/" for 127.0.0.1 at 2012-03-10 14:28:14 +0100 Processing by HomeController#index as HTML Rendered text template within layouts/application (0.0ms) Rendered layouts/_assets.html.erb (2.0ms) Rendered layouts/_top.html.erb (2.6ms) Rendered layouts/_about.html.erb (0.3ms) Rendered layouts/_google_analytics.html.erb (0.4ms) Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms) ``` We get a single line with this: ``` {"method":"GET","path":"/,"format":"html","controller":"HomeController","action":"index","status":200,"duration":79,"view":78.8,"db":0.0,"location":"http://localhost/","time":"2017-07-18 09:35:17 -0700"} ``` Part of #20060 --- Gemfile | 3 +++ Gemfile.lock | 5 +++++ .../unreleased/sh-structured-logging.yml | 4 ++++ config/initializers/lograge.rb | 21 +++++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 changelogs/unreleased/sh-structured-logging.yml create mode 100644 config/initializers/lograge.rb diff --git a/Gemfile b/Gemfile index abf9f323fb4..ddda5901a08 100644 --- a/Gemfile +++ b/Gemfile @@ -390,3 +390,6 @@ gem 'toml-rb', '~> 0.3.15', require: false # Feature toggles gem 'flipper', '~> 0.10.2' gem 'flipper-active_record', '~> 0.10.2' + +# Structured logging +gem 'lograge', '~> 0.5' diff --git a/Gemfile.lock b/Gemfile.lock index a24636ad512..f9783a3468c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -443,6 +443,10 @@ GEM logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) + lograge (0.5.1) + actionpack (>= 4, < 5.2) + activesupport (>= 4, < 5.2) + railties (>= 4, < 5.2) loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.5) @@ -998,6 +1002,7 @@ DEPENDENCIES letter_opener_web (~> 1.3.0) license_finder (~> 2.1.0) licensee (~> 8.7.0) + lograge (~> 0.5) loofah (~> 2.0.3) mail_room (~> 0.9.1) method_source (~> 0.8) diff --git a/changelogs/unreleased/sh-structured-logging.yml b/changelogs/unreleased/sh-structured-logging.yml new file mode 100644 index 00000000000..d89eb93f689 --- /dev/null +++ b/changelogs/unreleased/sh-structured-logging.yml @@ -0,0 +1,4 @@ +--- +title: Add structured logging for Rails processes +merge_request: +author: diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb new file mode 100644 index 00000000000..14902316240 --- /dev/null +++ b/config/initializers/lograge.rb @@ -0,0 +1,21 @@ +# Only use Lograge for Rails +unless Sidekiq.server? + filename = File.join(Rails.root, 'log', "#{Rails.env}_json.log") + + Rails.application.configure do + config.lograge.enabled = true + # Store the lograge JSON files in a separate file + config.lograge.keep_original_rails_log = true + # Don't use the Logstash formatter since this requires logstash-event, an + # unmaintained gem that monkey patches `Time` + config.lograge.formatter = Lograge::Formatters::Json.new + config.lograge.logger = ActiveSupport::Logger.new(filename) + # Add request parameters to log output + config.lograge.custom_options = lambda do |event| + { + time: event.time, + params: event.payload[:params].except(%w(controller action format)) + } + end + end +end From c322a2eee98703896894b36a36f207f59922e3ad Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 17 Jul 2017 11:56:16 -0500 Subject: [PATCH 074/130] Rename Project nav items --- app/views/layouts/nav/_new_group_sidebar.html.haml | 8 ++++---- app/views/layouts/nav/_new_project_sidebar.html.haml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index c80308ed0de..6e0c45739f1 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -6,15 +6,15 @@ = @group.name %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do - = link_to group_path(@group), title: 'Home' do + = link_to group_path(@group), title: 'About group' do %span - Group + About %ul.sidebar-sub-level-items = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do - = link_to group_path(@group), title: 'Group Home' do + = link_to group_path(@group), title: 'Group details' do %span - Home + Details = nav_link(path: 'groups#activity') do = link_to activity_group_path(@group), title: 'Activity' do diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index 7c9822c5a6a..882123c0b0a 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -7,14 +7,14 @@ = @project.name %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do - = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do + = link_to project_path(@project), title: 'About project', class: 'shortcuts-project' do %span - Project + About %ul.sidebar-sub-level-items = nav_link(path: 'projects#show') do - = link_to project_path(@project), title: _('Project home'), class: 'shortcuts-project' do - %span= _('Home') + = link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do + %span= _('Details') = nav_link(path: 'projects#activity') do = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do From 3dcd3df531081c2a3861866149463da4618e2396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 18 Jul 2017 19:00:46 +0200 Subject: [PATCH 075/130] Fix queries duration sorting in Performance Bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/performance_bar/peek_query_tracker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb index f97e895dbd0..67fee8c227d 100644 --- a/lib/gitlab/performance_bar/peek_query_tracker.rb +++ b/lib/gitlab/performance_bar/peek_query_tracker.rb @@ -37,7 +37,7 @@ module Gitlab def track_query(raw_query, bindings, start, finish) query = Gitlab::Sherlock::Query.new(raw_query, start, finish) - query_info = { duration: '%.3f' % query.duration, sql: query.formatted_query } + query_info = { duration: query.duration.round(3), sql: query.formatted_query } PEEK_DB_CLIENT.query_details << query_info end From 1b98ee75382d5293f3ce4e4f1884a3fc6a057616 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Tue, 18 Jul 2017 13:38:40 -0400 Subject: [PATCH 076/130] Adds datefix class. --- app/assets/javascripts/due_date_select.js | 8 ++++---- app/assets/javascripts/lib/utils/datefix.js | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/lib/utils/datefix.js diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 7a47a85c4fd..60da9bff06c 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -2,6 +2,8 @@ /* global dateFormat */ /* global Pikaday */ +import DateFix from './lib/utils/datefix' + class DueDateSelect { constructor({ $dropdown, $loading } = {}) { const $dropdownParent = $dropdown.closest('.dropdown'); @@ -50,7 +52,6 @@ class DueDateSelect { format: 'yyyy-mm-dd', onSelect: (dateText) => { const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd'); - $dueDateInput.val(formattedDate); if (this.$dropdown.hasClass('js-issue-boards-due-date')) { @@ -62,7 +63,7 @@ class DueDateSelect { } }); - calendar.setDate(new Date($dueDateInput.val())); + calendar.setDate(DateFix.dashedFix($dueDateInput.val())); this.$datePicker.append(calendar.el); this.$datePicker.data('pikaday', calendar); } @@ -168,7 +169,6 @@ class DueDateSelectors { initMilestoneDatePicker() { $('.datepicker').each(function() { const $datePicker = $(this); - const [y, m, d] = $datePicker.val().split('-'); const calendar = new Pikaday({ field: $datePicker.get(0), theme: 'gitlab-theme animate-picker', @@ -179,7 +179,7 @@ class DueDateSelectors { } }); - calendar.setDate(new Date(y, m - 1, d)); + calendar.setDate(DateFix.dashedFix($datePicker.val())); $datePicker.data('pikaday', calendar); }); diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js new file mode 100644 index 00000000000..d0c81ec854f --- /dev/null +++ b/app/assets/javascripts/lib/utils/datefix.js @@ -0,0 +1,9 @@ +const DateFix = { + dashedFix(val) { + const [y, m, d] = val.split('-'); + console.log(y,m,d) + return new Date(y, m - 1, d); + } +} + +export default DateFix; \ No newline at end of file From d66f8334310c77ad5908eec8d31ddb7cc19aeb2c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 18 Jul 2017 13:08:23 -0500 Subject: [PATCH 077/130] Fix CI Status alignment for artifacts page --- app/views/projects/jobs/_header.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml index d81b8f6bb4c..83a2af1dc74 100644 --- a/app/views/projects/jobs/_header.html.haml +++ b/app/views/projects/jobs/_header.html.haml @@ -1,7 +1,7 @@ - show_controls = local_assigns.fetch(:show_controls, true) - pipeline = @build.pipeline -.content-block.build-header.top-area +.content-block.build-header.top-area.page-content-header .header-content = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title %strong From a08fe9a9034cd0cdad1795e48a5f6f05f383b1cf Mon Sep 17 00:00:00 2001 From: Balasankar C Date: Tue, 18 Jul 2017 23:46:48 +0530 Subject: [PATCH 078/130] Provide option to trigger build only for official CE and EE repos in .com --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fd3a9ce0986..1a65e0473c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -160,6 +160,9 @@ build-package: when: manual script: - scripts/trigger-build + only: + - //@gitlab-org/gitlab-ce + - //@gitlab-org/gitlab-ee # Prepare and merge knapsack tests knapsack: From cb83c50d4663532b5b1729b27301d22102c17524 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 17 Jul 2017 17:33:12 -0500 Subject: [PATCH 079/130] Add mobile navigation on project page --- app/assets/javascripts/layout_nav.js | 2 + app/assets/javascripts/new_sidebar.js | 23 ++++ app/assets/stylesheets/new_nav.scss | 3 +- app/assets/stylesheets/new_sidebar.scss | 105 ++++++++++++++---- app/views/layouts/nav/_breadcrumbs.html.haml | 2 + .../nav/_new_project_sidebar.html.haml | 13 ++- 6 files changed, 117 insertions(+), 31 deletions(-) create mode 100644 app/assets/javascripts/new_sidebar.js diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 71064ccc539..e8f59f30035 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */ import _ from 'underscore'; +import NewNavSidebar from './new_sidebar'; (function() { var hideEndFade; @@ -53,6 +54,7 @@ import _ from 'underscore'; } $(() => { + new NewNavSidebar(); $(window).on('scroll', _.throttle(applyScrollNavClass, 100)); }); }).call(window); diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js new file mode 100644 index 00000000000..4aadeb6b462 --- /dev/null +++ b/app/assets/javascripts/new_sidebar.js @@ -0,0 +1,23 @@ +const SIDEBAR_EXPANDED_CLASS = 'nav-sidebar-expanded'; + +export default class NewNavSidebar { + constructor() { + this.initDomElements(); + this.bindEvents(); + } + + initDomElements() { + this.$sidebar = $('.nav-sidebar'); + this.$openSidebar = $('.toggle-mobile-nav'); + this.$closeSidebar = $('.close-nav-button'); + } + + bindEvents() { + this.$openSidebar.on('click', e => this.toggleSidebarNav(e, true)); + this.$closeSidebar.on('click', e => this.toggleSidebarNav(e, false)); + } + + toggleSidebarNav(show) { + this.$sidebar.toggleClass(SIDEBAR_EXPANDED_CLASS, show); + } +} diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index 393d5006e24..03ba0afe940 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -275,8 +275,6 @@ header.navbar-gitlab-new { .breadcrumbs { display: flex; min-height: 60px; - padding-top: $gl-padding-top; - padding-bottom: $gl-padding-top; color: $gl-text-color; border-bottom: 1px solid $border-color; @@ -300,6 +298,7 @@ header.navbar-gitlab-new { display: flex; width: 100%; position: relative; + align-items: center; .dropdown-menu-projects { margin-top: -$gl-padding; diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 82cabefa129..eaf7daaa5cf 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -26,41 +26,75 @@ $new-sidebar-width: 220px; } .context-header { - border-bottom: 1px solid $border-color; - font-weight: 600; - display: flex; - align-items: center; - padding: 10px 16px 10px 10px; - color: $gl-text-color; + position: relative; + + a { + border-bottom: 1px solid $border-color; + font-weight: 600; + display: flex; + align-items: center; + padding: 10px 16px 10px 10px; + color: $gl-text-color; + + @media (max-width: $screen-xs-max) { + padding-right: 30px; + } + + &:hover { + background-color: $hover-background; + color: $hover-color; + border-color: $hover-background; + + .avatar-container { + border-color: transparent; + } + + .settings-avatar { + background-color: $indigo-500; + + i { + color: $hover-color; + } + } + } + } .avatar-container { flex: 0 0 40px; background-color: $white-light; } - &:hover { - background-color: $hover-background; - color: $hover-color; - border-color: $hover-background; - - .avatar-container { - border-color: transparent; - } - - .settings-avatar { - background-color: $indigo-500; - - i { - color: $hover-color; - } - } - } - .project-title, .group-title { overflow: hidden; text-overflow: ellipsis; } + + + &:hover { + .close-nav-button { + color: $white-light; + } + } + + .close-nav-button { + display: none; + position: absolute; + top: 0; + right: 0; + height: 100%; + background-color: transparent; + border: 0; + padding: 0 10px; + + @media (max-width: $screen-xs-max) { + display: block; + } + + &:hover { + color: $gl-text-color; + } + } } .settings-avatar { @@ -89,6 +123,10 @@ $new-sidebar-width: 220px; background-color: $gray-normal; box-shadow: inset -2px 0 0 $border-color; + &.nav-sidebar-expanded { + width: $new-sidebar-width; + } + a { text-decoration: none; } @@ -185,6 +223,25 @@ $new-sidebar-width: 220px; } } +.toggle-mobile-nav { + display: none; + background-color: transparent; + border: 0; + padding: 6px 16px; + margin: 0 16px 0 -15px; + height: 46px; + border-right: 1px solid $gl-text-color-quaternary; + + i { + font-size: 20px; + color: $gl-text-color-secondary; + } + + @media (max-width: $screen-xs-max) { + display: inline-block; + } +} + // Make issue boards full-height now that sub-nav is gone diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index b0c1ab7420f..475d89f1156 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -3,6 +3,8 @@ %nav.breadcrumbs{ role: "navigation" } .breadcrumbs-container{ class: [container_class, @content_class] } + %button.toggle-mobile-nav + = icon ('bars') .breadcrumbs-links.js-title-container - unless hide_top_links .title diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index 7c9822c5a6a..baf257d06e0 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -1,10 +1,13 @@ .nav-sidebar - can_edit = can?(current_user, :admin_project, @project) - = link_to project_path(@project), title: @project.name, class: 'context-header' do - .avatar-container.s40.project-avatar - = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') - .project-title - = @project.name + .context-header + = link_to project_path(@project), title: @project.name do + .avatar-container.s40.project-avatar + = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') + .project-title + = @project.name + %button.close-nav-button + = icon('times') %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do From b4b5ad39932b9f56a16e1d0a8c96f75b32e934fc Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 18 Jul 2017 11:40:42 -0500 Subject: [PATCH 080/130] Make sidebar accessible on mobile --- app/assets/javascripts/new_sidebar.js | 7 ++++--- app/assets/stylesheets/new_sidebar.scss | 19 ++++++++++++++++--- app/views/layouts/_page.html.haml | 2 ++ app/views/layouts/nav/_breadcrumbs.html.haml | 5 +++-- .../layouts/nav/_new_admin_sidebar.html.haml | 11 +++++++---- .../layouts/nav/_new_group_sidebar.html.haml | 13 ++++++++----- .../nav/_new_profile_sidebar.html.haml | 11 +++++++---- 7 files changed, 47 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index 4aadeb6b462..ccf45fd7c97 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -1,5 +1,3 @@ -const SIDEBAR_EXPANDED_CLASS = 'nav-sidebar-expanded'; - export default class NewNavSidebar { constructor() { this.initDomElements(); @@ -8,6 +6,7 @@ export default class NewNavSidebar { initDomElements() { this.$sidebar = $('.nav-sidebar'); + this.$overlay = $('.mobile-overlay'); this.$openSidebar = $('.toggle-mobile-nav'); this.$closeSidebar = $('.close-nav-button'); } @@ -15,9 +14,11 @@ export default class NewNavSidebar { bindEvents() { this.$openSidebar.on('click', e => this.toggleSidebarNav(e, true)); this.$closeSidebar.on('click', e => this.toggleSidebarNav(e, false)); + this.$overlay.on('click', e => this.toggleSidebarNav(e, false)); } toggleSidebarNav(show) { - this.$sidebar.toggleClass(SIDEBAR_EXPANDED_CLASS, show); + this.$sidebar.toggleClass('nav-sidebar-expanded', show); + this.$overlay.toggleClass('mobile-nav-open', show); } } diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index eaf7daaa5cf..0ab5322a7a4 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -115,7 +115,7 @@ $new-sidebar-width: 220px; position: fixed; z-index: 400; width: $new-sidebar-width; - transition: width $sidebar-transition-duration; + transition: left $sidebar-transition-duration; top: 50px; bottom: 0; left: 0; @@ -124,7 +124,7 @@ $new-sidebar-width: 220px; box-shadow: inset -2px 0 0 $border-color; &.nav-sidebar-expanded { - width: $new-sidebar-width; + left: 0; } a { @@ -156,7 +156,7 @@ $new-sidebar-width: 220px; } @media (max-width: $screen-xs-max) { - width: 0; + left: (-$new-sidebar-width); } } @@ -242,6 +242,19 @@ $new-sidebar-width: 220px; } } +.mobile-overlay { + display: none; + + &.mobile-nav-open { + display: block; + position: absolute; + background-color: $black-transparent; + height: 100%; + width: 100%; + z-index: 300; + } +} + // Make issue boards full-height now that sub-nav is gone diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index cc9219cb6fe..7734c1d8be3 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -10,6 +10,8 @@ - if content_for?(:sub_nav) = yield :sub_nav .content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" } + - if show_new_nav? + .mobile-overlay .alert-wrapper = render "layouts/broadcast" - if show_new_nav? diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index 475d89f1156..cef6e022d2b 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -3,8 +3,9 @@ %nav.breadcrumbs{ role: "navigation" } .breadcrumbs-container{ class: [container_class, @content_class] } - %button.toggle-mobile-nav - = icon ('bars') + - if defined?(@new_sidebar) + %button.toggle-mobile-nav + = icon ('bars') .breadcrumbs-links.js-title-container - unless hide_top_links .title diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index d7a9e530983..2b5523f6fad 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -1,8 +1,11 @@ .nav-sidebar - = link_to admin_root_path, title: 'Admin Overview', class: 'context-header' do - .avatar-container.s40.settings-avatar - = icon('wrench') - .project-title Admin Area + .context-header + = link_to admin_root_path, title: 'Admin Overview' do + .avatar-container.s40.settings-avatar + = icon('wrench') + .project-title Admin Area + %button.close-nav-button + = icon('times') %ul.sidebar-top-level-items = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index c80308ed0de..fdb66d827ec 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -1,9 +1,12 @@ .nav-sidebar - = link_to group_path(@group), title: @group.name, class: 'context-header' do - .avatar-container.s40.group-avatar - = image_tag group_icon(@group), class: "avatar s40 avatar-tile" - .group-title - = @group.name + .context-header + = link_to group_path(@group), title: @group.name do + .avatar-container.s40.group-avatar + = image_tag group_icon(@group), class: "avatar s40 avatar-tile" + .group-title + = @group.name + %button.close-nav-button + = icon('times') %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do = link_to group_path(@group), title: 'Home' do diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml index 033ea149cfb..ce4eecc6c79 100644 --- a/app/views/layouts/nav/_new_profile_sidebar.html.haml +++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml @@ -1,8 +1,11 @@ .nav-sidebar - = link_to profile_path, title: 'Profile Settings', class: 'context-header' do - .avatar-container.s40.settings-avatar - = icon('user') - .project-title User Settings + .context-header + = link_to profile_path, title: 'Profile Settings' do + .avatar-container.s40.settings-avatar + = icon('user') + .project-title User Settings + %button.close-nav-button + = icon('times') %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do From e4a3c45e7e138f673958b01840821ae536b9fd87 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 18 Jul 2017 13:43:33 -0500 Subject: [PATCH 081/130] Remove transitions on nav link hover --- app/assets/stylesheets/new_sidebar.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 82cabefa129..bd9a5d7392d 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -65,7 +65,6 @@ $new-sidebar-width: 220px; .settings-avatar { background-color: $white-light; - transition: background-color 100ms linear; i { font-size: 20px; @@ -73,7 +72,6 @@ $new-sidebar-width: 220px; color: $gl-text-color-secondary; text-align: center; align-self: center; - transition: color 100ms linear; } } @@ -90,6 +88,7 @@ $new-sidebar-width: 220px; box-shadow: inset -2px 0 0 $border-color; a { + transition: none; text-decoration: none; } @@ -177,7 +176,6 @@ $new-sidebar-width: 220px; color: $hover-color; .badge { - transition: background-color 100ms linear, color 100ms linear; background-color: $indigo-500; color: $hover-color; } From c9ddfab4df80408d595b88a8f1a0c5584c0f39b2 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Tue, 18 Jul 2017 15:03:29 -0400 Subject: [PATCH 082/130] moves the date to the right place --- app/assets/javascripts/due_date_select.js | 7 ++++--- app/assets/javascripts/lib/utils/datefix.js | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 60da9bff06c..5c0455d35f0 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -45,7 +45,7 @@ class DueDateSelect { initDatePicker() { const $dueDateInput = $(`input[name='${this.fieldName}']`); - + const dateFix = DateFix.dashedFix($dueDateInput.val()); const calendar = new Pikaday({ field: $dueDateInput.get(0), theme: 'gitlab-theme', @@ -63,7 +63,7 @@ class DueDateSelect { } }); - calendar.setDate(DateFix.dashedFix($dueDateInput.val())); + calendar.setDate(dateFix); this.$datePicker.append(calendar.el); this.$datePicker.data('pikaday', calendar); } @@ -169,6 +169,7 @@ class DueDateSelectors { initMilestoneDatePicker() { $('.datepicker').each(function() { const $datePicker = $(this); + const dateFix = DateFix.dashedFix($datePicker.val()); const calendar = new Pikaday({ field: $datePicker.get(0), theme: 'gitlab-theme animate-picker', @@ -179,7 +180,7 @@ class DueDateSelectors { } }); - calendar.setDate(DateFix.dashedFix($datePicker.val())); + calendar.setDate(dateFix); $datePicker.data('pikaday', calendar); }); diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js index d0c81ec854f..bdf0f394d1a 100644 --- a/app/assets/javascripts/lib/utils/datefix.js +++ b/app/assets/javascripts/lib/utils/datefix.js @@ -1,7 +1,6 @@ const DateFix = { dashedFix(val) { const [y, m, d] = val.split('-'); - console.log(y,m,d) return new Date(y, m - 1, d); } } From 0c97f3574a8662230725c2b793c31a80b15cfb7f Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Tue, 18 Jul 2017 15:07:14 -0400 Subject: [PATCH 083/130] Fixes code quality issues --- app/assets/javascripts/due_date_select.js | 2 +- app/assets/javascripts/lib/utils/datefix.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 5c0455d35f0..2856c8e2862 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -2,7 +2,7 @@ /* global dateFormat */ /* global Pikaday */ -import DateFix from './lib/utils/datefix' +import DateFix from './lib/utils/datefix'; class DueDateSelect { constructor({ $dropdown, $loading } = {}) { diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js index bdf0f394d1a..289d93c1f1a 100644 --- a/app/assets/javascripts/lib/utils/datefix.js +++ b/app/assets/javascripts/lib/utils/datefix.js @@ -5,4 +5,4 @@ const DateFix = { } } -export default DateFix; \ No newline at end of file +export default DateFix; From 90f8feae46ca49299dbe138a229a51c5294f88be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Fri, 23 Jun 2017 17:33:16 -0400 Subject: [PATCH 084/130] Adapt to new Gitaly commit message format --- lib/gitlab/git/commit.rb | 20 ++++++++++++++ lib/gitlab/gitaly_client/ref_service.rb | 8 +++--- spec/lib/gitlab/git/commit_spec.rb | 35 +++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index d0f04d25db2..1b9f3c57957 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -210,6 +210,8 @@ module Gitlab init_from_hash(raw_commit) elsif raw_commit.is_a?(Rugged::Commit) init_from_rugged(raw_commit) + elsif raw_commit.is_a?(Gitaly::GitCommit) + init_from_gitaly(raw_commit) else raise "Invalid raw commit type: #{raw_commit.class}" end @@ -371,6 +373,24 @@ module Gitlab @parent_ids = commit.parents.map(&:oid) end + def init_from_gitaly(commit) + @raw_commit = commit + @id = commit.id + # NOTE: For ease of parsing in Gitaly, we have only the subject of + # the commit and not the full message. + # TODO: Once gitaly "takes over" Rugged consider separating the + # subject from the message to make it clearer when there's one + # available but not the other. + @message = commit.subject.dup + @authored_date = Time.at(commit.author.date.seconds) + @author_name = commit.author.name.dup + @author_email = commit.author.email.dup + @committed_date = Time.at(commit.committer.date.seconds) + @committer_name = commit.committer.name.dup + @committer_email = commit.committer.email.dup + @parent_ids = commit.parent_ids + end + def serialize_keys SERIALIZE_KEYS end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index f541887843d..2c3d53410ac 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -96,11 +96,11 @@ module Gitlab id: response.commit_id, message: message, authored_date: Time.at(response.commit_author.date.seconds), - author_name: response.commit_author.name, - author_email: response.commit_author.email, + author_name: response.commit_author.name.dup, + author_email: response.commit_author.email.dup, committed_date: Time.at(response.commit_committer.date.seconds), - committer_name: response.commit_committer.name, - committer_email: response.commit_committer.email + committer_name: response.commit_committer.name.dup, + committer_email: response.commit_committer.email.dup } Gitlab::Git::Commit.decorate(hash) diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index f20a14155dc..62b353890d6 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -64,6 +64,41 @@ describe Gitlab::Git::Commit, seed_helper: true do end end + describe "Commit info from gitaly commit" do + let(:id) { 'f00' } + let(:subject) { "My commit".force_encoding('ASCII-8BIT') } + let(:committer) do + Gitaly::CommitAuthor.new( + name: generate(:name), + email: generate(:email), + date: Google::Protobuf::Timestamp.new(seconds: 123) + ) + end + let(:author) do + Gitaly::CommitAuthor.new( + name: generate(:name), + email: generate(:email), + date: Google::Protobuf::Timestamp.new(seconds: 456) + ) + end + let(:gitaly_commit) do + Gitaly::GitCommit.new( + id: id, subject: subject, author: author, committer: committer + ) + end + let(:commit) { described_class.new(gitaly_commit) } + + it { expect(commit.short_id).to eq(id[0..10]) } + it { expect(commit.id).to eq(id) } + it { expect(commit.sha).to eq(id) } + it { expect(commit.safe_message).to eq(subject) } + it { expect(commit.created_at).to eq(Time.at(committer.date.seconds)) } + it { expect(commit.author_email).to eq(author.email) } + it { expect(commit.author_name).to eq(author.name) } + it { expect(commit.committer_name).to eq(committer.name) } + it { expect(commit.committer_email).to eq(committer.email) } + end + context 'Class methods' do describe '.find' do it "should return first head commit if without params" do From 25b01b4c8594e8260dd1c86523bb9989aefd1fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Fri, 23 Jun 2017 17:52:51 -0400 Subject: [PATCH 085/130] Incorporate Gitaly's Commits#between RPC --- lib/gitlab/git/commit.rb | 14 ++++++++++---- lib/gitlab/git/repository.rb | 16 ++++++++-------- lib/gitlab/gitaly_client/commit_service.rb | 15 +++++++++++++++ spec/lib/gitlab/git/commit_spec.rb | 15 +++++++++++++-- .../gitlab/gitaly_client/commit_service_spec.rb | 16 +++++++++++++++- spec/services/git_push_service_spec.rb | 4 ++-- 6 files changed, 63 insertions(+), 17 deletions(-) diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 1b9f3c57957..76a562f356e 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -98,7 +98,15 @@ module Gitlab # Commit.between(repo, '29eda46b', 'master') # def between(repo, base, head) - repo.commits_between(base, head).map do |commit| + commits = Gitlab::GitalyClient.migrate(:commits_between) do |is_enabled| + if is_enabled + repo.gitaly_commit_client.between(base, head) + else + repo.commits_between(base, head) + end + end + + commits.map do |commit| decorate(commit) end rescue Rugged::ReferenceError @@ -376,12 +384,10 @@ module Gitlab def init_from_gitaly(commit) @raw_commit = commit @id = commit.id - # NOTE: For ease of parsing in Gitaly, we have only the subject of - # the commit and not the full message. # TODO: Once gitaly "takes over" Rugged consider separating the # subject from the message to make it clearer when there's one # available but not the other. - @message = commit.subject.dup + @message = (commit.body.presence || commit.subject).dup @authored_date = Time.at(commit.author.date.seconds) @author_name = commit.author.name.dup @author_email = commit.author.email.dup diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index c091bb9dcfe..63eebadff2e 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -807,6 +807,14 @@ module Gitlab Gitlab::GitalyClient::Util.repository(@storage, @relative_path) end + def gitaly_ref_client + @gitaly_ref_client ||= Gitlab::GitalyClient::RefService.new(self) + end + + def gitaly_commit_client + @gitaly_commit_client ||= Gitlab::GitalyClient::CommitService.new(self) + end + private # Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'. @@ -1105,14 +1113,6 @@ module Gitlab end end - def gitaly_ref_client - @gitaly_ref_client ||= Gitlab::GitalyClient::RefService.new(self) - end - - def gitaly_commit_client - @gitaly_commit_client ||= Gitlab::GitalyClient::CommitService.new(self) - end - def gitaly_migrate(method, &block) Gitlab::GitalyClient.migrate(method, &block) rescue GRPC::NotFound => e diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 470e3ac8779..8f5738fed06 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -65,6 +65,17 @@ module Gitlab GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count end + def between(from, to) + request = Gitaly::CommitsBetweenRequest.new( + repository: @gitaly_repo, + from: from, + to: to + ) + + response = GitalyClient.call(@repository.storage, :commit_service, :commits_between, request) + consume_commits_response(response) + end + private def commit_diff_request_params(commit, options = {}) @@ -77,6 +88,10 @@ module Gitlab paths: options.fetch(:paths, []) } end + + def consume_commits_response(response) + response.flat_map { |r| r.commits } + end end end end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 62b353890d6..60de91324f0 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -67,6 +67,7 @@ describe Gitlab::Git::Commit, seed_helper: true do describe "Commit info from gitaly commit" do let(:id) { 'f00' } let(:subject) { "My commit".force_encoding('ASCII-8BIT') } + let(:body) { subject + "My body".force_encoding('ASCII-8BIT') } let(:committer) do Gitaly::CommitAuthor.new( name: generate(:name), @@ -83,7 +84,11 @@ describe Gitlab::Git::Commit, seed_helper: true do end let(:gitaly_commit) do Gitaly::GitCommit.new( - id: id, subject: subject, author: author, committer: committer + id: id, + subject: subject, + body: body, + author: author, + committer: committer ) end let(:commit) { described_class.new(gitaly_commit) } @@ -91,12 +96,18 @@ describe Gitlab::Git::Commit, seed_helper: true do it { expect(commit.short_id).to eq(id[0..10]) } it { expect(commit.id).to eq(id) } it { expect(commit.sha).to eq(id) } - it { expect(commit.safe_message).to eq(subject) } + it { expect(commit.safe_message).to eq(body) } it { expect(commit.created_at).to eq(Time.at(committer.date.seconds)) } it { expect(commit.author_email).to eq(author.email) } it { expect(commit.author_name).to eq(author.name) } it { expect(commit.committer_name).to eq(committer.name) } it { expect(commit.committer_email).to eq(committer.email) } + + context 'no body' do + let(:body) { "".force_encoding('ASCII-8BIT') } + + it { expect(commit.safe_message).to eq(subject) } + end end context 'Class methods' do diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index fee5bb45fe5..93affb12f2b 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Gitlab::GitalyClient::CommitService do - let(:diff_stub) { double('Gitaly::DiffService::Stub') } let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:repository_message) { repository.gitaly_repository } @@ -82,4 +81,19 @@ describe Gitlab::GitalyClient::CommitService do end end end + + describe '#between' do + let(:from) { 'master' } + let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } + it 'sends an RPC request' do + request = Gitaly::CommitsBetweenRequest.new( + repository: repository_message, from: from, to: to + ) + + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:commits_between) + .with(request, kind_of(Hash)).and_return([]) + + described_class.new(repository).between(from, to) + end + end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 3f77ed10069..c493c08a7ae 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -108,7 +108,7 @@ describe GitPushService, services: true do it { is_expected.to include(id: @commit.id) } it { is_expected.to include(message: @commit.safe_message) } - it { is_expected.to include(timestamp: @commit.date.xmlschema) } + it { expect(subject[:timestamp].in_time_zone).to eq(@commit.date.in_time_zone) } it do is_expected.to include( url: [ @@ -163,7 +163,7 @@ describe GitPushService, services: true do execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end end - + context "Sends System Push data" do it "when pushing on a branch" do expect(SystemHookPushWorker).to receive(:perform_async).with(@push_data, :push_hooks) From b92709eaf4b513d4cf86c6cef0598b9cd1f45f13 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 18 Jul 2017 16:58:05 -0400 Subject: [PATCH 086/130] Remove developer documentation about not describing symbols This is now covered by rubocop-rspec. --- doc/development/gotchas.md | 29 ----------------------------- doc/development/testing.md | 1 - 2 files changed, 30 deletions(-) diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md index 565d4b33457..c2ca8966a3f 100644 --- a/doc/development/gotchas.md +++ b/doc/development/gotchas.md @@ -3,35 +3,6 @@ The purpose of this guide is to document potential "gotchas" that contributors might encounter or should avoid during development of GitLab CE and EE. -## Do not `describe` symbols - -Consider the following model spec: - -```ruby -require 'rails_helper' - -describe User do - describe :to_param do - it 'converts the username to a param' do - user = described_class.new(username: 'John Smith') - - expect(user.to_param).to eq 'john-smith' - end - end -end -``` - -When run, this spec doesn't do what we might expect: - -```sh -spec/models/user_spec.rb|6 error| Failure/Error: u = described_class.new NoMethodError: undefined method `new' for :to_param:Symbol -``` - -### Solution - -Except for the top-level `describe` block, always provide a String argument to -`describe`. - ## Do not assert against the absolute value of a sequence-generated attribute Consider the following factory: diff --git a/doc/development/testing.md b/doc/development/testing.md index fc84932354b..e6aa4ae8f2f 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -195,7 +195,6 @@ Please consult the [dedicated "Frontend testing" guide](./fe_guide/testing.md). - Use `context` to test branching logic. - Use multi-line `do...end` blocks for `before` and `after`, even when it would fit on a single line. -- Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). - Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). - Don't supply the `:each` argument to hooks since it's the default. - Prefer `not_to` to `to_not` (_this is enforced by RuboCop_). From 49ee81ca12e46a703e999632efcb9f2a5627c930 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 18 Jul 2017 16:40:50 -0500 Subject: [PATCH 087/130] Fix eslint --- app/assets/javascripts/layout_nav.js | 3 ++- app/assets/javascripts/new_sidebar.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index e8f59f30035..1a24c7a6433 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -54,7 +54,8 @@ import NewNavSidebar from './new_sidebar'; } $(() => { - new NewNavSidebar(); + var newNavSidebar = new NewNavSidebar(); + newNavSidebar.bindEvents(); $(window).on('scroll', _.throttle(applyScrollNavClass, 100)); }); }).call(window); diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index ccf45fd7c97..2ab8d764a1d 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -1,7 +1,6 @@ export default class NewNavSidebar { constructor() { this.initDomElements(); - this.bindEvents(); } initDomElements() { From 2abb40f12a9442f0ac8ba8ba1fa0512d8c07eb4e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 18 Jul 2017 17:20:10 -0500 Subject: [PATCH 088/130] Update CHANGELOG.md for 9.3.7 [ci skip] --- CHANGELOG.md | 5 +++++ .../unreleased/34325-reinstate-is_admin-for-user-api.yml | 4 ---- ...34728-fix-application-setting-created-when-redis-down.yml | 4 ---- 3 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 changelogs/unreleased/34325-reinstate-is_admin-for-user-api.yml delete mode 100644 changelogs/unreleased/34728-fix-application-setting-created-when-redis-down.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index c181aba0205..b6f955553e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 9.3.7 (2017-07-18) + +- Prevent bad data being added to application settings when Redis is unavailable. !12750 +- Return `is_admin` attribute in the GET /user endpoint for admins. !12811 + ## 9.3.6 (2017-07-12) - Fix API Scoping. !12300 diff --git a/changelogs/unreleased/34325-reinstate-is_admin-for-user-api.yml b/changelogs/unreleased/34325-reinstate-is_admin-for-user-api.yml deleted file mode 100644 index 3bed1fbe16e..00000000000 --- a/changelogs/unreleased/34325-reinstate-is_admin-for-user-api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Return `is_admin` attribute in the GET /user endpoint for admins -merge_request: 12811 -author: diff --git a/changelogs/unreleased/34728-fix-application-setting-created-when-redis-down.yml b/changelogs/unreleased/34728-fix-application-setting-created-when-redis-down.yml deleted file mode 100644 index 4fddabebf36..00000000000 --- a/changelogs/unreleased/34728-fix-application-setting-created-when-redis-down.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Prevent bad data being added to application settings when Redis is unavailable -merge_request: 12750 -author: From 9a303fb7e889783c347778e67e2f6d15a330f418 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Tue, 18 Jul 2017 18:28:05 -0400 Subject: [PATCH 089/130] Fixes eslint bugs. --- app/assets/javascripts/lib/utils/datefix.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js index 289d93c1f1a..990dc3f6d1a 100644 --- a/app/assets/javascripts/lib/utils/datefix.js +++ b/app/assets/javascripts/lib/utils/datefix.js @@ -2,7 +2,7 @@ const DateFix = { dashedFix(val) { const [y, m, d] = val.split('-'); return new Date(y, m - 1, d); - } -} + }, +}; export default DateFix; From 309d40664582cacac5bb6b4060b868c184a7dc8a Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Tue, 18 Jul 2017 23:57:42 +0000 Subject: [PATCH 090/130] Simplify width for dropdown-menu on mobile --- app/assets/stylesheets/framework/dropdowns.scss | 11 +---------- app/assets/stylesheets/framework/nav.scss | 2 +- app/views/layouts/nav/_dashboard.html.haml | 4 ++-- .../34468-remove-extra-blank-on-admin-on-mobile.yml | 4 ++++ 4 files changed, 8 insertions(+), 13 deletions(-) create mode 100644 changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index fbf49f3a813..5e410cbf563 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -205,6 +205,7 @@ @media (max-width: $screen-sm-min) { width: 100%; + min-width: 180px; } &.dropdown-open-left { @@ -288,12 +289,6 @@ padding: 5px 8px; color: $gl-text-color-secondary; } - - .badge { - position: absolute; - right: 8px; - top: 5px; - } } .droplab-dropdown { @@ -466,10 +461,6 @@ left: auto; right: 0; margin-top: -5px; - - @media (max-width: $screen-xs-max) { - left: 0; - } } .dropdown-menu-selectable { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 28b2a7cfacd..e71bf04aec7 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -325,7 +325,7 @@ position: absolute; top: 7px; right: 15px; - z-index: 2; + z-index: 300; li.active { font-weight: bold; diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index ac222ad8c82..be7d27df2a0 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -42,18 +42,18 @@ .key = icon('arrow-up', 'aria-label' => 'hidden') I + %span.badge.pull-right= number_with_delimiter(assigned_issuables_count(:issues)) %span Issues - .badge= number_with_delimiter(assigned_issuables_count(:issues)) = nav_link(path: 'dashboard#merge_requests') do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do .shortcut-mappings .key = icon('arrow-up', 'aria-label' => 'hidden') M + %span.badge.pull-right= number_with_delimiter(assigned_issuables_count(:merge_requests)) %span Merge Requests - .badge= number_with_delimiter(assigned_issuables_count(:merge_requests)) = nav_link(controller: 'dashboard/snippets') do = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do .shortcut-mappings diff --git a/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml b/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml new file mode 100644 index 00000000000..99291b4c75a --- /dev/null +++ b/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml @@ -0,0 +1,4 @@ +--- +title: Use smaller min-width for dropdown-menu-nav only on mobile +merge_request: 12528 +author: Takuya Noguchi From afba21f6a0a07a59bbe2aed2b25d6a50f8dc7e2e Mon Sep 17 00:00:00 2001 From: Ben Kochie Date: Wed, 19 Jul 2017 09:43:57 +0200 Subject: [PATCH 091/130] Update piplines created metric name * Use `_total` naming convention. * Add to metrics documentation. --- app/services/ci/create_pipeline_service.rb | 2 +- doc/administration/monitoring/prometheus/gitlab_metrics.md | 1 + spec/services/ci/create_pipeline_service_spec.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 4f35255fb53..273386776fa 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -135,7 +135,7 @@ module Ci end def pipeline_created_counter - @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_count, "Pipelines created count") + @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_total, "Counter of pipelines created") end end end diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 7c5505de8a2..6023112d615 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -39,6 +39,7 @@ In this experimental phase, only a few metrics are available: | filesystem_readable | Gauge | Whether or not the filesystem is readable | | http_requests_total | Counter | Rack request count | | http_request_duration_seconds | Histogram | HTTP response time from rack middleware | +| pipelines_created_total | Counter | Counter of pipelines created | | rack_uncaught_errors_total | Counter | Rack connections handling uncaught errors count | | redis_ping_timeout | Gauge | Whether or not the last redis ping timed out | | redis_ping_success | Gauge | Whether or not the last redis ping succeeded | diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 77c07b71c68..e71c462b99a 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -40,7 +40,7 @@ describe Ci::CreatePipelineService, :services do it 'increments the prometheus counter' do expect(Gitlab::Metrics).to receive(:counter) - .with(:pipelines_created_count, "Pipelines created count") + .with(:pipelines_created_total, "Counter of pipelines created") .and_call_original pipeline From 9f36012e026845e87c39d5f142abbaa44bde8f2b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 19 Jul 2017 10:30:57 +0200 Subject: [PATCH 092/130] Fix docker tag reference routing constraints --- config/routes/project.rb | 2 +- lib/gitlab/regex.rb | 12 +++-- .../projects/registry/tags_controller_spec.rb | 48 +++++++++++++++++++ spec/routing/project_routing_spec.rb | 22 +++++++++ 4 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 spec/controllers/projects/registry/tags_controller_spec.rb diff --git a/config/routes/project.rb b/config/routes/project.rb index 62cab25c763..672b5a9a160 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -272,7 +272,7 @@ constraints(ProjectUrlConstrainer.new) do namespace :registry do resources :repository, only: [] do resources :tags, only: [:destroy], - constraints: { id: Gitlab::Regex.container_registry_reference_regex } + constraints: { id: Gitlab::Regex.container_registry_tag_regex } end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index c1ee20b6977..4b3666b3e46 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -19,17 +19,19 @@ module Gitlab "It must start with letter, digit, emoji or '_'." end - def container_registry_reference_regex - Gitlab::PathRegex.git_reference_regex - end - ## - # Docker Distribution Registry 2.4.1 repository name rules + # Docker Distribution Registry repository / tag name rules + # + # See https://github.com/docker/distribution/blob/master/reference/regexp.go. # def container_repository_name_regex @container_repository_regex ||= %r{\A[a-z0-9]+(?:[-._/][a-z0-9]+)*\Z} end + def container_registry_tag_regex + @container_registry_tag_regex ||= /[\w][\w.-]{0,127}/ + end + def environment_name_regex_chars 'a-zA-Z0-9_/\\$\\{\\}\\. -' end diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb new file mode 100644 index 00000000000..a823516830e --- /dev/null +++ b/spec/controllers/projects/registry/tags_controller_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Projects::Registry::TagsController do + let(:user) { create(:user) } + let(:project) { create(:empty_project, :private) } + + before do + sign_in(user) + stub_container_registry_config(enabled: true) + end + + context 'when user has access to registry' do + before do + project.add_developer(user) + end + + describe 'POST destroy' do + context 'when there is matching tag present' do + before do + stub_container_registry_tags(repository: /image/, tags: %w[rc1 test.]) + end + + let(:repository) do + create(:container_repository, name: 'image', project: project) + end + + it 'makes it possible to delete regular tag' do + expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete) + + destroy_tag('rc1') + end + + it 'makes it possible to delete a tag that ends with a dot' do + expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete) + + destroy_tag('test.') + end + end + end + end + + def destroy_tag(name) + post :destroy, namespace_id: project.namespace, + project_id: project, + repository_id: repository, + id: name + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 2f1c3c95e59..65314b688a4 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -609,4 +609,26 @@ describe 'project routing' do expect(get('/gitlab/gitlabhq/pages/domains/my.domain.com')).to route_to('projects/pages_domains#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'my.domain.com') end end + + describe Projects::Registry::TagsController, :routing do + describe '#destroy' do + it 'correctly routes to a destroy action' do + expect(delete('/gitlab/gitlabhq/registry/repository/1/tags/rc1')) + .to route_to('projects/registry/tags#destroy', + namespace_id: 'gitlab', + project_id: 'gitlabhq', + repository_id: '1', + id: 'rc1') + end + + it 'takes registry tag name constrains into account' do + expect(delete('/gitlab/gitlabhq/registry/repository/1/tags/-rc1')) + .not_to route_to('projects/registry/tags#destroy', + namespace_id: 'gitlab', + project_id: 'gitlabhq', + repository_id: '1', + id: '-rc1') + end + end + end end From 59c808a636b74e9ddaae33dc1236af9b569e027f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 19 Jul 2017 10:52:03 +0200 Subject: [PATCH 093/130] Add a test for container repository name regexp --- lib/gitlab/regex.rb | 4 ++++ spec/lib/gitlab/regex_spec.rb | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 4b3666b3e46..1adc5ec952a 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -28,6 +28,10 @@ module Gitlab @container_repository_regex ||= %r{\A[a-z0-9]+(?:[-._/][a-z0-9]+)*\Z} end + ## + # We do not use regexp anchors here because these are not allowed when + # used as a routing constraint. + # def container_registry_tag_regex @container_registry_tag_regex ||= /[\w][\w.-]{0,127}/ end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 51e2c3c38c6..251f82849bf 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -38,4 +38,15 @@ describe Gitlab::Regex, lib: true do it { is_expected.not_to match('9foo') } it { is_expected.not_to match('foo-') } end + + describe '.container_repository_name_regex' do + subject { described_class.container_repository_name_regex } + + it { is_expected.to match('image') } + it { is_expected.to match('my/image') } + it { is_expected.to match('my/awesome/image-1') } + it { is_expected.to match('my/awesome/image.test') } + it { is_expected.not_to match('.my/image') } + it { is_expected.not_to match('my/image.') } + end end From fe359ec760b104e6b1d5d1a5f87e8e954b18f8d9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 19 Jul 2017 10:52:50 +0200 Subject: [PATCH 094/130] Add a changelog entry for docker tag routing fix --- .../unreleased/fix-gb-fix-container-registry-tag-routing.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-gb-fix-container-registry-tag-routing.yml diff --git a/changelogs/unreleased/fix-gb-fix-container-registry-tag-routing.yml b/changelogs/unreleased/fix-gb-fix-container-registry-tag-routing.yml new file mode 100644 index 00000000000..5a2644d14a7 --- /dev/null +++ b/changelogs/unreleased/fix-gb-fix-container-registry-tag-routing.yml @@ -0,0 +1,4 @@ +--- +title: Fix docker tag reference routing constraints +merge_request: 12961 +author: From 2c3d52161af2f170bdb644b96b6ffe5da0c1df10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Wed, 19 Jul 2017 08:54:39 +0000 Subject: [PATCH 095/130] Update Prometheus gem to version that explicitly calls `munmap` --- Gemfile | 2 +- Gemfile.lock | 4 ++-- app/services/metrics_service.rb | 2 +- config/boot.rb | 5 ----- config/initializers/7_prometheus_metrics.rb | 12 ++++++++++++ lib/gitlab/metrics/prometheus.rb | 8 +++++--- spec/controllers/metrics_controller_spec.rb | 2 +- spec/spec_helper.rb | 1 - 8 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 config/initializers/7_prometheus_metrics.rb diff --git a/Gemfile b/Gemfile index b9c37e2b3fa..0d6b38897ef 100644 --- a/Gemfile +++ b/Gemfile @@ -281,7 +281,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~>0.7.0.beta5' + gem 'prometheus-client-mmap', '~>0.7.0.beta9' gem 'raindrops', '~> 0.18' end diff --git a/Gemfile.lock b/Gemfile.lock index 63eb0712ef3..69e4c4416ba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -592,7 +592,7 @@ GEM premailer-rails (1.9.7) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) - prometheus-client-mmap (0.7.0.beta8) + prometheus-client-mmap (0.7.0.beta9) mmap2 (~> 2.2, >= 2.2.7) pry (0.10.4) coderay (~> 1.1.0) @@ -1042,7 +1042,7 @@ DEPENDENCIES pg (~> 0.18.2) poltergeist (~> 1.9.0) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.7.0.beta5) + prometheus-client-mmap (~> 0.7.0.beta9) pry-byebug (~> 3.4.1) pry-rails (~> 0.3.4) rack-attack (~> 4.4.1) diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index c92f070601c..a02eee4961b 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -31,6 +31,6 @@ class MetricsService end def multiprocess_metrics_path - @multiprocess_metrics_path ||= Rails.root.join(ENV['prometheus_multiproc_dir']).freeze + ::Prometheus::Client.configuration.multiprocess_files_dir end end diff --git a/config/boot.rb b/config/boot.rb index 2d01092acd5..f2830ae3166 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -4,8 +4,3 @@ require 'rubygems' ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) - -# set default directory for multiproces metrics gathering -if ENV['RAILS_ENV'] == 'development' || ENV['RAILS_ENV'] == 'test' - ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir' -end diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb new file mode 100644 index 00000000000..987324a86c9 --- /dev/null +++ b/config/initializers/7_prometheus_metrics.rb @@ -0,0 +1,12 @@ +require 'prometheus/client' + +Prometheus::Client.configure do |config| + config.logger = Rails.logger + + config.initial_mmap_file_size = 4 * 1024 + config.multiprocess_files_dir = ENV['prometheus_multiproc_dir'] + + if Rails.env.development? && Rails.env.test? + config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir') + end +end diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb index fb7bbc7cfc7..460dab47276 100644 --- a/lib/gitlab/metrics/prometheus.rb +++ b/lib/gitlab/metrics/prometheus.rb @@ -6,9 +6,11 @@ module Gitlab include Gitlab::CurrentSettings def metrics_folder_present? - ENV.has_key?('prometheus_multiproc_dir') && - ::Dir.exist?(ENV['prometheus_multiproc_dir']) && - ::File.writable?(ENV['prometheus_multiproc_dir']) + multiprocess_files_dir = ::Prometheus::Client.configuration.multiprocess_files_dir + + multiprocess_files_dir && + ::Dir.exist?(multiprocess_files_dir) && + ::File.writable?(multiprocess_files_dir) end def prometheus_metrics_enabled? diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 8964d89b438..7b0976e3e67 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -12,7 +12,7 @@ describe MetricsController do before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - stub_env('prometheus_multiproc_dir', metrics_multiproc_dir) + allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir) allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(true) allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip, whitelisted_ip_range]) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b8ed1e18de0..5d5715b10ff 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,7 +3,6 @@ SimpleCovEnv.start! ENV["RAILS_ENV"] ||= 'test' ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true' -# ENV['prometheus_multiproc_dir'] = 'tmp/prometheus_multiproc_dir_test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' From f49a906a5f805d44ce2b4c6c01e58a38a606761b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 19 Jul 2017 11:02:21 +0200 Subject: [PATCH 096/130] Update changelog entry for a auto-retry of CI/CD jobs --- changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml b/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml index abde2397331..bdafc5929c0 100644 --- a/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml +++ b/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml @@ -1,4 +1,4 @@ --- -title: Make it possible to configure auto-retry of CI/CD job +title: Allow to configure automatic retry of a failed CI/CD job merge_request: 12909 author: From c57ae83dcfaeff9c358b5f896202e04a86ad6ef3 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 11 Jul 2017 13:19:43 +0100 Subject: [PATCH 097/130] Fix issuable state counter cache keys These cache a hash of counts by state, so the state isn't needed in the key itself. --- app/finders/issuable_finder.rb | 9 ++++----- app/finders/issues_finder.rb | 2 +- app/helpers/issuables_helper.rb | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 2e5a6493134..1585c11ab3a 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -20,7 +20,7 @@ # class IssuableFinder include CreatedAtFilter - + NONE = '0'.freeze IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze @@ -89,8 +89,8 @@ class IssuableFinder execute.find_by!(*params) end - def state_counter_cache_key(state) - Digest::SHA1.hexdigest(state_counter_cache_key_components(state).flatten.join('-')) + def state_counter_cache_key + Digest::SHA1.hexdigest(state_counter_cache_key_components.flatten.join('-')) end def group @@ -417,9 +417,8 @@ class IssuableFinder params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' end - def state_counter_cache_key_components(state) + def state_counter_cache_key_components opts = params.with_indifferent_access - opts[:state] = state opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) opts.delete_if { |_, value| value.blank? } diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 85230ff1293..295a64ef5b8 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -75,7 +75,7 @@ class IssuesFinder < IssuableFinder current_user.blank? || for_counting || params[:for_counting] end - def state_counter_cache_key_components(state) + def state_counter_cache_key_components extra_components = [ user_can_see_all_confidential_issues?, user_cannot_see_confidential_issues?(for_counting: true) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index d0c518f81f7..425af547330 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -235,7 +235,7 @@ module IssuablesHelper def issuables_count_for_state(issuable_type, state, finder: nil) finder ||= public_send("#{issuable_type}_finder") - cache_key = finder.state_counter_cache_key(state) + cache_key = finder.state_counter_cache_key @counts ||= {} @counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do From b3a588bccaaa078a81e8ce322d75ee167f642e13 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 11 Jul 2017 17:11:41 +0100 Subject: [PATCH 098/130] Fix issuable state caching We were including controller params in the cache key, so the key for the header didn't match the one for the list itself! --- app/controllers/concerns/issuable_collections.rb | 8 ++------ app/finders/issuable_finder.rb | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index e18778cdf80..b43b2c5621f 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -32,10 +32,10 @@ module IssuableCollections def filter_params set_sort_order_from_cookie - set_default_scope set_default_state - @filter_params = params.dup + # Skip irrelevant Rails routing params + @filter_params = params.dup.except(:controller, :action, :namespace_id) @filter_params[:sort] ||= default_sort_order @sort = @filter_params[:sort] @@ -55,10 +55,6 @@ module IssuableCollections @filter_params end - def set_default_scope - params[:scope] = 'all' if params[:scope].blank? - end - def set_default_state params[:state] = 'opened' if params[:state].blank? end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 1585c11ab3a..d3010eff262 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -22,7 +22,7 @@ class IssuableFinder include CreatedAtFilter NONE = '0'.freeze - IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze + IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page state].freeze attr_accessor :current_user, :params From 0e488ef70ab2608847423201e957693745f1e3b1 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 11 Jul 2017 17:12:33 +0100 Subject: [PATCH 099/130] Clear issuable counter caches on update When an issuable's state changes, or one is created, we should clear the cache counts for a user's assigned issuables, and also the project-wide caches for this user type. --- app/finders/issuable_finder.rb | 16 ++- app/finders/issues_finder.rb | 10 ++ app/services/boards/issues/list_service.rb | 5 - app/services/issuable_base_service.rb | 23 ++- app/services/issues/close_service.rb | 2 +- app/services/issues/reopen_service.rb | 2 +- app/services/merge_requests/close_service.rb | 2 +- .../merge_requests/post_merge_service.rb | 2 +- app/services/merge_requests/reopen_service.rb | 2 +- spec/features/dashboard/issues_spec.rb | 2 +- .../projects/issuable_counts_caching_spec.rb | 132 ++++++++++++++++++ 11 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 spec/features/projects/issuable_counts_caching_spec.rb diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index d3010eff262..fc63e30c8fb 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -90,7 +90,13 @@ class IssuableFinder end def state_counter_cache_key - Digest::SHA1.hexdigest(state_counter_cache_key_components.flatten.join('-')) + cache_key(state_counter_cache_key_components) + end + + def clear_caches! + state_counter_cache_key_components_permutations.each do |components| + Rails.cache.delete(cache_key(components)) + end end def group @@ -424,4 +430,12 @@ class IssuableFinder ['issuables_count', klass.to_ability_name, opts.sort] end + + def state_counter_cache_key_components_permutations + [state_counter_cache_key_components] + end + + def cache_key(components) + Digest::SHA1.hexdigest(components.flatten.join('-')) + end end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 295a64ef5b8..0ec42a4e6eb 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -84,6 +84,16 @@ class IssuesFinder < IssuableFinder super + extra_components end + def state_counter_cache_key_components_permutations + # Ignore the last two, as we'll provide both options for them. + components = super.first[0..-3] + + [ + components + [false, true], + components + [true, false] + ] + end + def by_assignee(items) if assignee items.assigned_to(assignee) diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index a1d67cbc244..eb345fead2d 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -33,17 +33,12 @@ module Boards end def filter_params - set_default_scope set_project set_state params end - def set_default_scope - params[:scope] = 'all' - end - def set_project params[:project_id] = project.id end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index a03a7abfeb1..9078b1f0983 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -183,7 +183,7 @@ class IssuableBaseService < BaseService after_create(issuable) issuable.create_cross_references!(current_user) execute_hooks(issuable) - invalidate_cache_counts(issuable.assignees, issuable) + invalidate_cache_counts(issuable, users: issuable.assignees) end issuable @@ -240,12 +240,12 @@ class IssuableBaseService < BaseService old_assignees: old_assignees ) - if old_assignees != issuable.assignees - new_assignees = issuable.assignees.to_a - affected_assignees = (old_assignees + new_assignees) - (old_assignees & new_assignees) - invalidate_cache_counts(affected_assignees.compact, issuable) - end + new_assignees = issuable.assignees.to_a + affected_assignees = (old_assignees + new_assignees) - (old_assignees & new_assignees) + # Don't clear the project cache, because it will be handled by the + # appropriate service (close / reopen / merge / etc.). + invalidate_cache_counts(issuable, users: affected_assignees.compact, skip_project_cache: true) after_update(issuable) issuable.create_new_cross_references!(current_user) execute_hooks(issuable, 'update') @@ -339,9 +339,18 @@ class IssuableBaseService < BaseService create_labels_note(issuable, old_labels) if issuable.labels != old_labels end - def invalidate_cache_counts(users, issuable) + def invalidate_cache_counts(issuable, users: [], skip_project_cache: false) users.each do |user| user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") end + + unless skip_project_cache + case issuable + when Issue + IssuesFinder.new(nil, project_id: issuable.project_id).clear_caches! + when MergeRequest + MergeRequestsFinder.new(nil, project_id: issuable.target_project_id).clear_caches! + end + end end end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index 85c616ca576..ddef5281498 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -28,7 +28,7 @@ module Issues notification_service.close_issue(issue, current_user) if notifications todo_service.close_issue(issue, current_user) execute_hooks(issue, 'close') - invalidate_cache_counts(issue.assignees, issue) + invalidate_cache_counts(issue, users: issue.assignees) end issue diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb index 80ea6312768..73b2e85cba3 100644 --- a/app/services/issues/reopen_service.rb +++ b/app/services/issues/reopen_service.rb @@ -8,7 +8,7 @@ module Issues create_note(issue) notification_service.reopen_issue(issue, current_user) execute_hooks(issue, 'reopen') - invalidate_cache_counts(issue.assignees, issue) + invalidate_cache_counts(issue, users: issue.assignees) end issue diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index 2ffc989ed71..c0ce01f7523 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -13,7 +13,7 @@ module MergeRequests notification_service.close_mr(merge_request, current_user) todo_service.close_merge_request(merge_request, current_user) execute_hooks(merge_request, 'close') - invalidate_cache_counts(merge_request.assignees, merge_request) + invalidate_cache_counts(merge_request, users: merge_request.assignees) end merge_request diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index f0d998731d7..261a8bfa200 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -13,7 +13,7 @@ module MergeRequests create_note(merge_request) notification_service.merge_mr(merge_request, current_user) execute_hooks(merge_request, 'merge') - invalidate_cache_counts(merge_request.assignees, merge_request) + invalidate_cache_counts(merge_request, users: merge_request.assignees) end private diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index f2fddf7f345..52f6d511f98 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -10,7 +10,7 @@ module MergeRequests execute_hooks(merge_request, 'reopen') merge_request.reload_diff(current_user) merge_request.mark_as_unchecked - invalidate_cache_counts(merge_request.assignees, merge_request) + invalidate_cache_counts(merge_request, users: merge_request.assignees) end merge_request diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 86ac24ea06e..69c1a2ed89a 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -62,7 +62,7 @@ RSpec.describe 'Dashboard Issues', feature: true do it 'state filter tabs work' do find('#state-closed').click - expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, scope: 'all', state: 'closed'), url: true) + expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true) end it_behaves_like "it has an RSS button with current_user's RSS token" diff --git a/spec/features/projects/issuable_counts_caching_spec.rb b/spec/features/projects/issuable_counts_caching_spec.rb new file mode 100644 index 00000000000..703d1cbd327 --- /dev/null +++ b/spec/features/projects/issuable_counts_caching_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' + +describe 'Issuable counts caching', :use_clean_rails_memory_store_caching do + let!(:member) { create(:user) } + let!(:member_2) { create(:user) } + let!(:non_member) { create(:user) } + let!(:project) { create(:empty_project, :public) } + let!(:open_issue) { create(:issue, project: project) } + let!(:confidential_issue) { create(:issue, :confidential, project: project, author: non_member) } + let!(:closed_issue) { create(:issue, :closed, project: project) } + + before do + project.add_developer(member) + project.add_developer(member_2) + end + + it 'caches issuable counts correctly for non-members' do + # We can't use expect_any_instance_of because that uses a single instance. + counts = 0 + + allow_any_instance_of(IssuesFinder).to receive(:count_by_state).and_wrap_original do |m, *args| + counts += 1 + + m.call(*args) + end + + aggregate_failures 'only counts once on first load with no params, and caches for later loads' do + expect { visit project_issues_path(project) } + .to change { counts }.by(1) + + expect { visit project_issues_path(project) } + .not_to change { counts } + end + + aggregate_failures 'uses counts from cache on load from non-member' do + sign_in(non_member) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_out(non_member) + end + + aggregate_failures 'does not use the same cache for a member' do + sign_in(member) + + expect { visit project_issues_path(project) } + .to change { counts }.by(1) + + sign_out(member) + end + + aggregate_failures 'uses the same cache for all members' do + sign_in(member_2) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_out(member_2) + end + + aggregate_failures 'shares caches when params are passed' do + expect { visit project_issues_path(project, author_username: non_member.username) } + .to change { counts }.by(1) + + sign_in(member) + + expect { visit project_issues_path(project, author_username: non_member.username) } + .to change { counts }.by(1) + + sign_in(non_member) + + expect { visit project_issues_path(project, author_username: non_member.username) } + .not_to change { counts } + + sign_in(member_2) + + expect { visit project_issues_path(project, author_username: non_member.username) } + .not_to change { counts } + + sign_out(member_2) + end + + aggregate_failures 'resets caches on issue close' do + Issues::CloseService.new(project, member).execute(open_issue) + + expect { visit project_issues_path(project) } + .to change { counts }.by(1) + + sign_in(member) + + expect { visit project_issues_path(project) } + .to change { counts }.by(1) + + sign_in(non_member) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_in(member_2) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_out(member_2) + end + + aggregate_failures 'does not reset caches on issue update' do + Issues::UpdateService.new(project, member, title: 'new title').execute(open_issue) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_in(member) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_in(non_member) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_in(member_2) + + expect { visit project_issues_path(project) } + .not_to change { counts } + + sign_out(member_2) + end + end +end From 23223ba64e91cc3406ed6cf5889f8cc25f9a1337 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 19 Jul 2017 11:27:49 +0200 Subject: [PATCH 100/130] Do not allow to auto-retry a job more than 2 times --- doc/ci/yaml/README.md | 2 +- lib/gitlab/ci/config/entry/job.rb | 2 +- spec/lib/gitlab/ci/config/entry/job_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 6b17af1394a..808a23df554 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1147,7 +1147,7 @@ up to the amount of times specified by the `retry` keyword. If `retry` is set to 3, and a job succeeds in a second run, it won't be retried again. `retry` value has to be a positive integer, equal or larger than 0, but -lower than 10. +lower or equal to 2 (two retries maximum, three runs in total). A simple example: diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 5f1144894b5..32f5c6ab142 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -25,7 +25,7 @@ module Gitlab validates :allow_failure, boolean: true validates :retry, numericality: { only_integer: true, greater_than_or_equal_to: 0, - less_than: 10 } + less_than_or_equal_to: 2 } validates :when, inclusion: { in: %w[on_success on_failure always manual], message: 'should be on_success, on_failure, ' \ diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index f8ed59a3a44..6769f64f950 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -115,7 +115,7 @@ describe Gitlab::Ci::Config::Entry::Job do it 'returns error about value too high' do expect(entry).not_to be_valid - expect(entry.errors).to include 'job retry must be less than 10' + expect(entry.errors).to include 'job retry must be less than or equal to 2' end end end From fc7c7cb954fb590afe8d6553308aa60945271ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 19 Jul 2017 11:34:00 +0200 Subject: [PATCH 101/130] Fix a broken spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../merge_requests/filter_merge_requests_spec.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb index 2a161b83aa0..e8085ec36aa 100644 --- a/spec/features/merge_requests/filter_merge_requests_spec.rb +++ b/spec/features/merge_requests/filter_merge_requests_spec.rb @@ -132,19 +132,13 @@ describe 'Filter merge requests', feature: true do end end - describe 'for assignee and label from issues#index' do + describe 'for assignee and label from mr#index' do let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" } before do - input_filtered_search("assignee:@#{user.username}") + input_filtered_search(search_query) - expect_mr_list_count(1) - expect_tokens([{ name: 'assignee', value: "@#{user.username}" }]) - expect_filtered_search_input_empty - - input_filtered_search_keys("label:~#{label.title}") - - expect_mr_list_count(1) + expect_mr_list_count(0) end context 'assignee and label', js: true do From a9d940bffcf7447f8d62012bd1c8f866697a12d6 Mon Sep 17 00:00:00 2001 From: Jarka Kadlecova Date: Mon, 17 Jul 2017 16:38:43 +0200 Subject: [PATCH 102/130] Use Ghost user when edited_by, merged_by deleted --- app/models/concerns/editable.rb | 4 ++ app/models/user.rb | 4 +- .../users/migrate_to_ghost_user_service.rb | 2 + changelogs/unreleased/34930-fix-edited-by.yml | 4 ++ .../projects/issues_controller_spec.rb | 30 +++++++++++++ spec/features/issues/issue_detail_spec.rb | 43 +++++++++++++++++++ spec/helpers/issuables_helper_spec.rb | 20 +++++++++ .../migrate_to_ghost_user_service_spec.rb | 31 +++++++++---- ...e_to_ghost_user_service_shared_examples.rb | 25 ++++++----- 9 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 changelogs/unreleased/34930-fix-edited-by.yml create mode 100644 spec/features/issues/issue_detail_spec.rb diff --git a/app/models/concerns/editable.rb b/app/models/concerns/editable.rb index c62c7e1e936..28623d257a6 100644 --- a/app/models/concerns/editable.rb +++ b/app/models/concerns/editable.rb @@ -4,4 +4,8 @@ module Editable def is_edited? last_edited_at.present? && last_edited_at != created_at end + + def last_edited_by + super || User.ghost + end end diff --git a/app/models/user.rb b/app/models/user.rb index 8f40af24e20..c26be6d05a2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -385,9 +385,11 @@ class User < ActiveRecord::Base # Return (create if necessary) the ghost user. The ghost user # owns records previously belonging to deleted users. def ghost - unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u| + email = 'ghost%s@example.com' + unique_internal(where(ghost: true), 'ghost', email) do |u| u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' u.name = 'Ghost User' + u.notification_email = email end end end diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb index 4628c4c6f6e..3a9c151cf9b 100644 --- a/app/services/users/migrate_to_ghost_user_service.rb +++ b/app/services/users/migrate_to_ghost_user_service.rb @@ -50,10 +50,12 @@ module Users def migrate_issues user.issues.update_all(author_id: ghost_user.id) + Issue.where(last_edited_by_id: user.id).update_all(last_edited_by_id: ghost_user.id) end def migrate_merge_requests user.merge_requests.update_all(author_id: ghost_user.id) + MergeRequest.where(merge_user_id: user.id).update_all(merge_user_id: ghost_user.id) end def migrate_notes diff --git a/changelogs/unreleased/34930-fix-edited-by.yml b/changelogs/unreleased/34930-fix-edited-by.yml new file mode 100644 index 00000000000..f133dfab0c2 --- /dev/null +++ b/changelogs/unreleased/34930-fix-edited-by.yml @@ -0,0 +1,4 @@ +--- +title: Use Ghost user for last_edited_by and merge_user when original user is deleted +merge_request: 12933 +author: diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 22aad0b3225..dcb7a621c58 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -512,6 +512,36 @@ describe Projects::IssuesController do end end + describe 'GET #realtime_changes' do + it_behaves_like 'restricted action', success: 200 + + def go(id:) + get :realtime_changes, + namespace_id: project.namespace.to_param, + project_id: project, + id: id + end + + context 'when an issue was edited by a deleted user' do + let(:deleted_user) { create(:user) } + + before do + project.team << [user, :developer] + + issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now) + + deleted_user.destroy + sign_in(user) + end + + it 'returns 200' do + go(id: issue.iid) + + expect(response).to have_http_status(200) + end + end + end + describe 'GET #edit' do it_behaves_like 'restricted action', success: 200 diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb new file mode 100644 index 00000000000..e1c55d246ab --- /dev/null +++ b/spec/features/issues/issue_detail_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +feature 'Issue Detail', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project, author: user) } + + context 'when user displays the issue' do + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'shows the issue' do + page.within('.issuable-details') do + expect(find('h2')).to have_content(issue.title) + end + end + end + + context 'when edited by a user who is later deleted' do + before do + sign_in(user) + visit project_issue_path(project, issue) + wait_for_requests + + click_link 'Edit' + fill_in 'issue-title', with: 'issue title' + click_button 'Save' + + visit profile_account_path + click_link 'Delete account' + + visit project_issue_path(project, issue) + end + + it 'shows the issue' do + page.within('.issuable-details') do + expect(find('h2')).to have_content(issue.reload.title) + end + end + end +end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index b423a09873b..7789cfa3554 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -244,5 +244,25 @@ describe IssuablesHelper do it { expect(helper.updated_at_by(unedited_issuable)).to eq({}) } it { expect(helper.updated_at_by(edited_issuable)).to eq(edited_updated_at_by) } + + context 'when updated by a deleted user' do + let(:edited_updated_at_by) do + { + updatedAt: edited_issuable.updated_at.to_time.iso8601, + updatedBy: { + name: User.ghost.name, + path: user_path(User.ghost) + } + } + end + + before do + user.destroy + end + + it 'returns "Ghost user" as edited_by' do + expect(helper.updated_at_by(edited_issuable.reload)).to eq(edited_updated_at_by) + end + end end end diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb index 9e1edf1ac30..e52ecd6d614 100644 --- a/spec/services/users/migrate_to_ghost_user_service_spec.rb +++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb @@ -7,16 +7,32 @@ describe Users::MigrateToGhostUserService, services: true do context "migrating a user's associated records to the ghost user" do context 'issues' do - include_examples "migrating a deleted user's associated records to the ghost user", Issue do - let(:created_record) { create(:issue, project: project, author: user) } - let(:assigned_record) { create(:issue, project: project, assignee: user) } + context 'deleted user is present as both author and edited_user' do + include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:author, :last_edited_by] do + let(:created_record) do + create(:issue, project: project, author: user, last_edited_by: user) + end + end + end + + context 'deleted user is present only as edited_user' do + include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:last_edited_by] do + let(:created_record) { create(:issue, project: project, author: create(:user), last_edited_by: user) } + end end end context 'merge requests' do - include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest do - let(:created_record) { create(:merge_request, source_project: project, author: user, target_branch: "first") } - let(:assigned_record) { create(:merge_request, source_project: project, assignee: user, target_branch: 'second') } + context 'deleted user is present as both author and merge_user' do + include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:author, :merge_user] do + let(:created_record) { create(:merge_request, source_project: project, author: user, merge_user: user, target_branch: "first") } + end + end + + context 'deleted user is present only as both merge_user' do + include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:merge_user] do + let(:created_record) { create(:merge_request, source_project: project, merge_user: user, target_branch: "first") } + end end end @@ -33,9 +49,8 @@ describe Users::MigrateToGhostUserService, services: true do end context 'award emoji' do - include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji do + include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji, [:user] do let(:created_record) { create(:award_emoji, user: user) } - let(:author_alias) { :user } context "when the awardable already has an award emoji of the same name assigned to the ghost user" do let(:awardable) { create(:issue) } diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb index dcc562c684b..855051921f0 100644 --- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb +++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb @@ -1,6 +1,6 @@ require "spec_helper" -shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class| +shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields| record_class_name = record_class.to_s.titleize.downcase let(:project) { create(:project) } @@ -11,6 +11,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user context "for a #{record_class_name} the user has created" do let!(:record) { created_record } + let(:migrated_fields) { fields || [:author] } it "does not delete the #{record_class_name}" do service.execute @@ -18,24 +19,22 @@ shared_examples "migrating a deleted user's associated records to the ghost user expect(record_class.find_by_id(record.id)).to be_present end - it "migrates the #{record_class_name} so that the 'Ghost User' is the #{record_class_name} owner" do - service.execute - - migrated_record = record_class.find_by_id(record.id) - - if migrated_record.respond_to?(:author) - expect(migrated_record.author).to eq(User.ghost) - else - expect(migrated_record.send(author_alias)).to eq(User.ghost) - end - end - it "blocks the user before migrating #{record_class_name}s to the 'Ghost User'" do service.execute expect(user).to be_blocked end + it 'migrates all associated fields to te "Ghost user"' do + service.execute + + migrated_record = record_class.find_by_id(record.id) + + migrated_fields.each do |field| + expect(migrated_record.public_send(field)).to eq(User.ghost) + end + end + context "race conditions" do context "when #{record_class_name} migration fails and is rolled back" do before do From ace95b15401bbd9231558f9ba1af0e4fe742b513 Mon Sep 17 00:00:00 2001 From: Pablo Catalina Date: Wed, 19 Jul 2017 09:58:47 +0000 Subject: [PATCH 103/130] Update projects.md. Fix Search project by name format and added a curl example. --- doc/api/projects.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 0d892c74d00..61ae89a64c0 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1257,17 +1257,21 @@ endpoint can be accessed without authentication if the project is publicly accessible. ``` -GET /projects/search/:query +GET /projects ``` Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `query` | string | yes | A string contained in the project name | +| `search` | string | yes | A string contained in the project name | | `order_by` | string | no | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields | | `sort` | string | no | Return requests sorted in `asc` or `desc` order | +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects?search=test +``` + ## Start the Housekeeping task for a Project >**Note:** This feature was introduced in GitLab 9.0 From 1e66907971873b34a22489ba071e7c65c6ab9aba Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Wed, 19 Jul 2017 10:47:41 +0000 Subject: [PATCH 104/130] Add Japanese Translation to i18n --- ...-japanese-translations-of-commits-page.yml | 4 + lib/gitlab/i18n.rb | 3 +- locale/ja/gitlab.po | 1184 +++++++++++++++++ locale/ja/gitlab.po.time_stamp | 0 4 files changed, 1190 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml create mode 100644 locale/ja/gitlab.po create mode 100644 locale/ja/gitlab.po.time_stamp diff --git a/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml b/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml new file mode 100644 index 00000000000..40a24847580 --- /dev/null +++ b/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Japanese translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. +merge_request: 12693 +author: Huang Tao diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index f3d489aad0d..0c2f8f8ea21 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -13,7 +13,8 @@ module Gitlab 'zh_TW' => '繁體中文(臺灣)', 'bg' => 'български', 'eo' => 'Esperanto', - 'it' => 'Italiano' + 'it' => 'Italiano', + 'ja' => '日本語' }.freeze def available_locales diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po new file mode 100644 index 00000000000..b880fc703ec --- /dev/null +++ b/locale/ja/gitlab.po @@ -0,0 +1,1184 @@ +# Arthur Charron , 2017. #zanata +# Huang Tao , 2017. #zanata +# Kohei Ota , 2017. #zanata +# Taisuke Inoue , 2017. #zanata +# Takuya Noguchi , 2017. #zanata +# YANO TETTER , 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: gitlab 1.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language-Team: Japanese (https://translate.zanata.org/project/view/GitLab)\n" +"PO-Revision-Date: 2017-07-18 09:27-0400\n" +"Last-Translator: YANO TETTER \n" +"Language: ja\n" +"X-Generator: Zanata 3.9.6\n" +"Plural-Forms: nplurals=1; plural=0\n" + +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "パフォーマンス低下を避けるため %s 個のコミットを省略しました。" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d個のコミット" + +msgid "%{commit_author_link} committed %{commit_timeago}" +msgstr "%{commit_author_link}は%{commit_timeago}前、コミットしました。" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "%d 個のパイプライン" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "CIについてのグラフ" + +msgid "About auto deploy" +msgstr "自動デプロイについて" + +msgid "Active" +msgstr "有効" + +msgid "Activity" +msgstr "アクティビティー" + +msgid "Add Changelog" +msgstr "変更履歴を追加" + +msgid "Add Contribution guide" +msgstr "貢献者向けガイドを追加" + +msgid "Add License" +msgstr "ライセンスを追加" + +msgid "Add an SSH key to your profile to pull or push via SSH." +msgstr "SSHでプルやプッシュする場合は、プロフィールにSSH鍵を追加してください。" + +msgid "Add new directory" +msgstr "新規ディレクトリを追加" + +msgid "Archived project! Repository is read-only" +msgstr "アーカイブ済みプロジェクト!(レポジトリーは読み取り専用です)" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "このパイプラインスケジュールを削除しますか?" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "ドラッグ&ドロップまたは %{upload_link} でファイルを添付" + +msgid "Branch" +msgid_plural "Branches" +msgstr[0] "ブランチ" + +msgid "" +"Branch %{branch_name} was created. To set up auto deploy, " +"choose a GitLab CI Yaml template and commit your changes. " +"%{link_to_autodeploy_doc}" +msgstr "" +"%{branch_name} ブランチが作成されました。自動デプロイを設定するには、GitLab CI Yaml " +"テンプレートを選択して、変更をコミットしてください。 %{link_to_autodeploy_doc}" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "ブランチを検索" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "ブランチを切替" + +msgid "Branches" +msgstr "ブランチ" + +msgid "Browse Directory" +msgstr "ディレクトリを表示" + +msgid "Browse File" +msgstr "ファイルを表示" + +msgid "Browse Files" +msgstr "ファイルを表示" + +msgid "Browse files" +msgstr "ファイルを表示" + +msgid "ByAuthor|by" +msgstr "作者" + +msgid "CI configuration" +msgstr "CI 設定" + +msgid "Cancel" +msgstr "キャンセル" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "ピック先ブランチ:" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "リバート先ブランチ:" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "チェリーピック" + +msgid "ChangeTypeAction|Revert" +msgstr "リバート" + +msgid "Changelog" +msgstr "変更履歴" + +msgid "Charts" +msgstr "チャート" + +msgid "Cherry-pick this commit" +msgstr "このコミットをチェリーピック" + +msgid "Cherry-pick this merge request" +msgstr "このマージリクエストをチェリーピック" + +msgid "CiStatusLabel|canceled" +msgstr "キャンセル" + +msgid "CiStatusLabel|created" +msgstr "作成済み" + +msgid "CiStatusLabel|failed" +msgstr "失敗" + +msgid "CiStatusLabel|manual action" +msgstr "手動実行" + +msgid "CiStatusLabel|passed" +msgstr "成功" + +msgid "CiStatusLabel|passed with warnings" +msgstr "成功(警告あり)" + +msgid "CiStatusLabel|pending" +msgstr "開始待ち" + +msgid "CiStatusLabel|skipped" +msgstr "スキップ済み" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "手動実行待ち" + +msgid "CiStatusText|blocked" +msgstr "ブロック" + +msgid "CiStatusText|canceled" +msgstr "キャンセル" + +msgid "CiStatusText|created" +msgstr "作成済み" + +msgid "CiStatusText|failed" +msgstr "失敗" + +msgid "CiStatusText|manual" +msgstr "手動" + +msgid "CiStatusText|passed" +msgstr "成功" + +msgid "CiStatusText|pending" +msgstr "実行待ち" + +msgid "CiStatusText|skipped" +msgstr "スキップ済み" + +msgid "CiStatus|running" +msgstr "実行中" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "コミット" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "直近30コミットの所要時間(分)" + +msgid "Commit message" +msgstr "コミットメッセージ" + +msgid "CommitBoxTitle|Commit" +msgstr "コミット" + +msgid "CommitMessage|Add %{file_name}" +msgstr "%{file_name} を追加" + +msgid "Commits" +msgstr "コミット" + +msgid "Commits feed" +msgstr "コミットフィード" + +msgid "Commits|History" +msgstr "履歴" + +msgid "Committed by" +msgstr "コミット担当者: " + +msgid "Compare" +msgstr "比較" + +msgid "Contribution guide" +msgstr "貢献者向けガイド" + +msgid "Contributors" +msgstr "貢献者" + +msgid "Copy URL to clipboard" +msgstr "クリップボードにURLをコピー" + +msgid "Copy commit SHA to clipboard" +msgstr "コミットのSHAをクリップボードにコピー" + +msgid "Create New Directory" +msgstr "新規ディレクトリを作成" + +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "%{protocol} でプッシュやプルするためのあなた個人用アクセストークンを作成" + +msgid "Create directory" +msgstr "ディレクトリを作成" + +msgid "Create empty bare repository" +msgstr "空のbareレポジトリーを作成" + +msgid "Create merge request" +msgstr "マージリクエストを作成" + +msgid "Create new..." +msgstr "新規作成" + +msgid "CreateNewFork|Fork" +msgstr "フォーク" + +msgid "CreateTag|Tag" +msgstr "タグ" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "個人用アクセストークンを作成" + +msgid "Cron Timezone" +msgstr "Cron のタイムゾーン" + +msgid "Cron syntax" +msgstr "Cron の構文" + +msgid "Custom notification events" +msgstr "カスタム通知設定" + +msgid "" +"Custom notification levels are the same as participating levels. With custom " +"notification levels you will also receive notifications for select events. " +"To find out more, check out %{notification_link}." +msgstr "" +"\"カスタム\" の通知レベルの基本は \"参加\" " +"と同じです。また、カスタム通知に設定することで選択したカスタムイベントの通知を受け取ることもできます。もっと詳しく知りたい場合は " +"%{notification_link} を見てください。" + +msgid "Cycle Analytics" +msgstr "サイクル分析" + +msgid "" +"Cycle Analytics gives an overview of how much time it takes to go from idea " +"to production in your project." +msgstr "" +"サイクル分析により、あなたのプロジェクトがアイディアの段階からプロダクション環境にリリースされるまでどれぐらい時間がかかったか俯瞰することができます。" + +msgid "CycleAnalyticsStage|Code" +msgstr "コード" + +msgid "CycleAnalyticsStage|Issue" +msgstr "課題" + +msgid "CycleAnalyticsStage|Plan" +msgstr "計画" + +msgid "CycleAnalyticsStage|Production" +msgstr "プロダクション" + +msgid "CycleAnalyticsStage|Review" +msgstr "レビュー" + +msgid "CycleAnalyticsStage|Staging" +msgstr "ステージング" + +msgid "CycleAnalyticsStage|Test" +msgstr "テスト" + +msgid "Define a custom pattern with cron syntax" +msgstr "Cron 構文でカスタムなパターンを指定する" + +msgid "Delete" +msgstr "削除" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "デプロイ" + +msgid "Description" +msgstr "説明" + +msgid "Directory name" +msgstr "ディレクトリ名" + +msgid "Don't show again" +msgstr "次回から表示しない" + +msgid "Download" +msgstr "ダウンロード" + +msgid "Download tar" +msgstr "tar形式でダウンロード" + +msgid "Download tar.bz2" +msgstr "tar.bz2形式でダウンロード" + +msgid "Download tar.gz" +msgstr "tar.gz形式でダウンロード" + +msgid "Download zip" +msgstr "zip形式でダウンロード" + +msgid "DownloadArtifacts|Download" +msgstr "ダウンロード" + +msgid "DownloadCommit|Email Patches" +msgstr "パッチをメールで送信" + +msgid "DownloadCommit|Plain Diff" +msgstr "プレーン差分" + +msgid "DownloadSource|Download" +msgstr "ダウンロード" + +msgid "Edit" +msgstr "編集" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "パイプラインスケジュール %{id} を編集" + +msgid "Every day (at 4:00am)" +msgstr "毎日 (午前4:00)" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "毎月 (1日の午前4:00)" + +msgid "Every week (Sundays at 4:00am)" +msgstr "毎週 (日曜日の午前4:00)" + +msgid "Failed to change the owner" +msgstr "オーナーを変更できませんでした" + +msgid "Failed to remove the pipeline schedule" +msgstr "パイプラインスケジュールを削除できませんでした" + +msgid "Files" +msgstr "ファイル" + +msgid "Filter by commit message" +msgstr "コミットメッセージで絞り込み" + +msgid "Find by path" +msgstr "パスで検索" + +msgid "Find file" +msgstr "ファイルを検索" + +msgid "FirstPushedBy|First" +msgstr "初回" + +msgid "FirstPushedBy|pushed by" +msgstr "プッシュした人" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "フォーク" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "フォーク元" + +msgid "From issue creation until deploy to production" +msgstr "課題が登録されてからプロダクションにデプロイされるまで" + +msgid "From merge request merge until deploy to production" +msgstr "マージリクエストがマージされてからプロダクションにデプロイされるまで" + +msgid "Go to your fork" +msgstr "自分のフォークへ移動" + +msgid "GoToYourFork|Fork" +msgstr "フォーク" + +msgid "Home" +msgstr "ホーム" + +msgid "Housekeeping successfully started" +msgstr "ハウスキーピングは正常に起動しました。" + +msgid "Import repository" +msgstr "レポジトリーをインポート" + +msgid "Interval Pattern" +msgstr "間隔のパターン" + +msgid "Introducing Cycle Analytics" +msgstr "サイクル分析のご紹介" + +msgid "Jobs for last month" +msgstr "先月のジョブ" + +msgid "Jobs for last week" +msgstr "先週のジョブ" + +msgid "Jobs for last year" +msgstr "昨年のジョブ" + +msgid "LFSStatus|Disabled" +msgstr "無効" + +msgid "LFSStatus|Enabled" +msgstr "有効" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "過去%d日間" + +msgid "Last Pipeline" +msgstr "最新パイプライン" + +msgid "Last Update" +msgstr "最新アップデート" + +msgid "Last commit" +msgstr "最新コミット" + +msgid "Learn more in the" +msgstr "詳しく見る:" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "詳しくはパイプラインスケジュールのドキュメントを参照" + +msgid "Leave group" +msgstr "グループを離脱" + +msgid "Leave project" +msgstr "プロジェクトを離脱" + +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "イベント表示数を最大 %d 個に制限" + +msgid "Median" +msgstr "中央値" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "SSH 鍵を追加" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "新規課題" + +msgid "New Pipeline Schedule" +msgstr "新規パイプラインスケジュール" + +msgid "New branch" +msgstr "新規ブランチ" + +msgid "New directory" +msgstr "新規ディレクトリ" + +msgid "New file" +msgstr "新規ファイル" + +msgid "New issue" +msgstr "新規課題" + +msgid "New merge request" +msgstr "新規マージリクエスト" + +msgid "New schedule" +msgstr "新規スケジュール" + +msgid "New snippet" +msgstr "新規スニペット" + +msgid "New tag" +msgstr "新規タグ" + +msgid "No repository" +msgstr "レポジトリーはありません" + +msgid "No schedules" +msgstr "スケジュールなし" + +msgid "Not available" +msgstr "利用できません" + +msgid "Not enough data" +msgstr "データ不足" + +msgid "Notification events" +msgstr "イベント通知" + +msgid "NotificationEvent|Close issue" +msgstr "課題をクローズ" + +msgid "NotificationEvent|Close merge request" +msgstr "マージリクエストをクローズ" + +msgid "NotificationEvent|Failed pipeline" +msgstr "パイプラインに失敗" + +msgid "NotificationEvent|Merge merge request" +msgstr "マージリクエストをマージ" + +msgid "NotificationEvent|New issue" +msgstr "新規課題" + +msgid "NotificationEvent|New merge request" +msgstr "新規マージリクエスト" + +msgid "NotificationEvent|New note" +msgstr "新規ノート" + +msgid "NotificationEvent|Reassign issue" +msgstr "課題の担当者を変更" + +msgid "NotificationEvent|Reassign merge request" +msgstr "マージリクエスト担当者を変更" + +msgid "NotificationEvent|Reopen issue" +msgstr "課題を再オープン" + +msgid "NotificationEvent|Successful pipeline" +msgstr "パイプライン成功" + +msgid "NotificationLevel|Custom" +msgstr "カスタム" + +msgid "NotificationLevel|Disabled" +msgstr "無効" + +msgid "NotificationLevel|Global" +msgstr "全体設定" + +msgid "NotificationLevel|On mention" +msgstr "メンション時" + +msgid "NotificationLevel|Participate" +msgstr "参加" + +msgid "NotificationLevel|Watch" +msgstr "すべて通知" + +msgid "OfSearchInADropdown|Filter" +msgstr "フィルター" + +msgid "OpenedNDaysAgo|Opened" +msgstr "オープンされたのは" + +msgid "Options" +msgstr "オプション" + +msgid "Owner" +msgstr "オーナー" + +msgid "Pipeline" +msgstr "パイプライン" + +msgid "Pipeline Health" +msgstr "パイプラインの進捗状況" + +msgid "Pipeline Schedule" +msgstr "パイプラインスケジュール" + +msgid "Pipeline Schedules" +msgstr "パイプラインスケジュール" + +msgid "PipelineCharts|Failed:" +msgstr "失敗:" + +msgid "PipelineCharts|Overall statistics" +msgstr "全体統計" + +msgid "PipelineCharts|Success ratio:" +msgstr "成功比率:" + +msgid "PipelineCharts|Successful:" +msgstr "成功:" + +msgid "PipelineCharts|Total:" +msgstr "合計:" + +msgid "PipelineSchedules|Activated" +msgstr "アクティブ" + +msgid "PipelineSchedules|Active" +msgstr "アクティブ" + +msgid "PipelineSchedules|All" +msgstr "全件" + +msgid "PipelineSchedules|Inactive" +msgstr "無効" + +msgid "PipelineSchedules|Next Run" +msgstr "次の実行" + +msgid "PipelineSchedules|None" +msgstr "なし" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "このパイプラインについて簡単に記述してください。" + +msgid "PipelineSchedules|Take ownership" +msgstr "権限を取得する" + +msgid "PipelineSchedules|Target" +msgstr "ターゲット" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "カスタム" + +msgid "Pipelines" +msgstr "パイプライン" + +msgid "Pipelines charts" +msgstr "パイプラインチャート" + +msgid "Pipeline|all" +msgstr "全件" + +msgid "Pipeline|success" +msgstr "成功" + +msgid "Pipeline|with stage" +msgstr "ステージあり" + +msgid "Pipeline|with stages" +msgstr "ステージあり" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "'%{project_name}' プロジェクトは削除処理待ちです。" + +msgid "Project '%{project_name}' was successfully created." +msgstr "'%{project_name}' プロジェクトは正常に作成されました。" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "'%{project_name}' プロジェクトは正常に更新されました。" + +msgid "Project '%{project_name}' will be deleted." +msgstr "'%{project_name}' プロジェクトは削除されます。" + +msgid "Project access must be granted explicitly to each user." +msgstr "ユーザーごとにプロジェクトアクセスの権限を指定しなければなりません。" + +msgid "Project export could not be deleted." +msgstr "プロジェクトのエクスポートを削除できませんでした。" + +msgid "Project export has been deleted." +msgstr "プロジェクトのエクスポートを削除しました。" + +msgid "" +"Project export link has expired. Please generate a new export from your " +"project settings." +msgstr "プロジェクトのエクスポートリンクは期限切れになりました。プロジェクト設定にて新しくエクスポートリンクを作成してください。" + +msgid "Project export started. A download link will be sent by email." +msgstr "プロジェクトのエクスポートを開始しました。ダウンロードのリンクはメールで送信します" + +msgid "Project home" +msgstr "プロジェクトホーム" + +msgid "ProjectFeature|Disabled" +msgstr "無効" + +msgid "ProjectFeature|Everyone with access" +msgstr "アクセス権限を持っている人" + +msgid "ProjectFeature|Only team members" +msgstr "チームメンバーのみ" + +msgid "ProjectFileTree|Name" +msgstr "名前" + +msgid "ProjectLastActivity|Never" +msgstr "記録なし" + +msgid "ProjectLifecycle|Stage" +msgstr "ステージ" + +msgid "ProjectNetworkGraph|Graph" +msgstr "ネットワークグラフ" + +msgid "Read more" +msgstr "続きを読む" + +msgid "Readme" +msgstr "Readme" + +msgid "RefSwitcher|Branches" +msgstr "ブランチ" + +msgid "RefSwitcher|Tags" +msgstr "タグ" + +msgid "Related Commits" +msgstr "関連するコミット" + +msgid "Related Deployed Jobs" +msgstr "関連するデプロイ済ジョブ" + +msgid "Related Issues" +msgstr "関連する課題" + +msgid "Related Jobs" +msgstr "関連するジョブ" + +msgid "Related Merge Requests" +msgstr "関連するマージリクエスト" + +msgid "Related Merged Requests" +msgstr "関連するマージリクエスト" + +msgid "Remind later" +msgstr "後で通知" + +msgid "Remove project" +msgstr "プロジェクトを削除" + +msgid "Request Access" +msgstr "アクセス権限をリクエストする" + +msgid "Revert this commit" +msgstr "このコミットをリバート" + +msgid "Revert this merge request" +msgstr "このマージリクエストをリバート" + +msgid "Save pipeline schedule" +msgstr "パイプラインスケジュールを保存" + +msgid "Schedule a new pipeline" +msgstr "新しいパイプラインのスケジュールを作成" + +msgid "Scheduling Pipelines" +msgstr "パイプラインスケジューリング" + +msgid "Search branches and tags" +msgstr "ブランチまたはタグを検索" + +msgid "Select Archive Format" +msgstr "アーカイブのフォーマットを選択" + +msgid "Select a timezone" +msgstr "タイムゾーンを選択" + +msgid "Select target branch" +msgstr "ターゲットブランチを選択" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "%{protocol} プロコトル経由でプル、プッシュするためにアカウントのパスワードを設定。" + +msgid "Set up CI" +msgstr "CI を設定" + +msgid "Set up Koding" +msgstr "Koding を設定" + +msgid "Set up auto deploy" +msgstr "自動デプロイを設定" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "パスワードを設定" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "%d のイベントを表示中" + +msgid "Source code" +msgstr "ソースコード" + +msgid "StarProject|Star" +msgstr "スターを付ける" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "この変更で %{new_merge_request} を作成する" + +msgid "Switch branch/tag" +msgstr "ブランチ・タグ切り替え" + +msgid "Tag" +msgid_plural "Tags" +msgstr[0] "タグ" + +msgid "Tags" +msgstr "タグ" + +msgid "Target Branch" +msgstr "ターゲットブランチ" + +msgid "" +"The coding stage shows the time from the first commit to creating the merge " +"request. The data will automatically be added here once you create your " +"first merge request." +msgstr "" +"コーディングステージでは、最初のコミットからマージリクエストが作成されるまでの時間が表示されます。このデータは最初のマージリクエストが作成されたときに自動的に追加されます。" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "このステージで計測データに追加されたイベントリスト" + +msgid "The fork relationship has been removed." +msgstr "フォークのリレーションが削除されました。" + +msgid "" +"The issue stage shows the time it takes from creating an issue to assigning " +"the issue to a milestone, or add the issue to a list on your Issue Board. " +"Begin creating issues to see data for this stage." +msgstr "" +"課題ステージでは、課題が登録されてからマイルストーンに割り当てられるか、課題ボードのリストに追加されるまでの時間が表示されます。このリストに表示するには課題を最初に作成してください。" + +msgid "The phase of the development lifecycle." +msgstr "開発ライフサイクルの段階" + +msgid "" +"The pipelines schedule runs pipelines in the future, repeatedly, for " +"specific branches or tags. Those scheduled pipelines will inherit limited " +"project access based on their associated user." +msgstr "" +"パイプラインスケジュールは指定のブランチまたはタグに対して自動的にパイプラインを実行します。計画済みパイプラインはそれらの紐付けられたユーザーのプロジェクトと同じ権限を継承します。" + +msgid "" +"The planning stage shows the time from the previous step to pushing your " +"first commit. This time will be added automatically once you push your first " +"commit." +msgstr "" +"計画ステージでは、課題ステージに登録されてからプッシュされた最初のコミット時刻までの時間が表示されます。最初のコミットがプッシュされときに自動的に追加されます。" + +msgid "" +"The production stage shows the total time it takes between creating an issue " +"and deploying the code to production. The data will be automatically added " +"once you have completed the full idea to production cycle." +msgstr "" +"プロダクションステージでは、課題が作成されてからプロダクションへデプロイされるまでの時間が表示されます。アイディアの時点からプロダクションまでの全ステージが完了したときに自動的に追加されます。" + +msgid "The project can be accessed by any logged in user." +msgstr "プロジェクトは、ログインユーザーであれば誰でもアクセスできます。" + +msgid "The project can be accessed without any authentication." +msgstr "プロジェクトは、ログインなしに誰でもアクセスできます。" + +msgid "The repository for this project does not exist." +msgstr "このプロジェクトにレポジトリーはありません。" + +msgid "" +"The review stage shows the time from creating the merge request to merging " +"it. The data will automatically be added after you merge your first merge " +"request." +msgstr "" +"レビューステージとは、マージリクエストを作成してからマージするまでの時間です。このデータは最初のマージリクエストがマージされたときに自動的に追加されます。" + +msgid "" +"The staging stage shows the time between merging the MR and deploying code " +"to the production environment. The data will be automatically added once you " +"deploy to production for the first time." +msgstr "" +"ステージングステージでは、マージリクエストがマージされてからコードがプロダクション環境にデプロイされるまでの時間が表示されます。このデータは最初にプロダクションにデプロイしたときに自動的に追加されます。" + +msgid "" +"The testing stage shows the time GitLab CI takes to run every pipeline for " +"the related merge request. The data will automatically be added after your " +"first pipeline finishes running." +msgstr "" +"テスティングステージでは、GitLab CI " +"が関連するマージリクエストの各パイプラインを実行する時間が表示されます。このデータは最初のパイプラインが完了したときに自動的に追加されます。" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "このステージに収集されたデータ毎の時間" + +msgid "" +"The value lying at the midpoint of a series of observed values. E.g., " +"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 =" +" 6." +msgstr "" +"得られた一連のデータを小さい順に並べたときに中央に位置する値。例えば、3, 5, 9の中央値は5。3, 5, 7, 8の中央値は (5+7)/2 = " +"6。" + +msgid "" +"This means you can not push code until you create an empty repository or " +"import existing one." +msgstr "空レポジトリーを作成または既存レポジトリーをインポートをしなければ、コードのプッシュはできません。" + +msgid "Time before an issue gets scheduled" +msgstr "課題が計画されるまでの時間" + +msgid "Time before an issue starts implementation" +msgstr "課題の実装が開始されるまでの時間" + +msgid "Time between merge request creation and merge/close" +msgstr "マージリクエストが作成されてからマージまたはクローズされるまでの時間" + +msgid "Time until first merge request" +msgstr "最初のマージリクエストまでの時間" + +msgid "Timeago|%s days ago" +msgstr "%s日前" + +msgid "Timeago|%s days remaining" +msgstr "残り %s日間" + +msgid "Timeago|%s hours remaining" +msgstr "残り %s時間" + +msgid "Timeago|%s minutes ago" +msgstr "%s分前" + +msgid "Timeago|%s minutes remaining" +msgstr "残り %s分間" + +msgid "Timeago|%s months ago" +msgstr "%sヶ月前" + +msgid "Timeago|%s months remaining" +msgstr "残り %sヶ月" + +msgid "Timeago|%s seconds remaining" +msgstr "残り %s 秒" + +msgid "Timeago|%s weeks ago" +msgstr "%s週間前" + +msgid "Timeago|%s weeks remaining" +msgstr "残り %s週間" + +msgid "Timeago|%s years ago" +msgstr "%s年前" + +msgid "Timeago|%s years remaining" +msgstr "残り %s年間" + +msgid "Timeago|1 day remaining" +msgstr "残り 1日間" + +msgid "Timeago|1 hour remaining" +msgstr "残り 1時間" + +msgid "Timeago|1 minute remaining" +msgstr "残り 1分間" + +msgid "Timeago|1 month remaining" +msgstr "残り 1ヶ月" + +msgid "Timeago|1 week remaining" +msgstr "残り 1週間" + +msgid "Timeago|1 year remaining" +msgstr "残り 1年間" + +msgid "Timeago|Past due" +msgstr "期限オーバー" + +msgid "Timeago|a day ago" +msgstr "1日前" + +msgid "Timeago|a month ago" +msgstr "1ヶ月前" + +msgid "Timeago|a week ago" +msgstr "1週間前" + +msgid "Timeago|a while" +msgstr "しばらく前" + +msgid "Timeago|a year ago" +msgstr "1年前" + +msgid "Timeago|about %s hours ago" +msgstr "約%s時間前" + +msgid "Timeago|about a minute ago" +msgstr "約1分間前" + +msgid "Timeago|about an hour ago" +msgstr "約1時間前" + +msgid "Timeago|in %s days" +msgstr "%s日間以内" + +msgid "Timeago|in %s hours" +msgstr "%s時間以内" + +msgid "Timeago|in %s minutes" +msgstr "%s分間以内" + +msgid "Timeago|in %s months" +msgstr "%sヶ月以内" + +msgid "Timeago|in %s seconds" +msgstr "%s秒以内" + +msgid "Timeago|in %s weeks" +msgstr "%s週間以内" + +msgid "Timeago|in %s years" +msgstr "%s年間以内" + +msgid "Timeago|in 1 day" +msgstr "1日以内" + +msgid "Timeago|in 1 hour" +msgstr "1時間以内" + +msgid "Timeago|in 1 minute" +msgstr "1分以内" + +msgid "Timeago|in 1 month" +msgstr "1ヶ月以内" + +msgid "Timeago|in 1 week" +msgstr "1週間以内" + +msgid "Timeago|in 1 year" +msgstr "1年以内" + +msgid "Timeago|less than a minute ago" +msgstr "1分未満" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "時間" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "分" + +msgid "Time|s" +msgstr "秒" + +msgid "Total Time" +msgstr "合計時間" + +msgid "Total test time for all commits/merges" +msgstr "すべてのコミット/マージの合計テスト時間" + +msgid "Unstar" +msgstr "スターを外す" + +msgid "Upload New File" +msgstr "新規ファイルをアップロード" + +msgid "Upload file" +msgstr "ファイルをアップロード" + +msgid "UploadLink|click to upload" +msgstr "クリックしてアップロード" + +msgid "Use your global notification setting" +msgstr "全体通知設定を利用" + +msgid "View open merge request" +msgstr "オープンなマージリクエストを表示" + +msgid "VisibilityLevel|Internal" +msgstr "内部" + +msgid "VisibilityLevel|Private" +msgstr "プライベート" + +msgid "VisibilityLevel|Public" +msgstr "パブリック" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "このデータを参照したいですか?アクセスするには管理者に問い合わせてください。" + +msgid "We don't have enough data to show this stage." +msgstr "データ不足のため、このステージの表示はできません。" + +msgid "Withdraw Access Request" +msgstr "アクセスリクエストを取り消す" + +msgid "" +"You are going to remove %{project_name_with_namespace}.\n" +"Removed project CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"%{project_name_with_namespace} プロジェクトを削除しようとしています。\n" +"削除されたプロジェクトは絶対に元には戻せません!\n" +"本当によろしいですか?" + +msgid "" +"You are going to remove the fork relationship to source project " +"%{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "元のプロジェクト (%{forked_from_project}) とのリレーションを削除しようとしています。\n" +"本当によろしいですか?" + +msgid "" +"You are going to transfer %{project_name_with_namespace} to another owner. " +"Are you ABSOLUTELY sure?" +msgstr "%{project_name_with_namespace} プロジェクトを別のオーナーに移譲しようとしています。本当によろしいですか?" + +msgid "You can only add files when you are on a branch" +msgstr "ファイルを追加するには、どこかのブランチにいなければいけません" + +msgid "You have reached your project limit" +msgstr "プロジェクト数の上限に達しています" + +msgid "You must sign in to star a project" +msgstr "プロジェクトにスターをつけたい場合はログインしてください" + +msgid "You need permission." +msgstr "権限が必要です" + +msgid "You will not get any notifications via email" +msgstr "通知メールを送信しません" + +msgid "You will only receive notifications for the events you choose" +msgstr "選択したイベントのみ通知します" + +msgid "" +"You will only receive notifications for threads you have participated in" +msgstr "参加したスレッドのみ通知します" + +msgid "You will receive notifications for any activity" +msgstr "全てのアクティビティーを通知します" + +msgid "" +"You will receive notifications only for comments in which you were " +"@mentioned" +msgstr "あなたが @mentioned でコメントされた時のみ通知します" + +msgid "" +"You won't be able to pull or push project code via %{protocol} until you " +"%{set_password_link} on your account" +msgstr "" +"%{set_password_link} でアカウントのパスワードがセットされていないので、プロジェクトに %{protocol} " +"でソースコードをプッシュ、プルできません" + +msgid "" +"You won't be able to pull or push project code via SSH until you " +"%{add_ssh_key_link} to your profile" +msgstr "%{add_ssh_key_link} をプロファイルに追加していないので、プロジェクトにソースコードをプッシュ、プルできません" + +msgid "Your name" +msgstr "名前" + +msgid "day" +msgid_plural "days" +msgstr[0] "日" + +msgid "new merge request" +msgstr "新規マージリクエスト" + +msgid "notification emails" +msgstr "メール通知" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "親" + diff --git a/locale/ja/gitlab.po.time_stamp b/locale/ja/gitlab.po.time_stamp new file mode 100644 index 00000000000..e69de29bb2d From da8796e343457fe4faf2844275a321f4815c0d37 Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Wed, 19 Jul 2017 10:51:52 +0000 Subject: [PATCH 105/130] Add Portuguese Brazil translations of Commits Page & Pipeline Charts --- ...se-brazil-translations-of-commits-page.yml | 4 + locale/pt_BR/gitlab.po | 109 +++++++++++++++++- 2 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml diff --git a/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml b/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml new file mode 100644 index 00000000000..16a9216852d --- /dev/null +++ b/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml @@ -0,0 +1,4 @@ +--- +title: Add Portuguese Brazil translations of Commits Page +merge_request: 12408 +author: Huang Tao diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index 1ea39894bb8..c4918a4c920 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -6,20 +6,41 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-15 21:59-0500\n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-05 02:56-0400\n" -"Last-Translator: Huang Tao \n" -"Language-Team: Portuguese (Brazil)\n" +"PO-Revision-Date: 2017-07-12 09:05-0400\n" +"Last-Translator: Leandro Nunes dos Santos \n" +"Language-Team: Portuguese (Brazil) (https://translate.zanata.org/project/view/GitLab)\n" "Language: pt-BR\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "" +"%s additional commits have been omitted to prevent performance issues." +msgstr[0] "" +"%s commit adicional foi omitido para prevenir problemas de performance." +msgstr[1] "" +"%s commits adicionais foram omitidos para prevenir problemas de performance." + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "%d commit" +msgstr[1] "%d commits" + msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} fez commit %{commit_timeago}" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 pipeline" +msgstr[1] "%d pipelines" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Uma coleção de gráficos sobre Integração Contínua" + msgid "About auto deploy" msgstr "Sobre a implantação automática" @@ -67,9 +88,24 @@ msgstr "" "implantação automática, selecione um modelo de Yaml do GitLab CI e registre " "suas mudanças. %{link_to_autodeploy_doc}" +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "BranchSwitcherPlaceholder|Procurar por branches" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "BranchSwitcherTitle|Mudar de branch" + msgid "Branches" msgstr "Branches" +msgid "Browse Directory" +msgstr "Navegar no Diretório" + +msgid "Browse File" +msgstr "Pesquisar Arquivo" + +msgid "Browse Files" +msgstr "Pesquisar Arquivos" + msgid "Browse files" msgstr "Navegar pelos arquivos" @@ -165,6 +201,9 @@ msgid_plural "Commits" msgstr[0] "Commit" msgstr[1] "Commits" +msgid "Commit duration in minutes for last 30 commits" +msgstr "Duração do commit em minutos para os últimos 30 commits" + msgid "Commit message" msgstr "Mensagem de commit" @@ -177,6 +216,9 @@ msgstr "Adicionar %{file_name}" msgid "Commits" msgstr "Commits" +msgid "Commits feed" +msgstr "Feed de commits" + msgid "Commits|History" msgstr "Histórico" @@ -201,6 +243,13 @@ msgstr "Copiar SHA do commit para a área de transferência" msgid "Create New Directory" msgstr "Criar Novo Diretório" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" +"Crie um token de acesso pessoal na sua conta para dar pull ou push via " +"%{protocol}." + msgid "Create directory" msgstr "Criar diretório" @@ -219,6 +268,9 @@ msgstr "Fork" msgid "CreateTag|Tag" msgstr "Tag" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "CreateTokenToCloneLink|criar um token de acesso pessoal" + msgid "Cron Timezone" msgstr "Fuso horário do cron" @@ -340,6 +392,9 @@ msgstr "Erro ao excluir o agendamento do pipeline" msgid "Files" msgstr "Arquivos" +msgid "Filter by commit message" +msgstr "Filtrar por mensagem de commit" + msgid "Find by path" msgstr "Localizar por caminho" @@ -388,6 +443,15 @@ msgstr "Padrão de intervalo" msgid "Introducing Cycle Analytics" msgstr "Apresentando a Análise de Ciclo" +msgid "Jobs for last month" +msgstr "Jobs no último mês" + +msgid "Jobs for last week" +msgstr "Jobs na última semana" + +msgid "Jobs for last year" +msgstr "Jobs no último ano" + msgid "LFSStatus|Disabled" msgstr "Desabilitado" @@ -553,6 +617,21 @@ msgstr "Agendamento da Pipeline" msgid "Pipeline Schedules" msgstr "Agendamentos da Pipeline" +msgid "PipelineCharts|Failed:" +msgstr "PipelineCharts|Falhou:" + +msgid "PipelineCharts|Overall statistics" +msgstr "PipelineCharts|Estatísticas gerais" + +msgid "PipelineCharts|Success ratio:" +msgstr "PipelineCharts|Taxa de sucesso:" + +msgid "PipelineCharts|Successful:" +msgstr "PipelineCharts|Sucesso:" + +msgid "PipelineCharts|Total:" +msgstr "PipelineCharts|Total:" + msgid "PipelineSchedules|Activated" msgstr "Ativado" @@ -583,6 +662,18 @@ msgstr "Destino" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Personalizado" +msgid "Pipelines" +msgstr "Pipelines" + +msgid "Pipelines charts" +msgstr "Gráficos de pipelines" + +msgid "Pipeline|all" +msgstr "Pipeline|todos" + +msgid "Pipeline|success" +msgstr "Pipeline|sucesso" + msgid "Pipeline|with stage" msgstr "com etapa" @@ -713,10 +804,10 @@ msgstr "Selecionar fuso horário" msgid "Select target branch" msgstr "Selecionar branch de destino" -msgid "Set a password on your account to pull or push via %{protocol}" +msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" "Defina uma senha para sua conta para aceitar ou entregar código via " -"%{protocol}" +"%{protocol}." msgid "Set up CI" msgstr "Configurar CI" @@ -1032,9 +1123,15 @@ msgstr "Enviar Novo Arquivo" msgid "Upload file" msgstr "Enviar arquivo" +msgid "UploadLink|click to upload" +msgstr "UploadLink|clique para fazer upload" + msgid "Use your global notification setting" msgstr "Utilizar configuração de notificação global" +msgid "View open merge request" +msgstr "Ver merge request aberto" + msgid "VisibilityLevel|Internal" msgstr "Interno" From defce265003fdc555534ffd29e5d7a2aed098642 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 19 Jul 2017 10:56:11 +0000 Subject: [PATCH 106/130] Resolve "Clarify k8s service keys" --- .../project_services/kubernetes_service.rb | 20 +++++++++---------- .../33741-clarify-k8s-service-keys.yml | 5 +++++ doc/user/project/integrations/kubernetes.md | 4 ++-- 3 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/33741-clarify-k8s-service-keys.yml diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 62f7c057c5b..dee99bbb859 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -58,22 +58,22 @@ class KubernetesService < DeploymentService def fields [ - { type: 'text', - name: 'namespace', - title: 'Kubernetes namespace', - placeholder: namespace_placeholder }, { type: 'text', name: 'api_url', title: 'API URL', placeholder: 'Kubernetes API URL, like https://kube.example.com/' }, - { type: 'text', - name: 'token', - title: 'Service token', - placeholder: 'Service token' }, { type: 'textarea', name: 'ca_pem', - title: 'Custom CA bundle', - placeholder: 'Certificate Authority bundle (PEM format)' } + title: 'CA Certificate', + placeholder: 'Certificate Authority bundle (PEM format)' }, + { type: 'text', + name: 'namespace', + title: 'Project namespace (optional/unique)', + placeholder: namespace_placeholder }, + { type: 'text', + name: 'token', + title: 'Token', + placeholder: 'Service token' } ] end diff --git a/changelogs/unreleased/33741-clarify-k8s-service-keys.yml b/changelogs/unreleased/33741-clarify-k8s-service-keys.yml new file mode 100644 index 00000000000..91142a0d580 --- /dev/null +++ b/changelogs/unreleased/33741-clarify-k8s-service-keys.yml @@ -0,0 +1,5 @@ +--- +title: Clarifies and rearranges the input variables on the kubernetes integration + page and adjusts the docs slightly to meet the same order +merge_request: !12188 +author: diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md index bfe2672e098..f4000523938 100644 --- a/doc/user/project/integrations/kubernetes.md +++ b/doc/user/project/integrations/kubernetes.md @@ -19,10 +19,10 @@ of your project and select the **Kubernetes** service to configure it. The Kubernetes service takes the following arguments: -1. Kubernetes namespace 1. API URL -1. Service token 1. Custom CA bundle +1. Kubernetes namespace +1. Service token The API URL is the URL that GitLab uses to access the Kubernetes API. Kubernetes exposes several APIs - we want the "base" URL that is common to all of them, From a6d1e92d98e71098c5a32999294bcdce6c7a092d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 19 Jul 2017 13:19:27 +0200 Subject: [PATCH 107/130] Isolate stage_id reference clean up migration This addreses a review remarks discussed in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12785/diffs#note_35276344 --- .../clean_stage_id_reference_migration_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb index 1b8d044ed61..c2072f2672d 100644 --- a/spec/migrations/clean_stage_id_reference_migration_spec.rb +++ b/spec/migrations/clean_stage_id_reference_migration_spec.rb @@ -1,17 +1,17 @@ require 'spec_helper' require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb') -require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background') describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do - let(:migration) { MigrateStageIdReferenceInBackground::MIGRATION } + let(:migration) { 'MigrateBuildStageIdReference' } context 'when there are pending background migrations' do it 'processes pending jobs synchronously' do Sidekiq::Testing.disable! do - BackgroundMigrationWorker.perform_in(2.minutes, migration, [1]) - BackgroundMigrationWorker.perform_async(migration, [1]) + BackgroundMigrationWorker.perform_in(2.minutes, migration, [1, 1]) + BackgroundMigrationWorker.perform_async(migration, [1, 1]) - expect(Gitlab::BackgroundMigration).to receive(:perform).twice + expect(Gitlab::BackgroundMigration) + .to receive(:perform).twice.and_call_original migrate! end From 8df2bb3b9a989246e7ac292e872f340f5f9665f9 Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Wed, 19 Jul 2017 12:32:43 +0000 Subject: [PATCH 108/130] Add Simplified Chinese translations of Pipeline Schedules --- locale/zh_CN/gitlab.po | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index b7a88aadeb9..47b72d7be1a 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -4,11 +4,11 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-10 09:58-0400\n" +"PO-Revision-Date: 2017-07-12 06:23-0400\n" "Last-Translator: Huang Tao \n" "Language-Team: Chinese (China) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-CN\n" @@ -621,6 +621,12 @@ msgstr "所有" msgid "PipelineSchedules|Inactive" msgstr "未启用" +msgid "PipelineSchedules|Input variable key" +msgstr "输入变量名" + +msgid "PipelineSchedules|Input variable value" +msgstr "输入变量值" + msgid "PipelineSchedules|Next Run" msgstr "下次运行时间" @@ -630,12 +636,18 @@ msgstr "无" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "为此流水线提供简短描述" +msgid "PipelineSchedules|Remove variable row" +msgstr "删除变量" + msgid "PipelineSchedules|Take ownership" -msgstr "取得所有者" +msgstr "取得所有权" msgid "PipelineSchedules|Target" msgstr "目标" +msgid "PipelineSchedules|Variables" +msgstr "变量" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "自定义" @@ -1085,6 +1097,14 @@ msgstr "该阶段的数据不足,无法显示。" msgid "Withdraw Access Request" msgstr "取消权限申请" +msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "即将删除 %{group_name}。\n" +"已删除的群组无法恢复!\n" +"确定继续吗?" + msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" From c2fbac8137844ec0053e81da448e54a434f36403 Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Wed, 19 Jul 2017 12:33:24 +0000 Subject: [PATCH 109/130] Add Traditional Chinese in HongKong translations of Pipeline Schedules --- locale/zh_HK/gitlab.po | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index f6add31db99..8a4e6da4ea9 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -3,13 +3,13 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-06 11:26-0400\n" +"PO-Revision-Date: 2017-07-12 06:32-0400\n" "Last-Translator: Huang Tao \n" -"Language-Team: Chinese (Hong Kong SAR China)\n" +"Language-Team: Chinese (Hong Kong SAR China) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-HK\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" @@ -620,6 +620,12 @@ msgstr "所有" msgid "PipelineSchedules|Inactive" msgstr "未啟用" +msgid "PipelineSchedules|Input variable key" +msgstr "輸入變量名" + +msgid "PipelineSchedules|Input variable value" +msgstr "輸入變量值" + msgid "PipelineSchedules|Next Run" msgstr "下次運行時間" @@ -629,12 +635,18 @@ msgstr "無" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "為此流水線提供簡短描述" +msgid "PipelineSchedules|Remove variable row" +msgstr "刪除變量" + msgid "PipelineSchedules|Take ownership" -msgstr "取得所有者" +msgstr "取得所有權" msgid "PipelineSchedules|Target" msgstr "目標" +msgid "PipelineSchedules|Variables" +msgstr "變量" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "自定義" @@ -1084,6 +1096,14 @@ msgstr "該階段的數據不足,無法顯示。" msgid "Withdraw Access Request" msgstr "取消權限申请" +msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "即將刪除 %{group_name}。\n" +"已刪除的群組無法恢復!\n" +"確定繼續嗎?" + msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" From 40e7a528d9d93579de5009c62dce8afe8fc8aedd Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Wed, 19 Jul 2017 12:35:35 +0000 Subject: [PATCH 110/130] Add Esperanto translations of Pipeline Schedules --- locale/eo/gitlab.po | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 5218f6ae7b9..62dbc2621f4 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -4,11 +4,11 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-05 08:18-0400\n" +"PO-Revision-Date: 2017-07-13 08:46-0400\n" "Last-Translator: Lyubomir Vasilev \n" "Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n" "Language: eo\n" @@ -642,6 +642,12 @@ msgstr "Ĉiuj" msgid "PipelineSchedules|Inactive" msgstr "Malŝaltitaj" +msgid "PipelineSchedules|Input variable key" +msgstr "Entajpu ŝlosilon por la variablo" + +msgid "PipelineSchedules|Input variable value" +msgstr "Entajpu la valoron de la variablo" + msgid "PipelineSchedules|Next Run" msgstr "Sekvanta plenumo" @@ -651,12 +657,18 @@ msgstr "Nenio" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Entajpu mallongan priskribon pri ĉi tiu ĉenstablo" +msgid "PipelineSchedules|Remove variable row" +msgstr "Forigi la variablan linion" + msgid "PipelineSchedules|Take ownership" msgstr "Akiri posedon" msgid "PipelineSchedules|Target" msgstr "Celo" +msgid "PipelineSchedules|Variables" +msgstr "Variabloj" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Propra" @@ -1150,6 +1162,15 @@ msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon." msgid "Withdraw Access Request" msgstr "Nuligi la peton pri atingeblo" +msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"Vi forigos „%{group_name}“.\n" +"Oni NE POVAS malfari la forigon de grupo!\n" +"Ĉu vi estas ABSOLUTE certa?" + msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" From 5f66d295a0deaef904b647ee01e87b5fd74506b6 Mon Sep 17 00:00:00 2001 From: Huang Tao Date: Wed, 19 Jul 2017 12:37:09 +0000 Subject: [PATCH 111/130] Add Bulgarian translations of Pipeline Schedules --- locale/bg/gitlab.po | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 0cc16404e1b..1774c911d71 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -4,11 +4,11 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-28 13:32+0200\n" +"POT-Creation-Date: 2017-07-05 08:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-05 08:18-0400\n" +"PO-Revision-Date: 2017-07-13 08:13-0400\n" "Last-Translator: Lyubomir Vasilev \n" "Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n" "Language: bg\n" @@ -641,6 +641,12 @@ msgstr "Всички" msgid "PipelineSchedules|Inactive" msgstr "Неактивно" +msgid "PipelineSchedules|Input variable key" +msgstr "Въведете ключ за променливата" + +msgid "PipelineSchedules|Input variable value" +msgstr "Въведете стойността на променливата" + msgid "PipelineSchedules|Next Run" msgstr "Следващо изпълнение" @@ -650,12 +656,18 @@ msgstr "Нищо" msgid "PipelineSchedules|Provide a short description for this pipeline" msgstr "Въведете кратко описание за тази схема" +msgid "PipelineSchedules|Remove variable row" +msgstr "Премахване на реда за променлива" + msgid "PipelineSchedules|Take ownership" msgstr "Поемане на собствеността" msgid "PipelineSchedules|Target" msgstr "Цел" +msgid "PipelineSchedules|Variables" +msgstr "Променливи" + msgid "PipelineSheduleIntervalPattern|Custom" msgstr "собствен" @@ -1148,6 +1160,15 @@ msgstr "Няма достатъчно данни за този етап." msgid "Withdraw Access Request" msgstr "Оттегляне на заявката за достъп" +msgid "" +"You are going to remove %{group_name}.\n" +"Removed groups CANNOT be restored!\n" +"Are you ABSOLUTELY sure?" +msgstr "" +"На път сте да премахнете „%{group_name}“.\n" +"Ако я премахнете, групата НЕ може да бъде възстановена!\n" +"НАИСТИНА ли искате това?" + msgid "" "You are going to remove %{project_name_with_namespace}.\n" "Removed project CANNOT be restored!\n" From 53c2f5538b1ca4bbf7f8c203b895643371b9af08 Mon Sep 17 00:00:00 2001 From: Ben Kochie Date: Tue, 18 Jul 2017 14:22:23 +0200 Subject: [PATCH 112/130] Add versions to Prometheus metrics doc Document the original release of each Prometheus metric. --- .../monitoring/prometheus/gitlab_metrics.md | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 6023112d615..7072ab5d02a 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -26,25 +26,25 @@ server, because the embedded server configuration is overwritten once every In this experimental phase, only a few metrics are available: -| Metric | Type | Description | -| --------------------------------- | --------- | ----------- | -| db_ping_timeout | Gauge | Whether or not the last database ping timed out | -| db_ping_success | Gauge | Whether or not the last database ping succeeded | -| db_ping_latency_seconds | Gauge | Round trip time of the database ping | -| filesystem_access_latency_seconds | Gauge | Latency in accessing a specific filesystem | -| filesystem_accessible | Gauge | Whether or not a specific filesystem is accessible | -| filesystem_write_latency_seconds | Gauge | Write latency of a specific filesystem | -| filesystem_writable | Gauge | Whether or not the filesystem is writable | -| filesystem_read_latency_seconds | Gauge | Read latency of a specific filesystem | -| filesystem_readable | Gauge | Whether or not the filesystem is readable | -| http_requests_total | Counter | Rack request count | -| http_request_duration_seconds | Histogram | HTTP response time from rack middleware | -| pipelines_created_total | Counter | Counter of pipelines created | -| rack_uncaught_errors_total | Counter | Rack connections handling uncaught errors count | -| redis_ping_timeout | Gauge | Whether or not the last redis ping timed out | -| redis_ping_success | Gauge | Whether or not the last redis ping succeeded | -| redis_ping_latency_seconds | Gauge | Round trip time of the redis ping | -| user_session_logins_total | Counter | Counter of how many users have logged in | +| Metric | Type | Since | Description | +|:--------------------------------- |:--------- |:----- |:----------- | +| db_ping_timeout | Gauge | 9.4 | Whether or not the last database ping timed out | +| db_ping_success | Gauge | 9.4 | Whether or not the last database ping succeeded | +| db_ping_latency_seconds | Gauge | 9.4 | Round trip time of the database ping | +| filesystem_access_latency_seconds | Gauge | 9.4 | Latency in accessing a specific filesystem | +| filesystem_accessible | Gauge | 9.4 | Whether or not a specific filesystem is accessible | +| filesystem_write_latency_seconds | Gauge | 9.4 | Write latency of a specific filesystem | +| filesystem_writable | Gauge | 9.4 | Whether or not the filesystem is writable | +| filesystem_read_latency_seconds | Gauge | 9.4 | Read latency of a specific filesystem | +| filesystem_readable | Gauge | 9.4 | Whether or not the filesystem is readable | +| http_requests_total | Counter | 9.4 | Rack request count | +| http_request_duration_seconds | Histogram | 9.4 | HTTP response time from rack middleware | +| pipelines_created_total | Counter | 9.4 | Counter of pipelines created | +| rack_uncaught_errors_total | Counter | 9.4 | Rack connections handling uncaught errors count | +| redis_ping_timeout | Gauge | 9.4 | Whether or not the last redis ping timed out | +| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded | +| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping | +| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in | [← Back to the main Prometheus page](index.md) From 2cc063e5926337b2d0d168d3de5a865b33d1ec58 Mon Sep 17 00:00:00 2001 From: Jarka Kadlecova Date: Wed, 19 Jul 2017 09:07:17 +0200 Subject: [PATCH 113/130] Add github imported projects count to usage data --- changelogs/unreleased/34563-usage-ping-github.yml | 4 ++++ lib/gitlab/usage_data.rb | 1 + spec/lib/gitlab/usage_data_spec.rb | 1 + 3 files changed, 6 insertions(+) create mode 100644 changelogs/unreleased/34563-usage-ping-github.yml diff --git a/changelogs/unreleased/34563-usage-ping-github.yml b/changelogs/unreleased/34563-usage-ping-github.yml new file mode 100644 index 00000000000..3ab982beea3 --- /dev/null +++ b/changelogs/unreleased/34563-usage-ping-github.yml @@ -0,0 +1,4 @@ +--- +title: Add GitHub imported projects count to usage data +merge_request: +author: diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index f19b325a126..dba071d7e47 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -39,6 +39,7 @@ module Gitlab notes: Note.count, pages_domains: PagesDomain.count, projects: Project.count, + projects_imported_from_github: Project.where(import_type: 'github').count, projects_prometheus_active: PrometheusService.active.count, protected_branches: ProtectedBranch.count, releases: Release.count, diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index c6718827028..daf097f8d51 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -48,6 +48,7 @@ describe Gitlab::UsageData do milestones notes projects + projects_imported_from_github projects_prometheus_active pages_domains protected_branches From 3d08a309a5a155bc096bb5812e14e9eb6e57dfab Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Tue, 18 Jul 2017 09:43:17 -0700 Subject: [PATCH 114/130] 35209 Add wip message to new navigation preference section --- app/assets/stylesheets/pages/profile.scss | 15 +++++++++++++++ app/views/profiles/preferences/show.html.haml | 6 ++++++ .../35209-add-wip-info-new-nav-pref.yml | 4 ++++ 3 files changed, 25 insertions(+) create mode 100644 changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 235c475ff26..22672614e0d 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -376,3 +376,18 @@ table.u2f-registrations { } } } + +.nav-wip { + border: 1px solid $blue-500; + background: $blue-25; + padding: $gl-padding; + margin-bottom: $gl-padding; + + a { + color: $blue-500; + } + + p:last-child { + margin-bottom: 0; + } +} diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index bd602071384..9aed498a8a0 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -24,6 +24,12 @@ %p This setting allows you to turn on or off the new upcoming navigation concept. .col-lg-8.syntax-theme + .nav-wip + %p + The new navigation is currently a work-in-progress concept and is currently only usable on wide-screens. There are a number of improvements that we are working on in order to further refine our navigation. + %p + %a{ href: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/32794', target: 'blank' } Learn more + about the improvements that are coming soon! = label_tag do .preview= image_tag "old_nav.png" %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? } diff --git a/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml b/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml new file mode 100644 index 00000000000..680e1cd8222 --- /dev/null +++ b/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml @@ -0,0 +1,4 @@ +--- +title: Add wip message to new navigation preference section +merge_request: +author: From 7a7bcd1716b4e9489ac31ad9e6d8ccdeb961b1ae Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 19 Jul 2017 12:41:34 -0500 Subject: [PATCH 115/130] Respect blockquote line breaks in markdown --- app/assets/stylesheets/framework/typography.scss | 5 ++++- .../unreleased/33770-respect-blockquote-line-breaks.yml | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/33770-respect-blockquote-line-breaks.yml diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 77b7d901f9a..8a58c1ed567 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -116,9 +116,12 @@ blockquote p { color: $gl-grayish-blue !important; - margin: 0; font-size: inherit; line-height: 1.5; + + &:last-child { + margin: 0; + } } p { diff --git a/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml b/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml new file mode 100644 index 00000000000..3a45ad88270 --- /dev/null +++ b/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml @@ -0,0 +1,4 @@ +--- +title: Respect blockquote line breaks in markdown +merge_request: +author: From 0d6c7a423dd5e740ab3868642b2e04a15a96a85a Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 19 Jul 2017 21:46:32 +0100 Subject: [PATCH 116/130] Update CHANGELOG.md for 9.3.8 [ci skip] --- CHANGELOG.md | 7 +++++++ .../unreleased/adam-external-issue-references-spike.yml | 4 ---- 2 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 changelogs/unreleased/adam-external-issue-references-spike.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index b6f955553e1..e12665bf4d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 9.3.8 (2017-07-19) + +- Improve support for external issue references. !12485 +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Use uploads/system directory for personal snippets. +- Remove uploads/appearance symlink. A leftover from a previous migration. + ## 9.3.7 (2017-07-18) - Prevent bad data being added to application settings when Redis is unavailable. !12750 diff --git a/changelogs/unreleased/adam-external-issue-references-spike.yml b/changelogs/unreleased/adam-external-issue-references-spike.yml deleted file mode 100644 index aeec6688425..00000000000 --- a/changelogs/unreleased/adam-external-issue-references-spike.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Improve support for external issue references -merge_request: 12485 -author: From 88cd9ff266508b40eb70dcb694f5036eb9a59cad Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 19 Jul 2017 22:00:43 +0100 Subject: [PATCH 117/130] Update CHANGELOG.md for 9.2.8 [ci skip] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e12665bf4d9..35aa5b5aff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -270,6 +270,13 @@ entry. - Remove foreigh key on ci_trigger_schedules only if it exists. - Allow translation of Pipeline Schedules. +## 9.2.8 (2017-07-19) + +- Improve support for external issue references. !12485 +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Fix incorrect project authorizations. +- Remove uploads/appearance symlink. A leftover from a previous migration. + ## 9.2.7 (2017-06-21) - Reinstate is_admin flag in users api when authenticated user is an admin. !12211 (rickettm) From f01c122b61db3944ac7fdb416c722a51ded11202 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 19 Jul 2017 22:13:32 +0100 Subject: [PATCH 118/130] Update CHANGELOG.md for 8.17.7 [ci skip] --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35aa5b5aff8..c6a40dce6cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1203,6 +1203,11 @@ entry. - Change development tanuki favicon colors to match logo color order. - API issues - support filtering by iids. +## 8.17.7 (2017-07-19) + +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Fix incorrect project authorizations. + ## 8.17.6 (2017-05-05) - Enforce project features when searching blobs and wikis. From c929e39b4ae5b6555b2fe1359b4ac7143ac4bddd Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 19 Jul 2017 23:34:15 +0100 Subject: [PATCH 119/130] Update CHANGELOG.md for 9.1.8 [ci skip] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a40dce6cd..d977e5f8cf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -521,6 +521,13 @@ entry. - Fix preemptive scroll bar on user activity calendar. - Pipeline chat notifications convert seconds to minutes and hours. +## 9.1.8 (2017-07-19) + +- Improve support for external issue references. !12485 +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Fix incorrect project authorizations. +- Remove uploads/appearance symlink. A leftover from a previous migration. + ## 9.1.7 (2017-06-07) - No changes. From 55453215dffd1fc510caed07e2b155756de93401 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 19 Jul 2017 23:48:51 +0100 Subject: [PATCH 120/130] Update CHANGELOG.md for 9.0.11 [ci skip] --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d977e5f8cf0..de3b4b0d3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -840,6 +840,12 @@ entry. - Only send chat notifications for the default branch. - Don't fill in the default kubernetes namespace. +## 9.0.11 (2017-07-19) + +- Renders 404 if given project is not readable by the user on Todos dashboard. +- Fix incorrect project authorizations. +- Remove uploads/appearance symlink. A leftover from a previous migration. + ## 9.0.10 (2017-06-07) - No changes. From 1ec5136128364f36f88713f7678188b5d478e6c9 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Wed, 19 Jul 2017 01:55:48 +0900 Subject: [PATCH 121/130] Hide description about protected branches to non-member --- app/views/projects/branches/index.html.haml | 7 ++++--- .../35253-desc-protected-branches-for-non-member.yml | 4 ++++ spec/features/projects/branches_spec.rb | 9 ++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 8bc1996452b..73583c6bbc2 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -4,9 +4,10 @@ %div{ class: container_class } .top-area.adjust - .nav-text - Protected branches can be managed in - = link_to 'project settings', project_protected_branches_path(@project) + - if can?(current_user, :admin_project, @project) + .nav-text + Protected branches can be managed in + = link_to 'project settings', project_protected_branches_path(@project) .nav-controls = form_tag(filter_branches_path, method: :get) do diff --git a/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml b/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml new file mode 100644 index 00000000000..9b2a66da1c3 --- /dev/null +++ b/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml @@ -0,0 +1,4 @@ +--- +title: Hide description about protected branches to non-member +merge_request: 12945 +author: Takuya Noguchi diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 4fae324d8d5..d18cd3d6adc 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -24,7 +24,6 @@ describe 'Branches', feature: true do repository.branches_sorted_by(:name).first(20).each do |branch| expect(page).to have_content("#{branch.name}") end - expect(page).to have_content("Protected branches can be managed in project settings") end it 'sorts the branches by name' do @@ -130,6 +129,14 @@ describe 'Branches', feature: true do project.team << [user, :master] end + describe 'Initial branches page' do + it 'shows description for admin' do + visit project_branches_path(project) + + expect(page).to have_content("Protected branches can be managed in project settings") + end + end + describe 'Delete protected branch' do before do visit project_protected_branches_path(project) From 3a7b724f6a03a19719b05b30e1e76e536230bcca Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 22 Jun 2017 01:03:26 +0000 Subject: [PATCH 122/130] Merge branch 'bvl-remove-appearance-symlink' into 'security-9-3' Remove the `appearance` symlink that was previously missed See merge request !2124 --- .../bvl-remove-appearance-symlink.yml | 4 ++ .../20170406111121_clean_upload_symlinks.rb | 2 +- ...0170613111224_clean_appearance_symlinks.rb | 52 +++++++++++++++++++ .../clean_appearance_symlinks_spec.rb | 46 ++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/bvl-remove-appearance-symlink.yml create mode 100644 db/post_migrate/20170613111224_clean_appearance_symlinks.rb create mode 100644 spec/migrations/clean_appearance_symlinks_spec.rb diff --git a/changelogs/unreleased/bvl-remove-appearance-symlink.yml b/changelogs/unreleased/bvl-remove-appearance-symlink.yml new file mode 100644 index 00000000000..2b1c188528a --- /dev/null +++ b/changelogs/unreleased/bvl-remove-appearance-symlink.yml @@ -0,0 +1,4 @@ +--- +title: Remove uploads/appearance symlink. A leftover from a previous migration. +merge_request: +author: diff --git a/db/post_migrate/20170406111121_clean_upload_symlinks.rb b/db/post_migrate/20170406111121_clean_upload_symlinks.rb index 3ac9a6c10bc..fc3a4acc0bb 100644 --- a/db/post_migrate/20170406111121_clean_upload_symlinks.rb +++ b/db/post_migrate/20170406111121_clean_upload_symlinks.rb @@ -6,7 +6,7 @@ class CleanUploadSymlinks < ActiveRecord::Migration disable_ddl_transaction! DOWNTIME = false - DIRECTORIES_TO_MOVE = %w(user project note group appeareance) + DIRECTORIES_TO_MOVE = %w(user project note group appearance) def up return unless file_storage? diff --git a/db/post_migrate/20170613111224_clean_appearance_symlinks.rb b/db/post_migrate/20170613111224_clean_appearance_symlinks.rb new file mode 100644 index 00000000000..acb895e426f --- /dev/null +++ b/db/post_migrate/20170613111224_clean_appearance_symlinks.rb @@ -0,0 +1,52 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CleanAppearanceSymlinks < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + return unless file_storage? + + symlink_location = File.join(old_upload_dir, dir) + + return unless File.symlink?(symlink_location) + say "removing symlink: #{symlink_location}" + FileUtils.rm(symlink_location) + end + + def down + return unless file_storage? + + symlink = File.join(old_upload_dir, dir) + destination = File.join(new_upload_dir, dir) + + return if File.directory?(symlink) + return unless File.directory?(destination) + + say "Creating symlink #{symlink} -> #{destination}" + FileUtils.ln_s(destination, symlink) + end + + def file_storage? + CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File + end + + def dir + 'appearance' + end + + def base_directory + Rails.root + end + + def old_upload_dir + File.join(base_directory, "public", "uploads") + end + + def new_upload_dir + File.join(base_directory, "public", "uploads", "system") + end +end diff --git a/spec/migrations/clean_appearance_symlinks_spec.rb b/spec/migrations/clean_appearance_symlinks_spec.rb new file mode 100644 index 00000000000..9225dc0d894 --- /dev/null +++ b/spec/migrations/clean_appearance_symlinks_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170613111224_clean_appearance_symlinks.rb') + +describe CleanAppearanceSymlinks do + let(:migration) { described_class.new } + let(:test_dir) { File.join(Rails.root, "tmp", "tests", "clean_appearance_test") } + let(:uploads_dir) { File.join(test_dir, "public", "uploads") } + let(:new_uploads_dir) { File.join(uploads_dir, "system") } + let(:original_path) { File.join(new_uploads_dir, 'appearance') } + let(:symlink_path) { File.join(uploads_dir, 'appearance') } + + before do + FileUtils.remove_dir(test_dir) if File.directory?(test_dir) + FileUtils.mkdir_p(uploads_dir) + allow(migration).to receive(:base_directory).and_return(test_dir) + allow(migration).to receive(:say) + end + + describe "#up" do + before do + FileUtils.mkdir_p(original_path) + FileUtils.ln_s(original_path, symlink_path) + end + + it 'removes the symlink' do + migration.up + + expect(File.symlink?(symlink_path)).to be(false) + end + end + + describe '#down' do + before do + FileUtils.mkdir_p(File.join(original_path)) + FileUtils.touch(File.join(original_path, 'dummy.file')) + end + + it 'creates a symlink' do + expected_path = File.join(symlink_path, "dummy.file") + migration.down + + expect(File.exist?(expected_path)).to be(true) + expect(File.symlink?(symlink_path)).to be(true) + end + end +end From 88df076fae9568314473de5fa6a0086c33663869 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 27 Jun 2017 10:53:06 +0000 Subject: [PATCH 123/130] Merge branch '33359-pers-snippet-files-location' into 'security-9-3' Use uploads/system directory for personal snippets See merge request !2123 --- app/uploaders/personal_file_uploader.rb | 4 + .../33359-pers-snippet-files-location.yml | 4 + config/routes/uploads.rb | 4 +- ...0612071012_move_personal_snippets_files.rb | 91 +++++++++ spec/controllers/snippets_controller_spec.rb | 8 +- spec/controllers/uploads_controller_spec.rb | 4 +- .../snippets/user_creates_snippet_spec.rb | 6 +- .../snippets/user_edits_snippet_spec.rb | 2 +- .../move_personal_snippets_files_spec.rb | 180 ++++++++++++++++++ spec/uploaders/file_mover_spec.rb | 14 +- spec/uploaders/personal_file_uploader_spec.rb | 4 +- 11 files changed, 300 insertions(+), 21 deletions(-) create mode 100644 changelogs/unreleased/33359-pers-snippet-files-location.yml create mode 100644 db/post_migrate/20170612071012_move_personal_snippets_files.rb create mode 100644 spec/migrations/move_personal_snippets_files_spec.rb diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb index 7f857765fbf..ef70871624b 100644 --- a/app/uploaders/personal_file_uploader.rb +++ b/app/uploaders/personal_file_uploader.rb @@ -3,6 +3,10 @@ class PersonalFileUploader < FileUploader File.join(CarrierWave.root, model_path(model)) end + def self.base_dir + File.join(root_dir, 'system') + end + private def secure_url diff --git a/changelogs/unreleased/33359-pers-snippet-files-location.yml b/changelogs/unreleased/33359-pers-snippet-files-location.yml new file mode 100644 index 00000000000..22fa301cb5e --- /dev/null +++ b/changelogs/unreleased/33359-pers-snippet-files-location.yml @@ -0,0 +1,4 @@ +--- +title: Use uploads/system directory for personal snippets +merge_request: +author: diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb index ed5476c8f71..e9c9aa8b2f9 100644 --- a/config/routes/uploads.rb +++ b/config/routes/uploads.rb @@ -5,12 +5,12 @@ scope path: :uploads do constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } # show uploads for models, snippets (notes) available for now - get ':model/:id/:secret/:filename', + get 'system/:model/:id/:secret/:filename', to: 'uploads#show', constraints: { model: /personal_snippet/, id: /\d+/, filename: /[^\/]+/ } # show temporary uploads - get 'temp/:secret/:filename', + get 'system/temp/:secret/:filename', to: 'uploads#show', constraints: { filename: /[^\/]+/ } diff --git a/db/post_migrate/20170612071012_move_personal_snippets_files.rb b/db/post_migrate/20170612071012_move_personal_snippets_files.rb new file mode 100644 index 00000000000..33043364bde --- /dev/null +++ b/db/post_migrate/20170612071012_move_personal_snippets_files.rb @@ -0,0 +1,91 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. +class MovePersonalSnippetsFiles < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + return unless file_storage? + + @source_relative_location = File.join('/uploads', 'personal_snippet') + @destination_relative_location = File.join('/uploads', 'system', 'personal_snippet') + + move_personal_snippet_files + end + + def down + return unless file_storage? + + @source_relative_location = File.join('/uploads', 'system', 'personal_snippet') + @destination_relative_location = File.join('/uploads', 'personal_snippet') + + move_personal_snippet_files + end + + def move_personal_snippet_files + query = "SELECT uploads.path, uploads.model_id, snippets.description FROM uploads "\ + "INNER JOIN snippets ON snippets.id = uploads.model_id WHERE uploader = 'PersonalFileUploader'" + select_all(query).each do |upload| + secret = upload['path'].split('/')[0] + file_name = upload['path'].split('/')[1] + + next unless move_file(upload['model_id'], secret, file_name) + update_markdown(upload['model_id'], secret, file_name, upload['description']) + end + end + + def move_file(snippet_id, secret, file_name) + source_dir = File.join(base_directory, @source_relative_location, snippet_id.to_s, secret) + destination_dir = File.join(base_directory, @destination_relative_location, snippet_id.to_s, secret) + + source_file_path = File.join(source_dir, file_name) + destination_file_path = File.join(destination_dir, file_name) + + unless File.exist?(source_file_path) + say "Source file `#{source_file_path}` doesn't exist. Skipping." + return + end + + say "Moving file #{source_file_path} -> #{destination_file_path}" + + FileUtils.mkdir_p(destination_dir) + FileUtils.move(source_file_path, destination_file_path) + + true + end + + def update_markdown(snippet_id, secret, file_name, description) + source_markdown_path = File.join(@source_relative_location, snippet_id.to_s, secret, file_name) + destination_markdown_path = File.join(@destination_relative_location, snippet_id.to_s, secret, file_name) + + source_markdown = "](#{source_markdown_path})" + destination_markdown = "](#{destination_markdown_path})" + + if description.present? + description = description.gsub(source_markdown, destination_markdown) + quoted_description = quote_string(description) + + execute("UPDATE snippets SET description = '#{quoted_description}', description_html = NULL "\ + "WHERE id = #{snippet_id}") + end + + query = "SELECT id, note FROM notes WHERE noteable_id = #{snippet_id} "\ + "AND noteable_type = 'Snippet' AND note IS NOT NULL" + select_all(query).each do |note| + text = note['note'].gsub(source_markdown, destination_markdown) + quoted_text = quote_string(text) + + execute("UPDATE notes SET note = '#{quoted_text}', note_html = NULL WHERE id = #{note['id']}") + end + end + + def base_directory + File.join(Rails.root, 'public') + end + + def file_storage? + CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File + end +end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 15416a89017..475ceda11fe 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -186,8 +186,8 @@ describe SnippetsController do end context 'when the snippet description contains a file' do - let(:picture_file) { '/temp/secret56/picture.jpg' } - let(:text_file) { '/temp/secret78/text.txt' } + let(:picture_file) { '/system/temp/secret56/picture.jpg' } + let(:text_file) { '/system/temp/secret78/text.txt' } let(:description) do "Description with picture: ![picture](/uploads#{picture_file}) and "\ "text: [text.txt](/uploads#{text_file})" @@ -208,8 +208,8 @@ describe SnippetsController do snippet = subject expected_description = "Description with picture: "\ - "![picture](/uploads/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\ - "text: [text.txt](/uploads/personal_snippet/#{snippet.id}/secret78/text.txt)" + "![picture](/uploads/system/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\ + "text: [text.txt](/uploads/system/personal_snippet/#{snippet.id}/secret78/text.txt)" expect(snippet.description).to eq(expected_description) end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 01a0659479b..96f719e2b82 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -102,7 +102,7 @@ describe UploadsController do subject expect(response.body).to match '\"alt\":\"rails_sample\"' - expect(response.body).to match "\"url\":\"/uploads/temp" + expect(response.body).to match "\"url\":\"/uploads/system/temp" end it 'does not create an Upload record' do @@ -119,7 +119,7 @@ describe UploadsController do subject expect(response.body).to match '\"alt\":\"doc_sample.txt\"' - expect(response.body).to match "\"url\":\"/uploads/temp" + expect(response.body).to match "\"url\":\"/uploads/system/temp" end it 'does not create an Upload record' do diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index 57dec14b480..698d3b5d3e3 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -41,7 +41,7 @@ feature 'User creates snippet', :js, feature: true do expect(page).to have_content('My Snippet') link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/temp/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/uploads/system/temp/\h{32}/banana_sample\.gif\z}) visit(link) expect(page.status_code).to eq(200) @@ -59,7 +59,7 @@ feature 'User creates snippet', :js, feature: true do wait_for_requests link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/uploads/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) visit(link) expect(page.status_code).to eq(200) @@ -84,7 +84,7 @@ feature 'User creates snippet', :js, feature: true do end expect(page).to have_content('Hello World!') link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/uploads/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) visit(link) expect(page.status_code).to eq(200) diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index cff64423873..c9f9741b4bb 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -33,7 +33,7 @@ feature 'User edits snippet', :js, feature: true do wait_for_requests link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] - expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z}) + expect(link).to match(%r{/uploads/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z}) end it 'updates the snippet to make it internal' do diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb new file mode 100644 index 00000000000..8505c7bf3e3 --- /dev/null +++ b/spec/migrations/move_personal_snippets_files_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170612071012_move_personal_snippets_files.rb') + +describe MovePersonalSnippetsFiles do + let(:migration) { described_class.new } + let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_snippet_files_test") } + let(:uploads_dir) { File.join(test_dir, 'uploads') } + let(:new_uploads_dir) { File.join(uploads_dir, 'system') } + + before do + allow(CarrierWave).to receive(:root).and_return(test_dir) + allow(migration).to receive(:base_directory).and_return(test_dir) + FileUtils.remove_dir(test_dir) if File.directory?(test_dir) + allow(migration).to receive(:say) + end + + describe "#up" do + let(:snippet) do + snippet = create(:personal_snippet) + create_upload('picture.jpg', snippet) + snippet.update(description: markdown_linking_file('picture.jpg', snippet)) + snippet + end + + let(:snippet_with_missing_file) do + snippet = create(:snippet) + create_upload('picture.jpg', snippet, create_file: false) + snippet.update(description: markdown_linking_file('picture.jpg', snippet)) + snippet + end + + it 'moves the files' do + source_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet)) + destination_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet)) + + migration.up + + expect(File.exist?(source_path)).to be_falsy + expect(File.exist?(destination_path)).to be_truthy + end + + describe 'updating the markdown' do + it 'includes the new path when the file exists' do + secret = "secret#{snippet.id}" + file_location = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" + + migration.up + + expect(snippet.reload.description).to include(file_location) + end + + it 'does not update the markdown when the file is missing' do + secret = "secret#{snippet_with_missing_file.id}" + file_location = "/uploads/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg" + + migration.up + + expect(snippet_with_missing_file.reload.description).to include(file_location) + end + + it 'updates the note markdown' do + secret = "secret#{snippet.id}" + file_location = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" + markdown = markdown_linking_file('picture.jpg', snippet) + note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") + + migration.up + + expect(note.reload.note).to include(file_location) + end + end + end + + describe "#down" do + let(:snippet) do + snippet = create(:personal_snippet) + create_upload('picture.jpg', snippet, in_new_path: true) + snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true)) + snippet + end + + let(:snippet_with_missing_file) do + snippet = create(:personal_snippet) + create_upload('picture.jpg', snippet, create_file: false, in_new_path: true) + snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true)) + snippet + end + + it 'moves the files' do + source_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet)) + destination_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet)) + + migration.down + + expect(File.exist?(source_path)).to be_falsey + expect(File.exist?(destination_path)).to be_truthy + end + + describe 'updating the markdown' do + it 'includes the new path when the file exists' do + secret = "secret#{snippet.id}" + file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" + + migration.down + + expect(snippet.reload.description).to include(file_location) + end + + it 'keeps the markdown as is when the file is missing' do + secret = "secret#{snippet_with_missing_file.id}" + file_location = "/uploads/system/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg" + + migration.down + + expect(snippet_with_missing_file.reload.description).to include(file_location) + end + + it 'updates the note markdown' do + markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true) + secret = "secret#{snippet.id}" + file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" + note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") + + migration.down + + expect(note.reload.note).to include(file_location) + end + end + end + + describe '#update_markdown' do + it 'escapes sql in the snippet description' do + migration.instance_variable_set('@source_relative_location', '/uploads/personal_snippet') + migration.instance_variable_set('@destination_relative_location', '/uploads/system/personal_snippet') + + secret = '123456789' + filename = 'hello.jpg' + snippet = create(:personal_snippet) + + path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}" + path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}" + description_before = "Hello world; ![image](#{path_before})'; select * from users;" + description_after = "Hello world; ![image](#{path_after})'; select * from users;" + + migration.update_markdown(snippet.id, secret, filename, description_before) + + expect(snippet.reload.description).to eq(description_after) + end + end + + def create_upload(filename, snippet, create_file: true, in_new_path: false) + secret = "secret#{snippet.id}" + absolute_path = if in_new_path + File.join(new_uploads_dir, model_file_path(filename, snippet)) + else + File.join(uploads_dir, model_file_path(filename, snippet)) + end + + if create_file + FileUtils.mkdir_p(File.dirname(absolute_path)) + FileUtils.touch(absolute_path) + end + + create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader) + end + + def markdown_linking_file(filename, snippet, in_new_path: false) + markdown = "![#{filename.split('.')[0]}]" + markdown += '(/uploads' + markdown += '/system' if in_new_path + markdown += "/#{model_file_path(filename, snippet)})" + markdown + end + + def model_file_path(filename, snippet) + secret = "secret#{snippet.id}" + + File.join('personal_snippet', snippet.id.to_s, secret, filename) + end +end diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb index 896cb410ed5..d7c1b390f9a 100644 --- a/spec/uploaders/file_mover_spec.rb +++ b/spec/uploaders/file_mover_spec.rb @@ -4,11 +4,11 @@ describe FileMover do let(:filename) { 'banana_sample.gif' } let(:file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', filename)) } let(:temp_description) do - 'test ![banana_sample](/uploads/temp/secret55/banana_sample.gif) same ![banana_sample]'\ - '(/uploads/temp/secret55/banana_sample.gif)' + 'test ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif) same ![banana_sample]'\ + '(/uploads/system/temp/secret55/banana_sample.gif)' end let(:temp_file_path) { File.join('secret55', filename).to_s } - let(:file_path) { File.join('uploads', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s } + let(:file_path) { File.join('uploads', 'system', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s } let(:snippet) { create(:personal_snippet, description: temp_description) } @@ -28,8 +28,8 @@ describe FileMover do expect(snippet.reload.description) .to eq( - "test ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\ - " same ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)" + "test ![banana_sample](/uploads/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\ + " same ![banana_sample](/uploads/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)" ) end @@ -50,8 +50,8 @@ describe FileMover do expect(snippet.reload.description) .to eq( - "test ![banana_sample](/uploads/temp/secret55/banana_sample.gif)"\ - " same ![banana_sample](/uploads/temp/secret55/banana_sample.gif)" + "test ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif)"\ + " same ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif)" ) end diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb index fb92f2ae3ab..eb55e8ebd24 100644 --- a/spec/uploaders/personal_file_uploader_spec.rb +++ b/spec/uploaders/personal_file_uploader_spec.rb @@ -10,7 +10,7 @@ describe PersonalFileUploader do dynamic_segment = "personal_snippet/#{snippet.id}" - expect(described_class.absolute_path(upload)).to end_with("#{dynamic_segment}/secret/foo.jpg") + expect(described_class.absolute_path(upload)).to end_with("/system/#{dynamic_segment}/secret/foo.jpg") end end @@ -19,7 +19,7 @@ describe PersonalFileUploader do uploader = described_class.new(snippet, 'secret') allow(uploader).to receive(:file).and_return(double(extension: 'txt', filename: 'file_name')) - expected_url = "/uploads/personal_snippet/#{snippet.id}/secret/file_name" + expected_url = "/uploads/system/personal_snippet/#{snippet.id}/secret/file_name" expect(uploader.to_h).to eq( alt: 'file_name', From ceda6bd5a6d5e7b24f0ec003ce2e7b446d0917c0 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 13 Jun 2017 17:14:14 +0000 Subject: [PATCH 124/130] Merge branch '33303-404-for-unauthorized-project' into 'security-9-3' [9.3 security fix] Renders 404 if given project is not readable by the user on Todos dashboard See merge request !2118 --- app/controllers/dashboard/todos_controller.rb | 10 +++++++ .../33303-404-for-unauthorized-project.yml | 4 +++ .../dashboard/todos_controller_spec.rb | 30 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 changelogs/unreleased/33303-404-for-unauthorized-project.yml diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 28c90548cc1..59e5b5e4775 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -1,6 +1,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController include ActionView::Helpers::NumberHelper + before_action :authorize_read_project!, only: :index before_action :find_todos, only: [:index, :destroy_all] def index @@ -49,6 +50,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController private + def authorize_read_project! + project_id = params[:project_id] + + if project_id.present? + project = Project.find(project_id) + render_404 unless can?(current_user, :read_project, project) + end + end + def find_todos @todos ||= TodosFinder.new(current_user, params).execute end diff --git a/changelogs/unreleased/33303-404-for-unauthorized-project.yml b/changelogs/unreleased/33303-404-for-unauthorized-project.yml new file mode 100644 index 00000000000..5a5a337129e --- /dev/null +++ b/changelogs/unreleased/33303-404-for-unauthorized-project.yml @@ -0,0 +1,4 @@ +--- +title: Renders 404 if given project is not readable by the user on Todos dashboard +merge_request: +author: diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 085f3fd8543..4a48621abe1 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -12,6 +12,36 @@ describe Dashboard::TodosController do end describe 'GET #index' do + context 'project authorization' do + it 'renders 404 when user does not have read access on given project' do + unauthorized_project = create(:empty_project, :private) + + get :index, project_id: unauthorized_project.id + + expect(response).to have_http_status(404) + end + + it 'renders 404 when given project does not exists' do + get :index, project_id: 999 + + expect(response).to have_http_status(404) + end + + it 'renders 200 when filtering for "any project" todos' do + get :index, project_id: '' + + expect(response).to have_http_status(200) + end + + it 'renders 200 when user has access on given project' do + authorized_project = create(:empty_project, :public) + + get :index, project_id: authorized_project.id + + expect(response).to have_http_status(200) + end + end + context 'when using pagination' do let(:last_page) { user.todos.page.total_pages } let!(:issues) { create_list(:issue, 2, project: project, assignees: [user]) } From ba60d4f6e4f3a6d3cb56c9320f475bee8f0b38da Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 22 Jun 2017 15:33:17 +0000 Subject: [PATCH 125/130] Merge branch '24570-use-re2-for-user-supplied-regexp-9-3' into 'security-9-3' 24570 use re2 for user supplied regexp 9 3 See merge request !2129 --- Gemfile | 3 + Gemfile.lock | 2 + lib/gitlab/ci/trace/stream.rb | 4 +- lib/gitlab/route_map.rb | 8 +- lib/gitlab/untrusted_regexp.rb | 53 ++++++++++++ spec/lib/gitlab/ci/trace/stream_spec.rb | 7 ++ spec/lib/gitlab/route_map_spec.rb | 13 +++ spec/lib/gitlab/untrusted_regexp_spec.rb | 80 +++++++++++++++++++ .../malicious_regexp_shared_examples.rb | 8 ++ 9 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 lib/gitlab/untrusted_regexp.rb create mode 100644 spec/lib/gitlab/untrusted_regexp_spec.rb create mode 100644 spec/support/malicious_regexp_shared_examples.rb diff --git a/Gemfile b/Gemfile index 57d2274b4b6..da66651b894 100644 --- a/Gemfile +++ b/Gemfile @@ -163,6 +163,9 @@ gem 'rainbow', '~> 2.2' # GitLab settings gem 'settingslogic', '~> 2.0.9' +# Linear-time regex library for untrusted regular expressions +gem 're2', '~> 1.0.0' + # Misc gem 'version_sorter', '~> 2.1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 52409000d3f..dfa7acc8917 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -657,6 +657,7 @@ GEM debugger-ruby_core_source (~> 1.3) rdoc (4.2.2) json (~> 1.4) + re2 (1.0.0) recaptcha (3.0.0) json recursive-open-struct (1.0.0) @@ -1056,6 +1057,7 @@ DEPENDENCIES raindrops (~> 0.18) rblineprof (~> 0.3.6) rdoc (~> 4.2) + re2 (~> 1.0.0) recaptcha (~> 3.0) redcarpet (~> 3.4) redis (~> 3.2) diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index c4c0623df6c..5d6977106d6 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -69,12 +69,12 @@ module Gitlab return unless valid? return unless regex - regex = Regexp.new(regex) + regex = Gitlab::UntrustedRegexp.new(regex) match = "" reverse_line do |line| - matches = line.scan(regex) + matches = regex.scan(line) next unless matches.is_a?(Array) next if matches.empty? diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb index 877aa6e6a28..f3952657983 100644 --- a/lib/gitlab/route_map.rb +++ b/lib/gitlab/route_map.rb @@ -18,7 +18,11 @@ module Gitlab mapping = @map.find { |mapping| mapping[:source] === path } return unless mapping - path.sub(mapping[:source], mapping[:public]) + if mapping[:source].is_a?(String) + path.sub(mapping[:source], mapping[:public]) + else + mapping[:source].replace(path, mapping[:public]) + end end private @@ -35,7 +39,7 @@ module Gitlab source_pattern = source_pattern[1...-1].gsub('\/', '/') begin - source_pattern = /\A#{source_pattern}\z/ + source_pattern = Gitlab::UntrustedRegexp.new('\A' + source_pattern + '\z') rescue RegexpError => e raise FormatError, "Route map entry source is not a valid regular expression: #{e}" end diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb new file mode 100644 index 00000000000..8b43f0053d6 --- /dev/null +++ b/lib/gitlab/untrusted_regexp.rb @@ -0,0 +1,53 @@ +module Gitlab + # An untrusted regular expression is any regexp containing patterns sourced + # from user input. + # + # Ruby's built-in regular expression library allows patterns which complete in + # exponential time, permitting denial-of-service attacks. + # + # Not all regular expression features are available in untrusted regexes, and + # there is a strict limit on total execution time. See the RE2 documentation + # at https://github.com/google/re2/wiki/Syntax for more details. + class UntrustedRegexp + delegate :===, to: :regexp + + def initialize(pattern) + @regexp = RE2::Regexp.new(pattern, log_errors: false) + + raise RegexpError.new(regexp.error) unless regexp.ok? + end + + def replace_all(text, rewrite) + RE2.GlobalReplace(text, regexp, rewrite) + end + + def scan(text) + scan_regexp.scan(text).map do |match| + if regexp.number_of_capturing_groups == 0 + match.first + else + match + end + end + end + + def replace(text, rewrite) + RE2.Replace(text, regexp, rewrite) + end + + private + + attr_reader :regexp + + # RE2 scan operates differently to Ruby scan when there are no capture + # groups, so work around it + def scan_regexp + @scan_regexp ||= + if regexp.number_of_capturing_groups == 0 + RE2::Regexp.new('(' + regexp.source + ')') + else + regexp + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index bbb3f9912a3..13f0338b6aa 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -293,5 +293,12 @@ describe Gitlab::Ci::Trace::Stream do it { is_expected.to eq("65") } end + + context 'malicious regexp' do + let(:data) { malicious_text } + let(:regex) { malicious_regexp } + + include_examples 'malicious regexp' + end end end diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb index 21c00c6e5b8..e8feb21e4d7 100644 --- a/spec/lib/gitlab/route_map_spec.rb +++ b/spec/lib/gitlab/route_map_spec.rb @@ -55,6 +55,19 @@ describe Gitlab::RouteMap, lib: true do end describe '#public_path_for_source_path' do + context 'malicious regexp' do + include_examples 'malicious regexp' + + subject do + map = described_class.new(<<-"MAP".strip_heredoc) + - source: '#{malicious_regexp}' + public: '/' + MAP + + map.public_path_for_source_path(malicious_text) + end + end + subject do described_class.new(<<-'MAP'.strip_heredoc) # Team data diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb new file mode 100644 index 00000000000..66045917cb3 --- /dev/null +++ b/spec/lib/gitlab/untrusted_regexp_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +describe Gitlab::UntrustedRegexp do + describe '#initialize' do + subject { described_class.new(pattern) } + + context 'invalid regexp' do + let(:pattern) { '[' } + + it { expect { subject }.to raise_error(RegexpError) } + end + end + + describe '#replace_all' do + it 'replaces all instances of the match in a string' do + result = described_class.new('foo').replace_all('foo bar foo', 'oof') + + expect(result).to eq('oof bar oof') + end + end + + describe '#replace' do + it 'replaces the first instance of the match in a string' do + result = described_class.new('foo').replace('foo bar foo', 'oof') + + expect(result).to eq('oof bar foo') + end + end + + describe '#===' do + it 'returns true for a match' do + result = described_class.new('foo') === 'a foo here' + + expect(result).to be_truthy + end + + it 'returns false for no match' do + result = described_class.new('foo') === 'a bar here' + + expect(result).to be_falsy + end + end + + describe '#scan' do + subject { described_class.new(regexp).scan(text) } + context 'malicious regexp' do + let(:text) { malicious_text } + let(:regexp) { malicious_regexp } + + include_examples 'malicious regexp' + end + + context 'no capture group' do + let(:regexp) { '.+' } + let(:text) { 'foo' } + + it 'returns the whole match' do + is_expected.to eq(['foo']) + end + end + + context 'one capture group' do + let(:regexp) { '(f).+' } + let(:text) { 'foo' } + + it 'returns the captured part' do + is_expected.to eq([%w[f]]) + end + end + + context 'two capture groups' do + let(:regexp) { '(f).(o)' } + let(:text) { 'foo' } + + it 'returns the captured parts' do + is_expected.to eq([%w[f o]]) + end + end + end +end diff --git a/spec/support/malicious_regexp_shared_examples.rb b/spec/support/malicious_regexp_shared_examples.rb new file mode 100644 index 00000000000..ac5d22298bb --- /dev/null +++ b/spec/support/malicious_regexp_shared_examples.rb @@ -0,0 +1,8 @@ +shared_examples 'malicious regexp' do + let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' } + let(:malicious_regexp) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' } + + it 'takes under a second' do + expect { Timeout.timeout(1) { subject } }.not_to raise_error + end +end From 0c602fe88c7ae1068ff94546fdbbc97d14342f5a Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 19 Jul 2017 22:32:54 -0500 Subject: [PATCH 126/130] remove redundant changelog entries --- changelogs/unreleased/33303-404-for-unauthorized-project.yml | 4 ---- changelogs/unreleased/33359-pers-snippet-files-location.yml | 4 ---- changelogs/unreleased/bvl-remove-appearance-symlink.yml | 4 ---- 3 files changed, 12 deletions(-) delete mode 100644 changelogs/unreleased/33303-404-for-unauthorized-project.yml delete mode 100644 changelogs/unreleased/33359-pers-snippet-files-location.yml delete mode 100644 changelogs/unreleased/bvl-remove-appearance-symlink.yml diff --git a/changelogs/unreleased/33303-404-for-unauthorized-project.yml b/changelogs/unreleased/33303-404-for-unauthorized-project.yml deleted file mode 100644 index 5a5a337129e..00000000000 --- a/changelogs/unreleased/33303-404-for-unauthorized-project.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Renders 404 if given project is not readable by the user on Todos dashboard -merge_request: -author: diff --git a/changelogs/unreleased/33359-pers-snippet-files-location.yml b/changelogs/unreleased/33359-pers-snippet-files-location.yml deleted file mode 100644 index 22fa301cb5e..00000000000 --- a/changelogs/unreleased/33359-pers-snippet-files-location.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use uploads/system directory for personal snippets -merge_request: -author: diff --git a/changelogs/unreleased/bvl-remove-appearance-symlink.yml b/changelogs/unreleased/bvl-remove-appearance-symlink.yml deleted file mode 100644 index 2b1c188528a..00000000000 --- a/changelogs/unreleased/bvl-remove-appearance-symlink.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove uploads/appearance symlink. A leftover from a previous migration. -merge_request: -author: From b711e10093f303c2d35aa3ebe9aff1c30c74ae7c Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 19 Jul 2017 20:26:18 -0500 Subject: [PATCH 127/130] Fix JS; make buttons sr accessibile; fix overlay --- app/assets/javascripts/layout_nav.js | 8 ++++++-- app/assets/javascripts/new_sidebar.js | 6 +++--- app/assets/stylesheets/new_sidebar.scss | 2 +- app/views/layouts/nav/_breadcrumbs.html.haml | 3 ++- app/views/layouts/nav/_new_admin_sidebar.html.haml | 5 +++-- app/views/layouts/nav/_new_group_sidebar.html.haml | 5 +++-- app/views/layouts/nav/_new_profile_sidebar.html.haml | 5 +++-- app/views/layouts/nav/_new_project_sidebar.html.haml | 5 +++-- 8 files changed, 24 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 1a24c7a6433..6186ffe20b3 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */ import _ from 'underscore'; +import Cookies from 'js-cookie'; import NewNavSidebar from './new_sidebar'; (function() { @@ -54,8 +55,11 @@ import NewNavSidebar from './new_sidebar'; } $(() => { - var newNavSidebar = new NewNavSidebar(); - newNavSidebar.bindEvents(); + if (Cookies.get('new_nav') === 'true') { + const newNavSidebar = new NewNavSidebar(); + newNavSidebar.bindEvents(); + } + $(window).on('scroll', _.throttle(applyScrollNavClass, 100)); }); }).call(window); diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index 2ab8d764a1d..5f98aff8ced 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -11,9 +11,9 @@ export default class NewNavSidebar { } bindEvents() { - this.$openSidebar.on('click', e => this.toggleSidebarNav(e, true)); - this.$closeSidebar.on('click', e => this.toggleSidebarNav(e, false)); - this.$overlay.on('click', e => this.toggleSidebarNav(e, false)); + this.$openSidebar.on('click', () => this.toggleSidebarNav(true)); + this.$closeSidebar.on('click', () => this.toggleSidebarNav(false)); + this.$overlay.on('click', () => this.toggleSidebarNav(false)); } toggleSidebarNav(show) { diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 0ab5322a7a4..1532ff3940c 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -247,7 +247,7 @@ $new-sidebar-width: 220px; &.mobile-nav-open { display: block; - position: absolute; + position: fixed; background-color: $black-transparent; height: 100%; width: 100%; diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index cef6e022d2b..6ffa1dd97d2 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -4,7 +4,8 @@ %nav.breadcrumbs{ role: "navigation" } .breadcrumbs-container{ class: [container_class, @content_class] } - if defined?(@new_sidebar) - %button.toggle-mobile-nav + = button_tag class: 'toggle-mobile-nav', type: 'button' do + %span.sr-only Open sidebar = icon ('bars') .breadcrumbs-links.js-title-container - unless hide_top_links diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index 2b5523f6fad..e58bc66635a 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -4,8 +4,9 @@ .avatar-container.s40.settings-avatar = icon('wrench') .project-title Admin Area - %button.close-nav-button - = icon('times') + = button_tag class: 'close-nav-button', type: 'button' do + %span.sr-only Close sidebar + = icon ('times') %ul.sidebar-top-level-items = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index fdb66d827ec..b8a2a36ef2a 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -5,8 +5,9 @@ = image_tag group_icon(@group), class: "avatar s40 avatar-tile" .group-title = @group.name - %button.close-nav-button - = icon('times') + = button_tag class: 'close-nav-button', type: 'button' do + %span.sr-only Close sidebar + = icon ('times') %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do = link_to group_path(@group), title: 'Home' do diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml index ce4eecc6c79..239e6b949e2 100644 --- a/app/views/layouts/nav/_new_profile_sidebar.html.haml +++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml @@ -4,8 +4,9 @@ .avatar-container.s40.settings-avatar = icon('user') .project-title User Settings - %button.close-nav-button - = icon('times') + = button_tag class: 'close-nav-button', type: 'button' do + %span.sr-only Close sidebar + = icon ('times') %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index baf257d06e0..3f5b7caaed9 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -6,8 +6,9 @@ = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') .project-title = @project.name - %button.close-nav-button - = icon('times') + = button_tag class: 'close-nav-button', type: 'button' do + %span.sr-only Close sidebar + = icon ('times') %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do From d382ed5eb618b5719f4fbf705a47b00c98e7c5a6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 20 Jul 2017 07:43:15 +0200 Subject: [PATCH 128/130] Fix CI/CD job auto-retry specs --- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 ++-- spec/services/ci/create_pipeline_service_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index e50f799a6e9..ed571a2ba05 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -35,11 +35,11 @@ module Ci describe 'retry entry' do context 'when retry count is specified' do let(:config) do - YAML.dump(rspec: { script: 'rspec', retry: 3 }) + YAML.dump(rspec: { script: 'rspec', retry: 1 }) end it 'includes retry count in build options attribute' do - expect(subject[:options]).to include(retry: 3) + expect(subject[:options]).to include(retry: 1) end end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 69f52b06980..194332f62c6 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -323,7 +323,7 @@ describe Ci::CreatePipelineService, :services do context 'when builds with auto-retries are configured' do before do - config = YAML.dump(rspec: { script: 'rspec', retry: 3 }) + config = YAML.dump(rspec: { script: 'rspec', retry: 2 }) stub_ci_pipeline_yaml_file(config) end @@ -331,7 +331,7 @@ describe Ci::CreatePipelineService, :services do pipeline = execute_service expect(pipeline).to be_persisted - expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 3 + expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2 end end end From 841de9752fb35b7d1a701da8764729ba334c2da5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 20 Jul 2017 10:53:52 +0200 Subject: [PATCH 129/130] Fix background migration cleanup specs We need to use a spy because an `after` RSpec hook is also going to call the migration we want to test, so we need to use `have_received` expectation. See gitlab-org/gitlab-ce#35351 for more details. --- ...clean_stage_id_reference_migration_spec.rb | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb index c2072f2672d..9a581df28a2 100644 --- a/spec/migrations/clean_stage_id_reference_migration_spec.rb +++ b/spec/migrations/clean_stage_id_reference_migration_spec.rb @@ -2,28 +2,32 @@ require 'spec_helper' require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb') describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do - let(:migration) { 'MigrateBuildStageIdReference' } + let(:migration_class) { 'MigrateBuildStageIdReference' } + let(:migration) { spy('migration') } + + before do + allow(Gitlab::BackgroundMigration.const_get(migration_class)) + .to receive(:new).and_return(migration) + end context 'when there are pending background migrations' do it 'processes pending jobs synchronously' do Sidekiq::Testing.disable! do - BackgroundMigrationWorker.perform_in(2.minutes, migration, [1, 1]) - BackgroundMigrationWorker.perform_async(migration, [1, 1]) - - expect(Gitlab::BackgroundMigration) - .to receive(:perform).twice.and_call_original + BackgroundMigrationWorker.perform_in(2.minutes, migration_class, [1, 1]) + BackgroundMigrationWorker.perform_async(migration_class, [1, 1]) migrate! + + expect(migration).to have_received(:perform).with(1, 1).twice end end end - context 'when there are no background migrations pending' do it 'does nothing' do Sidekiq::Testing.disable! do - expect(Gitlab::BackgroundMigration).not_to receive(:perform) - migrate! + + expect(migration).not_to have_received(:perform) end end end From 2f620aa7116f504229be81c2465fead342a57292 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 20 Jul 2017 11:02:00 +0200 Subject: [PATCH 130/130] Change auto-retry count to a correct value in docs --- doc/ci/yaml/README.md | 4 ++-- spec/models/ci/build_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 808a23df554..e12ef6e2685 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1145,7 +1145,7 @@ case of a failure. When a job fails, and has `retry` configured it is going to be processed again up to the amount of times specified by the `retry` keyword. -If `retry` is set to 3, and a job succeeds in a second run, it won't be retried +If `retry` is set to 2, and a job succeeds in a second run (first retry), it won't be retried again. `retry` value has to be a positive integer, equal or larger than 0, but lower or equal to 2 (two retries maximum, three runs in total). @@ -1154,7 +1154,7 @@ A simple example: ```yaml test: script: rspec - retry: 3 + retry: 2 ``` ## Git Strategy diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index acfc888d944..0b521d720f3 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -826,10 +826,10 @@ describe Ci::Build, :models do describe '#retries_max' do context 'when max retries value is defined' do - subject { create(:ci_build, options: { retry: 3 }) } + subject { create(:ci_build, options: { retry: 1 }) } it 'returns a number of configured max retries' do - expect(subject.retries_max).to eq 3 + expect(subject.retries_max).to eq 1 end end