From 84ee2857975ed2da991604d9f93a151b66032991 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 9 Aug 2016 13:24:16 -0500 Subject: [PATCH 01/64] Add delimiter to project stars and forks count --- CHANGELOG | 1 + app/views/shared/projects/_project.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7bfeff2a4ec..eb43b656f11 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.11.0 (unreleased) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) + - Add delimiter to project stars and forks count (ClemMakesApps) - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Improve diff performance by eliminating redundant checks for text blobs diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index b8b66d08db8..2cf958e9879 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -18,11 +18,11 @@ - if forks %span = icon('code-fork') - = project.forks_count + = number_with_delimiter(project.forks_count) - if stars %span = icon('star') - = project.star_count + = number_with_delimiter(project.star_count) %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)} = visibility_level_icon(project.visibility_level, fw: false) From 3b4fb0bbdc13f5b52d1a4d30128d0d2d99b97e72 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 8 Aug 2016 16:26:33 -0500 Subject: [PATCH 02/64] Add build data to top of pipeline page --- app/assets/stylesheets/pages/pipelines.scss | 6 ++++++ app/views/projects/commit/_pipeline.html.haml | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 21919fe4d73..5bc8c3a7e6c 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -229,3 +229,9 @@ box-shadow: none; } } + +// Pipeline visualization +.stage-column { + display: inline-block; + vertical-align: top; +} diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 540689f4a61..a52bfa0ea55 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,4 +1,16 @@ .row-content-block.build-content.middle-block + .pipeline-visualization + - pipeline.statuses.stages.each do |stage| + - statuses = pipeline.statuses.where(stage: stage) + .stage-column + %strong + %a{name: stage} + - if stage + = stage.titleize + - statuses.each do |status| + %div= status.name + + .pull-right - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) From dcb7d4148efe86ba07e9a00cb43818846d61f9d0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 8 Aug 2016 17:42:28 -0500 Subject: [PATCH 03/64] Connect top level tests to each other --- app/assets/stylesheets/pages/pipelines.scss | 50 +++++++++++++++++++ app/views/projects/commit/_pipeline.html.haml | 44 +++++++++------- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 5bc8c3a7e6c..e79c7e59720 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -231,7 +231,57 @@ } // Pipeline visualization + +.pipeline-visualization { + position: relative; +} + .stage-column { display: inline-block; vertical-align: top; + margin-right: 40px; + + .stage-name { + margin-bottom: 15px; + font-weight: bold; + } + + .builds-container { + + } + + .build { + border: 1px solid $border-color; + position: relative; + padding: 6px 10px; + border-radius: 30px; + width: 150px; + // TODO truncate text within .build div; not on build div itself + // white-space: nowrap; + // overflow: hidden; + // text-overflow: ellipsis; + margin-bottom: 10px; + + svg { + position: relative; + top: 2px; + margin-right: 5px; + } + + &:first-child { + &::after, &::before { + content: ''; + position: absolute; + top: 50%; + right: -44px; + border-top: 1px solid $border-color; + width: 44px; + height: 1px; + } + } + &:first-child::before, &:last-child::after{ + border: 0 none; + } + + } } diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index a52bfa0ea55..72259cd0756 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,16 +1,22 @@ -.row-content-block.build-content.middle-block +.row-content-block.white + .pipeline-visualization - pipeline.statuses.stages.each do |stage| - statuses = pipeline.statuses.where(stage: stage) + - status = statuses.latest.status .stage-column - %strong + .stage-name %a{name: stage} - if stage = stage.titleize - - statuses.each do |status| - %div= status.name - + .builds-container + - statuses.each do |build| + .build + %span{class: "ci-status-link ci-status-icon-#{status}"} + = ci_icon_for_status(status) + = build.name +.row-content-block.build-content.middle-block .pull-right - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) @@ -46,17 +52,17 @@ - if pipeline.project.builds_enabled? && !pipeline.ci_yaml_file .bs-callout.bs-callout-warning \.gitlab-ci.yml not found in this commit - -.table-holder.pipeline-holder - %table.table.builds.pipeline - %thead - %tr - %th Status - %th Build ID - %th Name - %th - - if pipeline.project.build_coverage_enabled? - %th Coverage - %th - - pipeline.statuses.stages.each do |stage| - = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.where(stage: stage) +-# +-# .table-holder.pipeline-holder +-# %table.table.builds.pipeline +-# %thead +-# %tr +-# %th Status +-# %th Build ID +-# %th Name +-# %th +-# - if pipeline.project.build_coverage_enabled? +-# %th Coverage +-# %th +-# - pipeline.statuses.stages.each do |stage| +-# = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.where(stage: stage) From 83c1ce74e01dcf95ac8f545d7584259615e8834f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 14:26:25 -0500 Subject: [PATCH 04/64] Connect all builds in visual pipeline --- app/assets/stylesheets/pages/pipelines.scss | 81 +++++++++++++++++-- app/views/projects/commit/_pipeline.html.haml | 32 ++++---- 2 files changed, 90 insertions(+), 23 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e79c7e59720..fa422f4f3c5 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -234,6 +234,10 @@ .pipeline-visualization { position: relative; + + ul { + padding: 0; + } } .stage-column { @@ -241,15 +245,15 @@ vertical-align: top; margin-right: 40px; + li { + list-style: none; + } + .stage-name { margin-bottom: 15px; font-weight: bold; } - .builds-container { - - } - .build { border: 1px solid $border-color; position: relative; @@ -268,20 +272,81 @@ margin-right: 5px; } + // Connect first build in each stage with right horizontal line &:first-child { - &::after, &::before { + &::after { content: ''; position: absolute; top: 50%; right: -44px; - border-top: 1px solid $border-color; + border-top: 2px solid $border-color; width: 44px; height: 1px; } } - &:first-child::before, &:last-child::after{ - border: 0 none; + + // Connect each build (except for first) with curved lines + &:not(:first-child) { + &::after, &::before { + content: ''; + top: -47px; + position: absolute; + border-bottom: 2px solid $border-color; + width: 20px; + height: 65px; + } + + // Right connecting curves + &::after { + right: -21px; + border-right: 2px solid $border-color; + border-radius: 0 0 50px 0; + -webkit-border-radius: 0 0 50px 0; + } + + // Left connecting curves + &::before { + left: -21px; + border-left: 2px solid $border-color; + border-radius: 0 0 0 50px; + -webkit-border-radius: 0 0 0 50px; + } } + // Connect second build to first build with smaller curved line + &:nth-child(2) { + &::after, &::before { + height: 40px; + top: -26px; + } + } + } + + &:last-child { + .build { + // Remove right connecting horizontal line from first build in last stage + &:first-child { + &::after, &::before { + border: none; + } + } + // Remove right curved connectors from all builds in last stage + &:not(:first-child) { + &::after { + border: none; + } + } + } + } + + &:first-child { + .build { + // Remove left curved connectors from all builds in first stage + &:not(:first-child) { + &::before { + border: none; + } + } + } } } diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 72259cd0756..06a63839107 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,20 +1,22 @@ .row-content-block.white .pipeline-visualization - - pipeline.statuses.stages.each do |stage| - - statuses = pipeline.statuses.where(stage: stage) - - status = statuses.latest.status - .stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - - statuses.each do |build| - .build - %span{class: "ci-status-link ci-status-icon-#{status}"} - = ci_icon_for_status(status) - = build.name + %ul.stage-column-list + - pipeline.statuses.stages.each do |stage| + - statuses = pipeline.statuses.where(stage: stage) + - status = statuses.latest.status + %li.stage-column + .stage-name + %a{name: stage} + - if stage + = stage.titleize + .builds-container + %ul + - statuses.each do |build| + %li.build + %span{class: "ci-status-link ci-status-icon-#{status}"} + = ci_icon_for_status(status) + = build.name .row-content-block.build-content.middle-block .pull-right @@ -52,7 +54,7 @@ - if pipeline.project.builds_enabled? && !pipeline.ci_yaml_file .bs-callout.bs-callout-warning \.gitlab-ci.yml not found in this commit --# + -# .table-holder.pipeline-holder -# %table.table.builds.pipeline -# %thead From a76864cc92241ef9b759fbe63282a45dea7d5e03 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 15:00:09 -0500 Subject: [PATCH 05/64] Add min width and horizontally scroll pipeline graph when overflow --- app/assets/stylesheets/pages/pipelines.scss | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index fa422f4f3c5..a9ffda00278 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -234,16 +234,22 @@ .pipeline-visualization { position: relative; + width: 100%; + overflow: auto; ul { padding: 0; } } +.stage-column-list { + min-width: 1220px; +} + .stage-column { display: inline-block; vertical-align: top; - margin-right: 40px; + margin-right: 50px; li { list-style: none; @@ -278,9 +284,9 @@ content: ''; position: absolute; top: 50%; - right: -44px; + right: -54px; border-top: 2px solid $border-color; - width: 44px; + width: 54px; height: 1px; } } @@ -298,7 +304,7 @@ // Right connecting curves &::after { - right: -21px; + right: -20px; border-right: 2px solid $border-color; border-radius: 0 0 50px 0; -webkit-border-radius: 0 0 50px 0; @@ -306,7 +312,7 @@ // Left connecting curves &::before { - left: -21px; + left: -20px; border-left: 2px solid $border-color; border-radius: 0 0 0 50px; -webkit-border-radius: 0 0 0 50px; @@ -316,9 +322,14 @@ // Connect second build to first build with smaller curved line &:nth-child(2) { &::after, &::before { - height: 40px; + height: 45px; top: -26px; } + &::after { + // border-left: 2px solid $border-color; + border-top-right-radius: -50px; + -webkit-border-top-right-radius: -50px; + } } } From aeacadb3e32156a33060fea81243a4cef8fc1c3e Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 15:08:04 -0500 Subject: [PATCH 06/64] Truncate build and stage names --- CHANGELOG | 1 + app/assets/stylesheets/pages/pipelines.scss | 15 ++++++--- app/views/projects/commit/_pipeline.html.haml | 33 ++++++++++--------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 58751448d4a..31ed0e01095 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -81,6 +81,7 @@ v 8.11.0 (unreleased) - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker - Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko) - Adds support for pending invitation project members importing projects + - Add pipeline visualization/graph on pipeline page - Update devise initializer to turn on changed password notification emails. !5648 (tombell) - Avoid to show the original password field when password is automatically set. !5712 (duduribeiro) - Fix importing GitLab projects with an invalid MR source project diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a9ffda00278..49225d7d0d4 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -258,6 +258,10 @@ .stage-name { margin-bottom: 15px; font-weight: bold; + width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .build { @@ -266,12 +270,15 @@ padding: 6px 10px; border-radius: 30px; width: 150px; - // TODO truncate text within .build div; not on build div itself - // white-space: nowrap; - // overflow: hidden; - // text-overflow: ellipsis; margin-bottom: 10px; + .build-content { + width: 130px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + svg { position: relative; top: 2px; diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 06a63839107..2edf660db71 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -14,9 +14,10 @@ %ul - statuses.each do |build| %li.build - %span{class: "ci-status-link ci-status-icon-#{status}"} - = ci_icon_for_status(status) - = build.name + .build-content + %span{class: "ci-status-link ci-status-icon-#{status}"} + = ci_icon_for_status(status) + = build.name .row-content-block.build-content.middle-block .pull-right @@ -55,16 +56,16 @@ .bs-callout.bs-callout-warning \.gitlab-ci.yml not found in this commit --# .table-holder.pipeline-holder --# %table.table.builds.pipeline --# %thead --# %tr --# %th Status --# %th Build ID --# %th Name --# %th --# - if pipeline.project.build_coverage_enabled? --# %th Coverage --# %th --# - pipeline.statuses.stages.each do |stage| --# = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.where(stage: stage) +.table-holder.pipeline-holder + %table.table.builds.pipeline + %thead + %tr + %th Status + %th Build ID + %th Name + %th + - if pipeline.project.build_coverage_enabled? + %th Coverage + %th + - pipeline.statuses.stages.each do |stage| + = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.where(stage: stage) From 94ca1e3edea8fa8e70e828cc4127e0cb8ec8a9d1 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 15:39:01 -0500 Subject: [PATCH 07/64] Fix graph scrolling bug --- app/assets/stylesheets/pages/pipelines.scss | 13 +++++++------ app/views/projects/commit/_pipeline.html.haml | 5 ++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 49225d7d0d4..01bd131940a 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -232,20 +232,21 @@ // Pipeline visualization -.pipeline-visualization { - position: relative; +.pipeline-graph { width: 100%; overflow: auto; + white-space: nowrap; +} + +.pipeline-visualization { + position: relative; + min-width: 1220px; ul { padding: 0; } } -.stage-column-list { - min-width: 1220px; -} - .stage-column { display: inline-block; vertical-align: top; diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 2edf660db71..0c006656d1d 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,5 +1,4 @@ -.row-content-block.white - +.row-content-block.build-content.middle-block.pipeline-graph .pipeline-visualization %ul.stage-column-list - pipeline.statuses.stages.each do |stage| @@ -19,7 +18,7 @@ = ci_icon_for_status(status) = build.name -.row-content-block.build-content.middle-block +.row-content-block.build-content.middle-block.pipeline-graph .pull-right - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) From ef767268645e8f60b7111eb9f6b6c3aa1b615661 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 16:15:09 -0500 Subject: [PATCH 08/64] Fix scss lint error --- app/assets/stylesheets/pages/pipelines.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 01bd131940a..ad256e2a728 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -314,8 +314,8 @@ &::after { right: -20px; border-right: 2px solid $border-color; - border-radius: 0 0 50px 0; - -webkit-border-radius: 0 0 50px 0; + border-radius: 0 0 50px; + -webkit-border-radius: 0 0 50px; } // Left connecting curves From bbb019094b4b5999f96407b20a3c9da469dcf985 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 11 Aug 2016 10:02:27 +0200 Subject: [PATCH 09/64] Fix merge request new view not changing code view rendering style --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 2 ++ .../merge_requests/create_new_mr_spec.rb | 19 ++++++++++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 56ab89abaf9..504e1421002 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -75,6 +75,7 @@ v 8.11.0 (unreleased) - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - Add commit stats in commit api. !5517 (dixpac) - Add CI configuration button on project page + - Fix merge request new view not changing code view rendering style - Make error pages responsive (Takuya Noguchi) - Fix skip_repo parameter being ignored when destroying a namespace - Change requests_profiles resource constraint to catch virtually any file diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2cf6a2dd1b3..358c6ebe4ef 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -142,6 +142,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def new + apply_diff_view_cookie! + build_merge_request @noteable = @merge_request diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index e296078bad8..a5a3f8b8239 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -8,10 +8,11 @@ feature 'Create New Merge Request', feature: true, js: true do project.team << [user, :master] login_as user - visit namespace_project_merge_requests_path(project.namespace, project) end it 'generates a diff for an orphaned branch' do + visit namespace_project_merge_requests_path(project.namespace, project) + click_link 'New Merge Request' first('.js-source-branch').click @@ -40,4 +41,20 @@ feature 'Create New Merge Request', feature: true, js: true do expect(page).not_to have_content private_project.to_reference end end + + it 'allows to change the diff view' do + visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' }) + + click_link 'Changes' + + expect(page.find_link('Inline')[:class]).to match(/\bactive\b/) + expect(page.find_link('Side-by-side')[:class]).not_to match(/\bactive\b/) + + click_link 'Side-by-side' + + click_link 'Changes' + + expect(page.find_link('Inline')[:class]).not_to match(/\bactive\b/) + expect(page.find_link('Side-by-side')[:class]).to match(/\bactive\b/) + end end From 59bfa0809822c3dd257748197223809922ab5f80 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 12 Aug 2016 22:54:32 +0100 Subject: [PATCH 10/64] Send notification emails when users are newly mentioned in issue edits --- app/mailers/emails/issues.rb | 5 +++ app/services/issuable_base_service.rb | 3 +- app/services/issues/update_service.rb | 7 +++- app/services/notification_service.rb | 24 +++++++++++++- .../new_mention_in_issue_email.html.haml | 9 +++++ .../new_mention_in_issue_email.text.erb | 7 ++++ spec/services/issues/update_service_spec.rb | 33 +++++++++++++++++++ spec/services/notification_service_spec.rb | 26 +++++++++++++++ 8 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 app/views/notify/new_mention_in_issue_email.html.haml create mode 100644 app/views/notify/new_mention_in_issue_email.text.erb diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 6f54c42146c..d64e48f774b 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -6,6 +6,11 @@ module Emails mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id)) end + def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id) + setup_issue_mail(issue_id, recipient_id) + mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id)) + end + def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) setup_issue_mail(issue_id, recipient_id) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 2d96efe1042..b0ea7c905f8 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -104,11 +104,12 @@ class IssuableBaseService < BaseService change_subscription(issuable) filter_params old_labels = issuable.labels.to_a + old_mentioned_users = issuable.mentioned_users.to_a if params.present? && update_issuable(issuable, params) issuable.reset_events_cache handle_common_system_notes(issuable, old_labels: old_labels) - handle_changes(issuable, old_labels: old_labels) + handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users) issuable.create_new_cross_references!(current_user) execute_hooks(issuable, 'update') end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index c7d406cc331..a2111b3806b 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -4,7 +4,7 @@ module Issues update(issue) end - def handle_changes(issue, old_labels: []) + def handle_changes(issue, old_labels: [], old_mentioned_users: []) if has_changes?(issue, old_labels: old_labels) todo_service.mark_pending_todos_as_done(issue, current_user) end @@ -32,6 +32,11 @@ module Issues if added_labels.present? notification_service.relabeled_issue(issue, added_labels, current_user) end + + added_mentions = issue.mentioned_users - old_mentioned_users + if added_mentions.present? + notification_service.new_mentions_in_issue(issue, added_mentions, current_user) + end end def reopen_service diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index ab6e51209ee..73df572514f 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -35,6 +35,20 @@ class NotificationService new_resource_email(issue, issue.project, :new_issue_email) end + # When issue text is updated, we should send an email to: + # + # * newly mentioned project team members with notification level higher than Participating + # + def new_mentions_in_issue(issue, new_mentioned_users, current_user) + new_mentions_in_resource_email( + issue, + issue.project, + new_mentioned_users, + current_user, + :new_mention_in_issue_email + ) + end + # When we close an issue we should send an email to: # # * issue author if their notification level is not Disabled @@ -177,7 +191,7 @@ class NotificationService # build notify method like 'note_commit_email' notify_method = "note_#{note.noteable_type.underscore}_email".to_sym - + recipients.each do |recipient| mailer.send(notify_method, recipient.id, note.id).deliver_later end @@ -471,6 +485,14 @@ class NotificationService end end + def new_mentions_in_resource_email(target, project, new_mentioned_users, current_user, method) + recipients = build_recipients(target, project, current_user) & new_mentioned_users + + recipients.each do |recipient| + mailer.send(method, recipient.id, target.id, current_user.id).deliver_later + end + end + def close_resource_email(target, project, current_user, method) action = method == :merged_merge_request_email ? "merge" : "close" recipients = build_recipients(target, project, current_user, action: action) diff --git a/app/views/notify/new_mention_in_issue_email.html.haml b/app/views/notify/new_mention_in_issue_email.html.haml new file mode 100644 index 00000000000..f42b150c0d6 --- /dev/null +++ b/app/views/notify/new_mention_in_issue_email.html.haml @@ -0,0 +1,9 @@ +- if current_application_settings.email_author_in_body + %div + #{link_to @issue.author_name, user_url(@issue.author)} wrote: +-if @issue.description + = markdown(@issue.description, pipeline: :email, author: @issue.author) + +- if @issue.assignee_id.present? + %p + Assignee: #{@issue.assignee_name} diff --git a/app/views/notify/new_mention_in_issue_email.text.erb b/app/views/notify/new_mention_in_issue_email.text.erb new file mode 100644 index 00000000000..457e94b4800 --- /dev/null +++ b/app/views/notify/new_mention_in_issue_email.text.erb @@ -0,0 +1,7 @@ +You have been mentioned in an issue. + +Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> +Author: <%= @issue.author_name %> +Assignee: <%= @issue.assignee_name %> + +<%= @issue.description %> diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 088c3d48bf7..a5e375578ec 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -319,5 +319,38 @@ describe Issues::UpdateService, services: true do end end end + + context 'updated user mentions' do + let(:user4) { create(:user) } + before do + project.team << [user4, :developer] + end + + context "in title" do + before do + perform_enqueued_jobs { update_issue(title: user4.to_reference) } + end + + it "should email only the newly-mentioned user" do + should_not_email(user) + should_not_email(user2) + should_not_email(user3) + should_email(user4) + end + end + + context "in description" do + before do + perform_enqueued_jobs { update_issue(description: user4.to_reference) } + end + + it "should email only the newly-mentioned user" do + should_not_email(user) + should_not_email(user2) + should_not_email(user3) + should_email(user4) + end + end + end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 92b441c28ca..1935451ff12 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -399,6 +399,32 @@ describe NotificationService, services: true do end end + describe '#new_mentions_in_issue' do + def send_notifications(*new_mentions) + ActionMailer::Base.deliveries.clear + notification.new_mentions_in_issue(issue, new_mentions, @u_disabled) + end + + it "should not email anyone unless they are newly mentioned" do + send_notifications() + expect(ActionMailer::Base.deliveries).to eq [] + end + + it "should email new mentions with a watch level higher than participant" do + send_notifications(@u_watcher, @u_participant_mentioned) + + should_email(@u_watcher) + should_email(@u_participant_mentioned) + + expect(ActionMailer::Base.deliveries.count).to eq 2 + end + + it "should not email new mentions with a watch level equal to or less than participant" do + send_notifications(@u_participating, @u_mentioned) + expect(ActionMailer::Base.deliveries).to eq [] + end + end + describe '#reassigned_issue' do before do update_custom_notification(:reassign_issue, @u_guest_custom, project) From e6f519c4a7efa6a865c7e8d2a62ed5c3db12b453 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 15 Aug 2016 12:05:08 +0100 Subject: [PATCH 11/64] Fix a 'missing keyword' error introduced in the last commit --- app/services/merge_requests/update_service.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 026a37997d4..0ad42b83fe2 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -16,7 +16,7 @@ module MergeRequests update(merge_request) end - def handle_changes(merge_request, old_labels: []) + def handle_changes(merge_request, old_labels: [], old_mentioned_users: []) if has_changes?(merge_request, old_labels: old_labels) todo_service.mark_pending_todos_as_done(merge_request, current_user) end @@ -55,6 +55,8 @@ module MergeRequests current_user ) end + + # TODO(nick): use old_mentioned_users to send notify for changed mentions end def reopen_service From b450aab8501076740cd7140b47488b9dfa5f58f9 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 15 Aug 2016 14:38:22 +0100 Subject: [PATCH 12/64] Remove superfluous parens on a method call --- spec/services/notification_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 1935451ff12..d61de6c15d1 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -406,7 +406,7 @@ describe NotificationService, services: true do end it "should not email anyone unless they are newly mentioned" do - send_notifications() + send_notifications expect(ActionMailer::Base.deliveries).to eq [] end From 6642ae4579d6ceed6b26014aee4a22adb39fc43c Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 15 Aug 2016 14:47:16 +0100 Subject: [PATCH 13/64] Add notifications for new user mentions in merge requests --- app/mailers/emails/merge_requests.rb | 5 +++ app/services/merge_requests/update_service.rb | 9 ++++- app/services/notification_service.rb | 14 ++++++++ ...w_mention_in_merge_request_email.html.haml | 12 +++++++ ...ew_mention_in_merge_request_email.text.erb | 9 +++++ .../merge_requests/update_service_spec.rb | 33 +++++++++++++++++++ spec/services/notification_service_spec.rb | 26 +++++++++++++++ 7 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 app/views/notify/new_mention_in_merge_request_email.html.haml create mode 100644 app/views/notify/new_mention_in_merge_request_email.text.erb diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 9dd11d20ea6..95810b0ac6e 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -6,6 +6,11 @@ module Emails mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id)) end + def new_mention_in_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) + setup_merge_request_mail(merge_request_id, recipient_id) + mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id)) + end + def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) setup_merge_request_mail(merge_request_id, recipient_id) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 0ad42b83fe2..30c5f24988c 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -56,7 +56,14 @@ module MergeRequests ) end - # TODO(nick): use old_mentioned_users to send notify for changed mentions + added_mentions = merge_request.mentioned_users - old_mentioned_users + if added_mentions.present? + notification_service.new_mentions_in_merge_request( + merge_request, + added_mentions, + current_user + ) + end end def reopen_service diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 73df572514f..01f95281170 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -89,6 +89,20 @@ class NotificationService new_resource_email(merge_request, merge_request.target_project, :new_merge_request_email) end + # When merge request text is updated, we should send an email to: + # + # * newly mentioned project team members with notification level higher than Participating + # + def new_mentions_in_merge_request(merge_request, new_mentioned_users, current_user) + new_mentions_in_resource_email( + merge_request, + merge_request.target_project, + new_mentioned_users, + current_user, + :new_mention_in_merge_request_email + ) + end + # When we reassign a merge_request we should send an email to: # # * merge_request old assignee if their notification level is not Disabled diff --git a/app/views/notify/new_mention_in_merge_request_email.html.haml b/app/views/notify/new_mention_in_merge_request_email.html.haml new file mode 100644 index 00000000000..158404de396 --- /dev/null +++ b/app/views/notify/new_mention_in_merge_request_email.html.haml @@ -0,0 +1,12 @@ +- if current_application_settings.email_author_in_body + %div + #{link_to @merge_request.author_name, user_url(@merge_request.author)} wrote: +%p.details + != merge_path_description(@merge_request, '→') + +- if @merge_request.assignee_id.present? + %p + Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} + +-if @merge_request.description + = markdown(@merge_request.description, pipeline: :email, author: @merge_request.author) diff --git a/app/views/notify/new_mention_in_merge_request_email.text.erb b/app/views/notify/new_mention_in_merge_request_email.text.erb new file mode 100644 index 00000000000..5bf0282e097 --- /dev/null +++ b/app/views/notify/new_mention_in_merge_request_email.text.erb @@ -0,0 +1,9 @@ +You have been mentioned in Merge Request <%= @merge_request.to_reference %> + +<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %> + +<%= merge_path_description(@merge_request, 'to') %> +Author: <%= @merge_request.author_name %> +Assignee: <%= @merge_request.assignee_name %> + +<%= @merge_request.description %> diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 283a336afd9..e26e925cd81 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -226,6 +226,39 @@ describe MergeRequests::UpdateService, services: true do end end + context 'updated user mentions' do + let(:user4) { create(:user) } + before do + project.team << [user4, :developer] + end + + context "in title" do + before do + perform_enqueued_jobs { update_merge_request(title: user4.to_reference) } + end + + it "should email only the newly-mentioned user" do + should_not_email(user) + should_not_email(user2) + should_not_email(user3) + should_email(user4) + end + end + + context "in description" do + before do + perform_enqueued_jobs { update_merge_request(description: user4.to_reference) } + end + + it "should email only the newly-mentioned user" do + should_not_email(user) + should_not_email(user2) + should_not_email(user3) + should_email(user4) + end + end + end + context 'when MergeRequest has tasks' do before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index d61de6c15d1..2fa0d9f1ac2 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -789,6 +789,32 @@ describe NotificationService, services: true do end end + describe '#new_mentions_in_merge_request' do + def send_notifications(*new_mentions) + ActionMailer::Base.deliveries.clear + notification.new_mentions_in_merge_request(merge_request, new_mentions, @u_disabled) + end + + it "should not email anyone unless they are newly mentioned" do + send_notifications + expect(ActionMailer::Base.deliveries).to eq [] + end + + it "should email new mentions with a watch level higher than participant" do + send_notifications(@u_watcher, @u_participant_mentioned) + + should_email(@u_watcher) + should_email(@u_participant_mentioned) + + expect(ActionMailer::Base.deliveries.count).to eq 2 + end + + it "should not email new mentions with a watch level equal to or less than participant" do + send_notifications(@u_participating, @u_mentioned) + expect(ActionMailer::Base.deliveries).to eq [] + end + end + describe '#reassigned_merge_request' do before do update_custom_notification(:reassign_merge_request, @u_guest_custom, project) From 10af11f4fe71e46d4decd2ab428e9b2b38e2d463 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 15 Aug 2016 15:15:20 +0100 Subject: [PATCH 14/64] Allow people to subscribe to mentions in updated MRs and Issues This slightly changes the semantics of the 'New Issue' and 'New MR' events to include new mentions in edited Mentionables. An alternative would be to introduce 'Issue updated' and 'MR updated' events, but that would lead to questions about why those events were only available to new mentions, and not existing mentions as well, so hold off for now. --- app/services/notification_service.rb | 3 ++- doc/workflow/notifications.md | 7 ++++++- spec/services/notification_service_spec.rb | 12 ++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 01f95281170..2291bc0f127 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -500,7 +500,8 @@ class NotificationService end def new_mentions_in_resource_email(target, project, new_mentioned_users, current_user, method) - recipients = build_recipients(target, project, current_user) & new_mentioned_users + recipients = build_recipients(target, project, current_user, action: "new") + recipients = recipients & new_mentioned_users recipients.each do |recipient| mailer.send(method, recipient.id, target.id, current_user.id).deliver_later diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md index b4a9c2f3d3e..1b49a5c385f 100644 --- a/doc/workflow/notifications.md +++ b/doc/workflow/notifications.md @@ -67,7 +67,7 @@ In all of the below cases, the notification will be sent to: - Participants: - the author and assignee of the issue/merge request - authors of comments on the issue/merge request - - anyone mentioned by `@username` in the issue/merge request description + - anyone mentioned by `@username` in the issue/merge request title or description - anyone mentioned by `@username` in any of the comments on the issue/merge request ...with notification level "Participating" or higher @@ -89,6 +89,11 @@ In all of the below cases, the notification will be sent to: | Merge merge request | | | New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher | + +In addition, if the title or description of an Issue or Merge Request is +changed, notifications will be sent to any **new** mentions by `@username` as +if they had been mentioned in the original text. + You won't receive notifications for Issues, Merge Requests or Milestones created by yourself. You will only receive automatic notifications when somebody else comments or adds changes to the ones that you've created or diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 2fa0d9f1ac2..fbca28bbb17 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -411,12 +411,13 @@ describe NotificationService, services: true do end it "should email new mentions with a watch level higher than participant" do - send_notifications(@u_watcher, @u_participant_mentioned) + send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global) should_email(@u_watcher) should_email(@u_participant_mentioned) + should_email(@u_custom_global) - expect(ActionMailer::Base.deliveries.count).to eq 2 + expect(ActionMailer::Base.deliveries.count).to eq 3 end it "should not email new mentions with a watch level equal to or less than participant" do @@ -726,6 +727,8 @@ describe NotificationService, services: true do before do build_team(merge_request.target_project) add_users_with_subscription(merge_request.target_project, merge_request) + update_custom_notification(:new_merge_request, @u_guest_custom, project) + update_custom_notification(:new_merge_request, @u_custom_global) ActionMailer::Base.deliveries.clear end @@ -801,12 +804,13 @@ describe NotificationService, services: true do end it "should email new mentions with a watch level higher than participant" do - send_notifications(@u_watcher, @u_participant_mentioned) + send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global) should_email(@u_watcher) should_email(@u_participant_mentioned) + should_email(@u_custom_global) - expect(ActionMailer::Base.deliveries.count).to eq 2 + expect(ActionMailer::Base.deliveries.count).to eq 3 end it "should not email new mentions with a watch level equal to or less than participant" do From 6bf7b627d11e4719f60b109a5379e7975411f611 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 15 Aug 2016 16:08:00 +0100 Subject: [PATCH 15/64] Add new mention notifications to changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 008c5c64284..836bb0b9fce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.11.0 (unreleased) - Fix awardable button mutuality loading spinners (ClemMakesApps) - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Optimize maximum user access level lookup in loading of notes + - Send notification emails to users newly mentioned in issue and MR edits !5800 - Add "No one can push" as an option for protected branches. !5081 - Improve performance of AutolinkFilter#text_parse by using XPath - Add experimental Redis Sentinel support !1877 From 8a6e9b934e6e25023437ff8a2dd2271d67a12e40 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 16 Aug 2016 13:40:55 +0100 Subject: [PATCH 16/64] Address minor review comments for tests --- spec/services/issues/update_service_spec.rb | 6 +++--- .../merge_requests/update_service_spec.rb | 8 ++++---- spec/services/notification_service_spec.rb | 20 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index a5e375578ec..60561cc64d0 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -331,7 +331,7 @@ describe Issues::UpdateService, services: true do perform_enqueued_jobs { update_issue(title: user4.to_reference) } end - it "should email only the newly-mentioned user" do + it 'emails only the newly-mentioned user' do should_not_email(user) should_not_email(user2) should_not_email(user3) @@ -339,12 +339,12 @@ describe Issues::UpdateService, services: true do end end - context "in description" do + context 'in description' do before do perform_enqueued_jobs { update_issue(description: user4.to_reference) } end - it "should email only the newly-mentioned user" do + it 'emails only the newly-mentioned user' do should_not_email(user) should_not_email(user2) should_not_email(user3) diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index e26e925cd81..86d78f8c898 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -232,12 +232,12 @@ describe MergeRequests::UpdateService, services: true do project.team << [user4, :developer] end - context "in title" do + context 'in title' do before do perform_enqueued_jobs { update_merge_request(title: user4.to_reference) } end - it "should email only the newly-mentioned user" do + it 'emails only the newly-mentioned user' do should_not_email(user) should_not_email(user2) should_not_email(user3) @@ -245,12 +245,12 @@ describe MergeRequests::UpdateService, services: true do end end - context "in description" do + context 'in description' do before do perform_enqueued_jobs { update_merge_request(description: user4.to_reference) } end - it "should email only the newly-mentioned user" do + it 'emails only the newly-mentioned user' do should_not_email(user) should_not_email(user2) should_not_email(user3) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index fbca28bbb17..1cf644e1331 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -405,12 +405,12 @@ describe NotificationService, services: true do notification.new_mentions_in_issue(issue, new_mentions, @u_disabled) end - it "should not email anyone unless they are newly mentioned" do + it 'sends no emails when no new mentions are present' do send_notifications - expect(ActionMailer::Base.deliveries).to eq [] + expect(ActionMailer::Base.deliveries).to be_empty end - it "should email new mentions with a watch level higher than participant" do + it 'emails new mentions with a watch level higher than participant' do send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global) should_email(@u_watcher) @@ -420,9 +420,9 @@ describe NotificationService, services: true do expect(ActionMailer::Base.deliveries.count).to eq 3 end - it "should not email new mentions with a watch level equal to or less than participant" do + it 'does not email new mentions with a watch level equal to or less than participant' do send_notifications(@u_participating, @u_mentioned) - expect(ActionMailer::Base.deliveries).to eq [] + expect(ActionMailer::Base.deliveries).to be_empty end end @@ -798,12 +798,12 @@ describe NotificationService, services: true do notification.new_mentions_in_merge_request(merge_request, new_mentions, @u_disabled) end - it "should not email anyone unless they are newly mentioned" do + it 'sends no emails when there are no new mentions' do send_notifications - expect(ActionMailer::Base.deliveries).to eq [] + expect(ActionMailer::Base.deliveries).to be_empty end - it "should email new mentions with a watch level higher than participant" do + it 'emails new mentions with a watch level higher than participant' do send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global) should_email(@u_watcher) @@ -813,9 +813,9 @@ describe NotificationService, services: true do expect(ActionMailer::Base.deliveries.count).to eq 3 end - it "should not email new mentions with a watch level equal to or less than participant" do + it 'does not email new mentions with a watch level equal to or less than participant' do send_notifications(@u_participating, @u_mentioned) - expect(ActionMailer::Base.deliveries).to eq [] + expect(ActionMailer::Base.deliveries).to be_empty end end From b62954db4c40df435363994ce4632632fde01455 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 16 Aug 2016 14:12:05 +0100 Subject: [PATCH 17/64] Include the reason for emailing in the new_mention emails --- app/views/notify/new_mention_in_issue_email.html.haml | 3 +++ app/views/notify/new_mention_in_merge_request_email.html.haml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/views/notify/new_mention_in_issue_email.html.haml b/app/views/notify/new_mention_in_issue_email.html.haml index f42b150c0d6..4f3d36bd9ca 100644 --- a/app/views/notify/new_mention_in_issue_email.html.haml +++ b/app/views/notify/new_mention_in_issue_email.html.haml @@ -1,3 +1,6 @@ +%p + You have been mentioned in an issue. + - if current_application_settings.email_author_in_body %div #{link_to @issue.author_name, user_url(@issue.author)} wrote: diff --git a/app/views/notify/new_mention_in_merge_request_email.html.haml b/app/views/notify/new_mention_in_merge_request_email.html.haml index 158404de396..32aedb9e6b9 100644 --- a/app/views/notify/new_mention_in_merge_request_email.html.haml +++ b/app/views/notify/new_mention_in_merge_request_email.html.haml @@ -1,3 +1,6 @@ +%p + You have been mentioned in Merge Request #{@merge_request.to_reference} + - if current_application_settings.email_author_in_body %div #{link_to @merge_request.author_name, user_url(@merge_request.author)} wrote: From 0e9c4a902d7985d917fac1f833c9544133f83a95 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 16 Aug 2016 17:08:43 +0100 Subject: [PATCH 18/64] DRY up the added update service specs, add two email helpers --- spec/services/issues/update_service_spec.rb | 35 +++---------------- .../merge_requests/update_service_spec.rb | 34 ++---------------- spec/support/email_helpers.rb | 10 ++++++ .../updating_mentions_shared_examples.rb | 32 +++++++++++++++++ 4 files changed, 49 insertions(+), 62 deletions(-) create mode 100644 spec/support/updating_mentions_shared_examples.rb diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 60561cc64d0..d36937b6c59 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -320,37 +320,10 @@ describe Issues::UpdateService, services: true do end end - context 'updated user mentions' do - let(:user4) { create(:user) } - before do - project.team << [user4, :developer] - end - - context "in title" do - before do - perform_enqueued_jobs { update_issue(title: user4.to_reference) } - end - - it 'emails only the newly-mentioned user' do - should_not_email(user) - should_not_email(user2) - should_not_email(user3) - should_email(user4) - end - end - - context 'in description' do - before do - perform_enqueued_jobs { update_issue(description: user4.to_reference) } - end - - it 'emails only the newly-mentioned user' do - should_not_email(user) - should_not_email(user2) - should_not_email(user3) - should_email(user4) - end - end + context 'updating mentions' do + let(:mentionable) { issue } + include_examples 'updating mentions', Issues::UpdateService end + end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 86d78f8c898..6dfeb581975 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -226,37 +226,9 @@ describe MergeRequests::UpdateService, services: true do end end - context 'updated user mentions' do - let(:user4) { create(:user) } - before do - project.team << [user4, :developer] - end - - context 'in title' do - before do - perform_enqueued_jobs { update_merge_request(title: user4.to_reference) } - end - - it 'emails only the newly-mentioned user' do - should_not_email(user) - should_not_email(user2) - should_not_email(user3) - should_email(user4) - end - end - - context 'in description' do - before do - perform_enqueued_jobs { update_merge_request(description: user4.to_reference) } - end - - it 'emails only the newly-mentioned user' do - should_not_email(user) - should_not_email(user2) - should_not_email(user3) - should_email(user4) - end - end + context 'updating mentions' do + let(:mentionable) { merge_request } + include_examples 'updating mentions', MergeRequests::UpdateService end context 'when MergeRequest has tasks' do diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb index a85ab22ce36..0bfc4685532 100644 --- a/spec/support/email_helpers.rb +++ b/spec/support/email_helpers.rb @@ -3,6 +3,16 @@ module EmailHelpers ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1 end + def reset_delivered_emails! + ActionMailer::Base.deliveries.clear + end + + def should_only_email(*users) + users.each {|user| should_email(user) } + recipients = ActionMailer::Base.deliveries.flat_map(&:to) + expect(recipients.count).to eq(users.count) + end + def should_email(user) expect(sent_to_user?(user)).to be_truthy end diff --git a/spec/support/updating_mentions_shared_examples.rb b/spec/support/updating_mentions_shared_examples.rb new file mode 100644 index 00000000000..e0c59a5c280 --- /dev/null +++ b/spec/support/updating_mentions_shared_examples.rb @@ -0,0 +1,32 @@ +RSpec.shared_examples 'updating mentions' do |service_class| + let(:mentioned_user) { create(:user) } + let(:service_class) { service_class } + + before { project.team << [mentioned_user, :developer] } + + def update_mentionable(opts) + reset_delivered_emails! + + perform_enqueued_jobs do + service_class.new(project, user, opts).execute(mentionable) + end + + mentionable.reload + end + + context 'in title' do + before { update_mentionable(title: mentioned_user.to_reference) } + + it 'emails only the newly-mentioned user' do + should_only_email(mentioned_user) + end + end + + context 'in description' do + before { update_mentionable(description: mentioned_user.to_reference) } + + it 'emails only the newly-mentioned user' do + should_only_email(mentioned_user) + end + end +end From ffd7311d03485f146cef4b915d1f7d71f70d30bb Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 16 Aug 2016 17:40:04 +0100 Subject: [PATCH 19/64] DRY up the additions to notification_service_spec.rb --- spec/services/notification_service_spec.rb | 74 ++++++++-------------- 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 1cf644e1331..62c97e09288 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -9,6 +9,28 @@ describe NotificationService, services: true do end end + shared_examples 'notifications for new mentions' do + def send_notifications(*new_mentions) + reset_delivered_emails! + notification.send(notification_method, mentionable, new_mentions, @u_disabled) + end + + it 'sends no emails when no new mentions are present' do + send_notifications + expect(ActionMailer::Base.deliveries).to be_empty + end + + it 'emails new mentions with a watch level higher than participant' do + send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global) + should_only_email(@u_watcher, @u_participant_mentioned, @u_custom_global) + end + + it 'does not email new mentions with a watch level equal to or less than participant' do + send_notifications(@u_participating, @u_mentioned) + expect(ActionMailer::Base.deliveries).to be_empty + end + end + describe 'Keys' do describe '#new_key' do let!(:key) { create(:personal_key) } @@ -400,30 +422,10 @@ describe NotificationService, services: true do end describe '#new_mentions_in_issue' do - def send_notifications(*new_mentions) - ActionMailer::Base.deliveries.clear - notification.new_mentions_in_issue(issue, new_mentions, @u_disabled) - end + let(:notification_method) { :new_mentions_in_issue } + let(:mentionable) { issue } - it 'sends no emails when no new mentions are present' do - send_notifications - expect(ActionMailer::Base.deliveries).to be_empty - end - - it 'emails new mentions with a watch level higher than participant' do - send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global) - - should_email(@u_watcher) - should_email(@u_participant_mentioned) - should_email(@u_custom_global) - - expect(ActionMailer::Base.deliveries.count).to eq 3 - end - - it 'does not email new mentions with a watch level equal to or less than participant' do - send_notifications(@u_participating, @u_mentioned) - expect(ActionMailer::Base.deliveries).to be_empty - end + include_examples 'notifications for new mentions' end describe '#reassigned_issue' do @@ -793,30 +795,10 @@ describe NotificationService, services: true do end describe '#new_mentions_in_merge_request' do - def send_notifications(*new_mentions) - ActionMailer::Base.deliveries.clear - notification.new_mentions_in_merge_request(merge_request, new_mentions, @u_disabled) - end + let(:notification_method) { :new_mentions_in_merge_request } + let(:mentionable) { merge_request } - it 'sends no emails when there are no new mentions' do - send_notifications - expect(ActionMailer::Base.deliveries).to be_empty - end - - it 'emails new mentions with a watch level higher than participant' do - send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global) - - should_email(@u_watcher) - should_email(@u_participant_mentioned) - should_email(@u_custom_global) - - expect(ActionMailer::Base.deliveries.count).to eq 3 - end - - it 'does not email new mentions with a watch level equal to or less than participant' do - send_notifications(@u_participating, @u_mentioned) - expect(ActionMailer::Base.deliveries).to be_empty - end + include_examples 'notifications for new mentions' end describe '#reassigned_merge_request' do From bdbc3d355a86bf03e7a7a00fc2d867fa043c0bdd Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 8 Aug 2016 16:26:33 -0500 Subject: [PATCH 20/64] Add build data to top of pipeline page --- app/assets/stylesheets/pages/pipelines.scss | 6 ++++++ app/views/projects/commit/_pipeline.html.haml | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 21919fe4d73..5bc8c3a7e6c 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -229,3 +229,9 @@ box-shadow: none; } } + +// Pipeline visualization +.stage-column { + display: inline-block; + vertical-align: top; +} diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 640abdb993f..2452326fc04 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,4 +1,16 @@ .row-content-block.build-content.middle-block + .pipeline-visualization + - pipeline.statuses.stages.each do |stage| + - statuses = pipeline.statuses.where(stage: stage) + .stage-column + %strong + %a{name: stage} + - if stage + = stage.titleize + - statuses.each do |status| + %div= status.name + + .pull-right - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) From dfe94da0fb4da1a84bfd6324f7b2291067582af0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 8 Aug 2016 17:42:28 -0500 Subject: [PATCH 21/64] Connect top level tests to each other --- app/assets/stylesheets/pages/pipelines.scss | 50 +++++++++++++++++++ app/views/projects/commit/_pipeline.html.haml | 16 ++++-- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 5bc8c3a7e6c..e79c7e59720 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -231,7 +231,57 @@ } // Pipeline visualization + +.pipeline-visualization { + position: relative; +} + .stage-column { display: inline-block; vertical-align: top; + margin-right: 40px; + + .stage-name { + margin-bottom: 15px; + font-weight: bold; + } + + .builds-container { + + } + + .build { + border: 1px solid $border-color; + position: relative; + padding: 6px 10px; + border-radius: 30px; + width: 150px; + // TODO truncate text within .build div; not on build div itself + // white-space: nowrap; + // overflow: hidden; + // text-overflow: ellipsis; + margin-bottom: 10px; + + svg { + position: relative; + top: 2px; + margin-right: 5px; + } + + &:first-child { + &::after, &::before { + content: ''; + position: absolute; + top: 50%; + right: -44px; + border-top: 1px solid $border-color; + width: 44px; + height: 1px; + } + } + &:first-child::before, &:last-child::after{ + border: 0 none; + } + + } } diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 2452326fc04..b993ad5ce50 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,16 +1,22 @@ -.row-content-block.build-content.middle-block +.row-content-block.white + .pipeline-visualization - pipeline.statuses.stages.each do |stage| - statuses = pipeline.statuses.where(stage: stage) + - status = statuses.latest.status .stage-column - %strong + .stage-name %a{name: stage} - if stage = stage.titleize - - statuses.each do |status| - %div= status.name - + .builds-container + - statuses.each do |build| + .build + %span{class: "ci-status-link ci-status-icon-#{status}"} + = ci_icon_for_status(status) + = build.name +.row-content-block.build-content.middle-block .pull-right - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) From ca9bba29fea30fd4cdb223273fca007c9abf12b0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 14:26:25 -0500 Subject: [PATCH 22/64] Connect all builds in visual pipeline --- app/assets/stylesheets/pages/pipelines.scss | 81 +++++++++++++++++++-- 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e79c7e59720..fa422f4f3c5 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -234,6 +234,10 @@ .pipeline-visualization { position: relative; + + ul { + padding: 0; + } } .stage-column { @@ -241,15 +245,15 @@ vertical-align: top; margin-right: 40px; + li { + list-style: none; + } + .stage-name { margin-bottom: 15px; font-weight: bold; } - .builds-container { - - } - .build { border: 1px solid $border-color; position: relative; @@ -268,20 +272,81 @@ margin-right: 5px; } + // Connect first build in each stage with right horizontal line &:first-child { - &::after, &::before { + &::after { content: ''; position: absolute; top: 50%; right: -44px; - border-top: 1px solid $border-color; + border-top: 2px solid $border-color; width: 44px; height: 1px; } } - &:first-child::before, &:last-child::after{ - border: 0 none; + + // Connect each build (except for first) with curved lines + &:not(:first-child) { + &::after, &::before { + content: ''; + top: -47px; + position: absolute; + border-bottom: 2px solid $border-color; + width: 20px; + height: 65px; + } + + // Right connecting curves + &::after { + right: -21px; + border-right: 2px solid $border-color; + border-radius: 0 0 50px 0; + -webkit-border-radius: 0 0 50px 0; + } + + // Left connecting curves + &::before { + left: -21px; + border-left: 2px solid $border-color; + border-radius: 0 0 0 50px; + -webkit-border-radius: 0 0 0 50px; + } } + // Connect second build to first build with smaller curved line + &:nth-child(2) { + &::after, &::before { + height: 40px; + top: -26px; + } + } + } + + &:last-child { + .build { + // Remove right connecting horizontal line from first build in last stage + &:first-child { + &::after, &::before { + border: none; + } + } + // Remove right curved connectors from all builds in last stage + &:not(:first-child) { + &::after { + border: none; + } + } + } + } + + &:first-child { + .build { + // Remove left curved connectors from all builds in first stage + &:not(:first-child) { + &::before { + border: none; + } + } + } } } From 1b583583a9a93c2ba8e00982c33fca904e219fca Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 15:00:09 -0500 Subject: [PATCH 23/64] Add min width and horizontally scroll pipeline graph when overflow --- app/assets/stylesheets/pages/pipelines.scss | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index fa422f4f3c5..a9ffda00278 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -234,16 +234,22 @@ .pipeline-visualization { position: relative; + width: 100%; + overflow: auto; ul { padding: 0; } } +.stage-column-list { + min-width: 1220px; +} + .stage-column { display: inline-block; vertical-align: top; - margin-right: 40px; + margin-right: 50px; li { list-style: none; @@ -278,9 +284,9 @@ content: ''; position: absolute; top: 50%; - right: -44px; + right: -54px; border-top: 2px solid $border-color; - width: 44px; + width: 54px; height: 1px; } } @@ -298,7 +304,7 @@ // Right connecting curves &::after { - right: -21px; + right: -20px; border-right: 2px solid $border-color; border-radius: 0 0 50px 0; -webkit-border-radius: 0 0 50px 0; @@ -306,7 +312,7 @@ // Left connecting curves &::before { - left: -21px; + left: -20px; border-left: 2px solid $border-color; border-radius: 0 0 0 50px; -webkit-border-radius: 0 0 0 50px; @@ -316,9 +322,14 @@ // Connect second build to first build with smaller curved line &:nth-child(2) { &::after, &::before { - height: 40px; + height: 45px; top: -26px; } + &::after { + // border-left: 2px solid $border-color; + border-top-right-radius: -50px; + -webkit-border-top-right-radius: -50px; + } } } From d7b3b15f60e25e1b11b6fcc22f96c63bffa137a8 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 15:08:04 -0500 Subject: [PATCH 24/64] Truncate build and stage names --- CHANGELOG | 1 + app/assets/stylesheets/pages/pipelines.scss | 15 ++++++--- app/views/projects/commit/_pipeline.html.haml | 31 ++++++++++--------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index aececed9add..a3e824dcac6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -108,6 +108,7 @@ v 8.11.0 (unreleased) - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker - Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko) - Adds support for pending invitation project members importing projects + - Add pipeline visualization/graph on pipeline page - Update devise initializer to turn on changed password notification emails. !5648 (tombell) - Avoid to show the original password field when password is automatically set. !5712 (duduribeiro) - Fix importing GitLab projects with an invalid MR source project diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a9ffda00278..49225d7d0d4 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -258,6 +258,10 @@ .stage-name { margin-bottom: 15px; font-weight: bold; + width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .build { @@ -266,12 +270,15 @@ padding: 6px 10px; border-radius: 30px; width: 150px; - // TODO truncate text within .build div; not on build div itself - // white-space: nowrap; - // overflow: hidden; - // text-overflow: ellipsis; margin-bottom: 10px; + .build-content { + width: 130px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + svg { position: relative; top: 2px; diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index b993ad5ce50..4dacff8f6eb 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,20 +1,23 @@ .row-content-block.white .pipeline-visualization - - pipeline.statuses.stages.each do |stage| - - statuses = pipeline.statuses.where(stage: stage) - - status = statuses.latest.status - .stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - - statuses.each do |build| - .build - %span{class: "ci-status-link ci-status-icon-#{status}"} - = ci_icon_for_status(status) - = build.name + %ul.stage-column-list + - pipeline.statuses.stages.each do |stage| + - statuses = pipeline.statuses.where(stage: stage) + - status = statuses.latest.status + %li.stage-column + .stage-name + %a{name: stage} + - if stage + = stage.titleize + .builds-container + %ul + - statuses.each do |build| + %li.build + .build-content + %span{class: "ci-status-link ci-status-icon-#{status}"} + = ci_icon_for_status(status) + = build.name .row-content-block.build-content.middle-block .pull-right From e573cdfb91308759923fbd0aacb12f801dfb561f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 15:39:01 -0500 Subject: [PATCH 25/64] Fix graph scrolling bug --- app/assets/stylesheets/pages/pipelines.scss | 13 +++++++------ app/views/projects/commit/_pipeline.html.haml | 5 ++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 49225d7d0d4..01bd131940a 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -232,20 +232,21 @@ // Pipeline visualization -.pipeline-visualization { - position: relative; +.pipeline-graph { width: 100%; overflow: auto; + white-space: nowrap; +} + +.pipeline-visualization { + position: relative; + min-width: 1220px; ul { padding: 0; } } -.stage-column-list { - min-width: 1220px; -} - .stage-column { display: inline-block; vertical-align: top; diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 4dacff8f6eb..e2187673da7 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,5 +1,4 @@ -.row-content-block.white - +.row-content-block.build-content.middle-block.pipeline-graph .pipeline-visualization %ul.stage-column-list - pipeline.statuses.stages.each do |stage| @@ -19,7 +18,7 @@ = ci_icon_for_status(status) = build.name -.row-content-block.build-content.middle-block +.row-content-block.build-content.middle-block.pipeline-graph .pull-right - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) From 0ff0acc89c80b9ef4a473e4be1549ef4a21a8b8a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 16:15:09 -0500 Subject: [PATCH 26/64] Fix scss lint error --- app/assets/stylesheets/pages/pipelines.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 01bd131940a..ad256e2a728 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -314,8 +314,8 @@ &::after { right: -20px; border-right: 2px solid $border-color; - border-radius: 0 0 50px 0; - -webkit-border-radius: 0 0 50px 0; + border-radius: 0 0 50px; + -webkit-border-radius: 0 0 50px; } // Left connecting curves From a3ef844c0a59dd94bf8b8a962a92b6610758d5af Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 24 Jul 2016 01:13:13 +0200 Subject: [PATCH 27/64] fix star button icon alignment --- app/views/projects/buttons/_star.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 71cf5582a4c..311583037e5 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,10 +1,10 @@ - if current_user = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do - if current_user.starred?(@project) - = icon('star fw') + = icon('star') %span.starred Unstar - else - = icon('star-o fw') + = icon('star-o') %span Star %div.count-with-arrow %span.arrow @@ -13,7 +13,7 @@ - else = link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do - = icon('star fw') + = icon('star') Star %div.count-with-arrow %span.arrow From c4141bac76d4d869054a2c2979af7cf59ed9411f Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 24 Jul 2016 01:14:44 +0200 Subject: [PATCH 28/64] fix fork button icon alignment --- CHANGELOG | 1 + app/views/shared/icons/_icon_fork.svg | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2a7109b3c2b..4289aded1ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,6 +42,7 @@ v 8.11.0 (unreleased) - Remove unused images (ClemMakesApps) - Get issue and merge request description templates from repositories - Add hover state to todos !5361 (winniehell) + - Fix icon alignment of star and fork buttons !5451 (winniehell) - Limit git rev-list output count to one in forced push check - Show deployment status on merge requests with external URLs - Clean up unused routes (Josef Strzibny) diff --git a/app/views/shared/icons/_icon_fork.svg b/app/views/shared/icons/_icon_fork.svg index a21f8f3a951..fc970e4ce50 100644 --- a/app/views/shared/icons/_icon_fork.svg +++ b/app/views/shared/icons/_icon_fork.svg @@ -1,3 +1,3 @@ - + From 9810c71d9b61983840df61aa662041cffb9c9541 Mon Sep 17 00:00:00 2001 From: winniehell Date: Fri, 5 Aug 2016 00:37:32 +0200 Subject: [PATCH 29/64] add space between icon and button text --- app/assets/stylesheets/framework/buttons.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index f1fe1697d30..6c3786b49bb 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -204,6 +204,10 @@ position: relative; top: 2px; } + + svg, .fa { + margin-right: 3px; + } } .btn-lg { From 8d5dc4fa6b0f01dbe46e89105aed5072c60cc102 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 16 Aug 2016 19:42:05 -0500 Subject: [PATCH 30/64] Add toggle button to hide graph; set max-height --- app/assets/javascripts/pipeline.js.es6 | 10 ++++ app/assets/stylesheets/pages/pipelines.scss | 43 ++++++++++++++--- app/views/projects/commit/_pipeline.html.haml | 46 ++++++++++--------- 3 files changed, 71 insertions(+), 28 deletions(-) create mode 100644 app/assets/javascripts/pipeline.js.es6 diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 new file mode 100644 index 00000000000..7054199ac89 --- /dev/null +++ b/app/assets/javascripts/pipeline.js.es6 @@ -0,0 +1,10 @@ +function toggleGraph() { + $('.pipeline-graph, .toggle-pipeline-btn').toggleClass('graph-collapsed'); + + const $btnText = $('.toggle-pipeline-btn .btn-text'); + const graphCollapsed = $('.pipeline-graph').hasClass('graph-collapsed'); + + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') +} + +$(document).on('click', '.toggle-pipeline-btn', toggleGraph); diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index ad256e2a728..f35aca193e9 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -232,10 +232,35 @@ // Pipeline visualization +.toggle-pipeline-btn { + background-color: $gray-dark; + + .caret { + border-top: none; + border-bottom: 4px solid; + } + + &.graph-collapsed { + background-color: $white-light; + + .caret { + border-bottom: none; + border-top: 4px solid; + } + } +} + .pipeline-graph { width: 100%; overflow: auto; white-space: nowrap; + max-height: 500px; + transition: max-height 0.3s, padding 0.3s; + + &.graph-collapsed { + max-height: 0; + padding: 0 16px; + } } .pipeline-visualization { @@ -315,7 +340,6 @@ right: -20px; border-right: 2px solid $border-color; border-radius: 0 0 50px; - -webkit-border-radius: 0 0 50px; } // Left connecting curves @@ -323,7 +347,6 @@ left: -20px; border-left: 2px solid $border-color; border-radius: 0 0 0 50px; - -webkit-border-radius: 0 0 0 50px; } } @@ -333,11 +356,6 @@ height: 45px; top: -26px; } - &::after { - // border-left: 2px solid $border-color; - border-top-right-radius: -50px; - -webkit-border-top-right-radius: -50px; - } } } @@ -369,3 +387,14 @@ } } } + +.pipeline-actions { + border-bottom: none; +} + +.toggle-pipeline-btn { + + .fa { + color: $dropdown-header-color; + } +} diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index e2187673da7..eb80aadd524 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,25 +1,9 @@ -.row-content-block.build-content.middle-block.pipeline-graph - .pipeline-visualization - %ul.stage-column-list - - pipeline.statuses.stages.each do |stage| - - statuses = pipeline.statuses.where(stage: stage) - - status = statuses.latest.status - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - - statuses.each do |build| - %li.build - .build-content - %span{class: "ci-status-link ci-status-icon-#{status}"} - = ci_icon_for_status(status) - = build.name - -.row-content-block.build-content.middle-block.pipeline-graph +.row-content-block.build-content.middle-block.pipeline-actions .pull-right + .btn.btn-grouped.btn-white.toggle-pipeline-btn + %span.btn-text Hide + %span pipeline graph + %span.caret - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post @@ -43,6 +27,26 @@ in = time_interval_in_words pipeline.duration +.row-content-block.build-content.middle-block.pipeline-graph + .pipeline-visualization + %ul.stage-column-list + - pipeline.statuses.stages.each do |stage| + - statuses = pipeline.statuses.where(stage: stage) + - status = statuses.latest.status + %li.stage-column + .stage-name + %a{name: stage} + - if stage + = stage.titleize + .builds-container + %ul + - statuses.each do |build| + %li.build + .build-content + %span{class: "ci-status-link ci-status-icon-#{status}"} + = ci_icon_for_status(status) + = build.name + - if pipeline.yaml_errors.present? .bs-callout.bs-callout-danger %h4 Found errors in your .gitlab-ci.yml: From b631985ad7a8a85d1350425e6d1cf1cb90c93b5f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 16 Aug 2016 19:52:34 -0500 Subject: [PATCH 31/64] Add links to pipeline graph --- app/assets/stylesheets/pages/pipelines.scss | 4 ++++ app/views/projects/commit/_pipeline.html.haml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index f35aca193e9..ce9e3e95d3d 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -303,6 +303,10 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + + a { + color: $layout-link-gray; + } } svg { diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index eb80aadd524..44250860020 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -45,7 +45,7 @@ .build-content %span{class: "ci-status-link ci-status-icon-#{status}"} = ci_icon_for_status(status) - = build.name + = link_to build.name, namespace_project_build_url(build.project.namespace, build.project, build) - if pipeline.yaml_errors.present? .bs-callout.bs-callout-danger From 2797cfbf604be698f5b0aaf7178a9aad78d2805b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 17 Aug 2016 09:46:13 +0100 Subject: [PATCH 32/64] Fix a rubocop violation --- spec/services/issues/update_service_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index d36937b6c59..0313f424463 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -324,6 +324,5 @@ describe Issues::UpdateService, services: true do let(:mentionable) { issue } include_examples 'updating mentions', Issues::UpdateService end - end end From fee7992c08c434940f0722886dc96f249a8e7fbf Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 17 Aug 2016 09:46:34 +0100 Subject: [PATCH 33/64] Fix pipelines visualisation rendering --- app/models/ci/build.rb | 2 +- app/models/ci/pipeline.rb | 4 ++++ app/views/projects/commit/_pipeline.html.haml | 16 ++++++++++------ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 08f396210c9..b42977f9ebd 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -102,7 +102,7 @@ module Ci end def playable? - project.builds_enabled? && commands.present? && manual? + project.builds_enabled? && commands.present? && manual? && skipped? end def play(current_user = nil) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index bce6a992af6..caf4d25029f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -34,6 +34,10 @@ module Ci CommitStatus.where(pipeline: pluck(:id)).stages end + def stages + statuses.order(:stage_idx).latest.group_by(&:stage) + end + def project_id project.id end diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 0c006656d1d..27501d89dc5 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,9 +1,8 @@ .row-content-block.build-content.middle-block.pipeline-graph .pipeline-visualization %ul.stage-column-list - - pipeline.statuses.stages.each do |stage| - - statuses = pipeline.statuses.where(stage: stage) - - status = statuses.latest.status + - stages = pipeline.statuses.latest.order(:stage_idx).group_by(&:stage) + - stages.each do |stage, builds| %li.stage-column .stage-name %a{name: stage} @@ -11,11 +10,16 @@ = stage.titleize .builds-container %ul - - statuses.each do |build| + - builds.each do |build| %li.build .build-content - %span{class: "ci-status-link ci-status-icon-#{status}"} - = ci_icon_for_status(status) + %span{class: "ci-status-link ci-status-icon-#{build.status}"} + - if build.try(:playable?) + = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play' do + = icon('play') + - else + = link_to namespace_project_build_url(build.project.namespace, build.project, build) do + = ci_icon_for_status(build.status) = build.name .row-content-block.build-content.middle-block.pipeline-graph From f52f62d7aec168b3b8c291cdb81f64501bd80e5a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Aug 2016 16:18:44 +0200 Subject: [PATCH 34/64] Update fixtures to make development testing easier --- db/fixtures/development/14_builds.rb | 32 ++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb index e65abe4ef77..3734c861078 100644 --- a/db/fixtures/development/14_builds.rb +++ b/db/fixtures/development/14_builds.rb @@ -28,24 +28,44 @@ class Gitlab::Seeder::Builds build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success) commit_status_create!(pipeline, name: 'jenkins', status: :success) - print '.' rescue ActiveRecord::RecordInvalid print 'F' + ensure + pipeline.build_updated end end end def pipelines - commits = @project.repository.commits('master', limit: 5) - commits_sha = commits.map { |commit| commit.raw.id } - commits_sha.map do |sha| - @project.ensure_pipeline(sha, 'master') - end + master_pipelines + merge_request_pipelines + end + + def master_pipelines + create_pipelines_for(@project, 'master') rescue [] end + def merge_request_pipelines + @project.merge_requests.last(5).map do |merge_request| + create_pipelines(merge_request.source_project, merge_request.source_branch, merge_request.commits.last(5)) + end.flatten + rescue + [] + end + + def create_pipelines_for(project, ref) + commits = project.repository.commits(ref, limit: 5) + create_pipelines(project, ref, commits) + end + + def create_pipelines(project, ref, commits) + commits.map do |commit| + project.pipelines.create(sha: commit.id, ref: ref) + end + end + def build_create!(pipeline, opts = {}) attributes = build_attributes_for(pipeline, opts) build = Ci::Build.create!(attributes) From fd09311da93070546c52e65c0c2af9b8566c56b5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 17 Aug 2016 11:18:57 +0100 Subject: [PATCH 35/64] Update fixtures to add a created status to pipeline --- db/fixtures/development/14_builds.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb index 0d493fa1c3c..069d9dd6226 100644 --- a/db/fixtures/development/14_builds.rb +++ b/db/fixtures/development/14_builds.rb @@ -1,9 +1,8 @@ class Gitlab::Seeder::Builds - STAGES = %w[build notify_build test notify_test deploy notify_deploy] + STAGES = %w[build test deploy notify] BUILDS = [ { name: 'build:linux', stage: 'build', status: :success }, { name: 'build:osx', stage: 'build', status: :success }, - { name: 'slack post build', stage: 'notify_build', status: :success }, { name: 'rspec:linux', stage: 'test', status: :success }, { name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:windows', stage: 'test', status: :success }, @@ -12,9 +11,9 @@ class Gitlab::Seeder::Builds { name: 'spinach:osx', stage: 'test', status: :canceled }, { name: 'cucumber:linux', stage: 'test', status: :running }, { name: 'cucumber:osx', stage: 'test', status: :failed }, - { name: 'slack post test', stage: 'notify_test', status: :success }, { name: 'staging', stage: 'deploy', environment: 'staging', status: :success }, - { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success }, + { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, + { name: 'slack', stage: 'notify', when: 'manual', status: :created }, ] def initialize(project) @@ -25,7 +24,7 @@ class Gitlab::Seeder::Builds pipelines.each do |pipeline| begin BUILDS.each { |opts| build_create!(pipeline, opts) } - commit_status_create!(pipeline, name: 'jenkins', status: :success) + commit_status_create!(pipeline, name: 'jenkins', stage: 'test', status: :success) print '.' rescue ActiveRecord::RecordInvalid print 'F' From 1cd9b3b8a0b1024d043b9344869aceeadb9c84f1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 17 Aug 2016 11:21:00 +0100 Subject: [PATCH 36/64] Split pipeline status item for Commit Status and Build --- app/models/ci/pipeline.rb | 4 ++-- .../ci/builds/_build_pipeline.html.haml | 12 ++++++++++++ app/views/projects/commit/_pipeline.html.haml | 17 ++++------------- .../_generic_commit_status_pipeline.html.haml | 9 +++++++++ 4 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 app/views/projects/ci/builds/_build_pipeline.html.haml create mode 100644 app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d3fc5191721..c360a6ff729 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -78,8 +78,8 @@ module Ci CommitStatus.where(pipeline: pluck(:id)).stages end - def stages - statuses.order(:stage_idx).latest.group_by(&:stage) + def stages_with_latest_statuses + statuses.latest.order(:stage_idx).group_by(&:stage) end def project_id diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml new file mode 100644 index 00000000000..5149d75e5a2 --- /dev/null +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -0,0 +1,12 @@ +%li.build + .build-content + %span{class: "ci-status-link ci-status-icon-#{subject.status}"} + - if subject.playable? && can?(current_user, :update_build, @project) + = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do + = icon('play') + - elsif can?(current_user, :read_build, @project) && subject.started? + = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do + = ci_icon_for_status(subject.status) + - else + = ci_icon_for_status(subject.status) + = subject.name diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 5c8f40acfe3..7a7f61e9705 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,8 +1,8 @@ .row-content-block.build-content.middle-block.pipeline-graph .pipeline-visualization %ul.stage-column-list - - stages = pipeline.statuses.latest.order(:stage_idx).group_by(&:stage) - - stages.each do |stage, builds| + - stages = pipeline.stages_with_latest_statuses + - stages.each do |stage, statuses| %li.stage-column .stage-name %a{name: stage} @@ -10,17 +10,8 @@ = stage.titleize .builds-container %ul - - builds.each do |build| - %li.build - .build-content - %span{class: "ci-status-link ci-status-icon-#{build.status}"} - - if build.try(:playable?) - = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play' do - = icon('play') - - else - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - = ci_icon_for_status(build.status) - = build.name + - statuses.each do |status| + = render "projects/#{status.to_partial_path}_pipeline", subject: status .row-content-block.build-content.middle-block.pipeline-graph .pull-right diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml new file mode 100644 index 00000000000..760918b18a3 --- /dev/null +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -0,0 +1,9 @@ +%li.build + .build-content + %span{class: "ci-status-link ci-status-icon-#{subject.status}"} + - if subject.target_url + - link_to subject.target_url do + = ci_icon_for_status(subject.status) + - else + = ci_icon_for_status(subject.status) + = subject.name From 49e7070adfcb281938a43aceeb52cc0257601e1d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 17 Aug 2016 12:08:45 +0100 Subject: [PATCH 37/64] Add support for Play and Created jobs --- app/helpers/ci_status_helper.rb | 26 ++++++++++++------- .../ci/builds/_build_pipeline.html.haml | 22 +++++++++------- app/views/projects/commit/_pipeline.html.haml | 14 ++++------ .../_generic_commit_status_pipeline.html.haml | 14 +++++----- 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index ea2f5f9281a..573cdef767e 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -38,6 +38,10 @@ module CiStatusHelper 'icon_status_pending' when 'running' 'icon_status_running' + when 'play' + 'icon_status_warning' + when 'created' + 'icon_status_pending' else 'icon_status_cancel' end @@ -48,13 +52,13 @@ module CiStatusHelper def render_commit_status(commit, tooltip_placement: 'auto left') project = commit.project path = builds_namespace_project_commit_path(project.namespace, project, commit) - render_status_with_link('commit', commit.status, path, tooltip_placement) + render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement) end def render_pipeline_status(pipeline, tooltip_placement: 'auto left') project = pipeline.project path = namespace_project_pipeline_path(project.namespace, project, pipeline) - render_status_with_link('pipeline', pipeline.status, path, tooltip_placement) + render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement) end def no_runners_for_project?(project) @@ -62,13 +66,17 @@ module CiStatusHelper Ci::Runner.shared.blank? end - private + def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '') + klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" + title = "#{type.titleize}: #{ci_label_for_status(status)}" + data = { toggle: 'tooltip', placement: tooltip_placement } - def render_status_with_link(type, status, path, tooltip_placement, cssclass: '') - link_to ci_icon_for_status(status), - path, - class: "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}", - title: "#{type.titleize}: #{ci_label_for_status(status)}", - data: { toggle: 'tooltip', placement: tooltip_placement } + if path + link_to ci_icon_for_status(status), path, + class: klass, title: title, data: data + else + content_tag :span, ci_icon_for_status(status), + class: klass, title: title, data: data + end end end diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 5149d75e5a2..8c8e0efc60f 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,12 +1,14 @@ %li.build .build-content - %span{class: "ci-status-link ci-status-icon-#{subject.status}"} - - if subject.playable? && can?(current_user, :update_build, @project) - = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do - = icon('play') - - elsif can?(current_user, :read_build, @project) && subject.started? - = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = ci_icon_for_status(subject.status) - - else - = ci_icon_for_status(subject.status) - = subject.name + - if subject.playable? && can?(current_user, :update_build, @project) + = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do + = render_status_with_link('build', 'play') + = subject.name + - elsif can?(current_user, :read_build, @project) + = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do + = render_status_with_link('build', subject.status) + = subject.name + - else + = render_status_with_link('build', subject.status) + = ci_icon_for_status(subject.status) + diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 44250860020..9fa54057823 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -30,9 +30,8 @@ .row-content-block.build-content.middle-block.pipeline-graph .pipeline-visualization %ul.stage-column-list - - pipeline.statuses.stages.each do |stage| - - statuses = pipeline.statuses.where(stage: stage) - - status = statuses.latest.status + - stages = pipeline.stages_with_latest_statuses + - stages.each do |stage, statuses| %li.stage-column .stage-name %a{name: stage} @@ -40,12 +39,9 @@ = stage.titleize .builds-container %ul - - statuses.each do |build| - %li.build - .build-content - %span{class: "ci-status-link ci-status-icon-#{status}"} - = ci_icon_for_status(status) - = link_to build.name, namespace_project_build_url(build.project.namespace, build.project, build) + - statuses.each do |status| + = render "projects/#{status.to_partial_path}_pipeline", subject: status + - if pipeline.yaml_errors.present? .bs-callout.bs-callout-danger diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 760918b18a3..584c0fa18ae 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,9 +1,9 @@ %li.build .build-content - %span{class: "ci-status-link ci-status-icon-#{subject.status}"} - - if subject.target_url - - link_to subject.target_url do - = ci_icon_for_status(subject.status) - - else - = ci_icon_for_status(subject.status) - = subject.name + - if subject.target_url + - link_to subject.target_url do + = render_status_with_link('commit status', subject.status) + = subject.name + - else + = render_status_with_link('commit status', subject.status) + = subject.name From c0a722a223e50efeab5ed3f10523d83806a09215 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 15 Aug 2016 16:10:22 +0100 Subject: [PATCH 38/64] Removed sleep calls introduced in !5740 --- features/steps/project/issues/issues.rb | 2 -- features/steps/project/merge_requests.rb | 1 - 2 files changed, 3 deletions(-) diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index daee90b3767..35f166c7c08 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -354,8 +354,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end def filter_issue(text) - sleep 1 fill_in 'issue_search', with: text - sleep 1 end end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 53d1aedf27f..f3d78780da3 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -489,7 +489,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I fill in merge request search with "Fe"' do - sleep 1 fill_in 'issue_search', with: "Fe" end From 8691561016338a58e314cdaa69911523b35c6df4 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 17 Aug 2016 13:33:44 +0100 Subject: [PATCH 39/64] Now waits for All issuables to load when clicked --- features/steps/project/issues/issues.rb | 2 ++ features/steps/project/merge_requests.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index 35f166c7c08..056462a7152 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -45,6 +45,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps step 'I click link "All"' do click_link "All" + # Waits for load + expect(find('.issues-state-filters > .active')).to have_content 'All' end step 'I click link "Release 0.4"' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index f3d78780da3..9778ff4a6c7 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -22,6 +22,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I click link "All"' do click_link "All" + # Waits for load + expect(find('.issues-state-filters > .active')).to have_content 'All' end step 'I click link "Merged"' do From 4e66551a6614283f5b085a523bdbfbc552edb976 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 17 Aug 2016 10:06:10 -0500 Subject: [PATCH 40/64] Add deploy btn in graph; fix JS to toggle one graph at a time if multiple pipelines --- app/assets/javascripts/pipeline.js.es6 | 9 ++++++--- app/assets/stylesheets/pages/pipelines.scss | 8 ++++++++ app/helpers/ci_status_helper.rb | 4 ++-- app/views/projects/ci/builds/_build_pipeline.html.haml | 3 +-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 7054199ac89..ae7369004de 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,8 +1,11 @@ function toggleGraph() { - $('.pipeline-graph, .toggle-pipeline-btn').toggleClass('graph-collapsed'); + const indexOfBtn = $('.toggle-pipeline-btn').index($(this)); - const $btnText = $('.toggle-pipeline-btn .btn-text'); - const graphCollapsed = $('.pipeline-graph').hasClass('graph-collapsed'); + $($('.pipeline-graph')[indexOfBtn]).toggleClass('graph-collapsed'); + $($('.toggle-pipeline-btn')[indexOfBtn]).toggleClass('graph-collapsed'); + + const $btnText = $($('.toggle-pipeline-btn .btn-text')[indexOfBtn]); + const graphCollapsed = $($('.pipeline-graph')[indexOfBtn]).hasClass('graph-collapsed'); graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index ce9e3e95d3d..e49543d661f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -298,6 +298,10 @@ width: 150px; margin-bottom: 10px; + &.deployable { + background-color: $gray-light; + } + .build-content { width: 130px; white-space: nowrap; @@ -315,6 +319,10 @@ margin-right: 5px; } + .fa { + font-size: 13px; + } + // Connect first build in each stage with right horizontal line &:first-child { &::after { diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 573cdef767e..eb385d58e3b 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -39,14 +39,14 @@ module CiStatusHelper when 'running' 'icon_status_running' when 'play' - 'icon_status_warning' + 'play' when 'created' 'icon_status_pending' else 'icon_status_cancel' end - custom_icon(icon_name) + status == 'play' ? icon(icon_name + ' fw') : custom_icon(icon_name) end def render_commit_status(commit, tooltip_placement: 'auto left') diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 8c8e0efc60f..6be75860db2 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,4 +1,4 @@ -%li.build +%li.build{class: ("deployable" if subject.playable? && can?(current_user, :update_build, @project))} .build-content - if subject.playable? && can?(current_user, :update_build, @project) = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do @@ -11,4 +11,3 @@ - else = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) - From bfe2259bb76b320bd148de4fbdcebfd1b70a59ba Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 17 Aug 2016 10:20:46 -0500 Subject: [PATCH 41/64] Update CSS selector and play icon --- app/assets/stylesheets/pages/pipelines.scss | 2 +- app/helpers/ci_status_helper.rb | 4 ++-- app/views/projects/ci/builds/_build_pipeline.html.haml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e49543d661f..30239d609bc 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -298,7 +298,7 @@ width: 150px; margin-bottom: 10px; - &.deployable { + &.playable { background-color: $gray-light; } diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index eb385d58e3b..94df7d131ca 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -39,14 +39,14 @@ module CiStatusHelper when 'running' 'icon_status_running' when 'play' - 'play' + return icon('play fw') when 'created' 'icon_status_pending' else 'icon_status_cancel' end - status == 'play' ? icon(icon_name + ' fw') : custom_icon(icon_name) + custom_icon(icon_name) end def render_commit_status(commit, tooltip_placement: 'auto left') diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 6be75860db2..13088ef64d9 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,4 +1,4 @@ -%li.build{class: ("deployable" if subject.playable? && can?(current_user, :update_build, @project))} +%li.build{class: ("playable" if subject.playable? && can?(current_user, :update_build, @project))} .build-content - if subject.playable? && can?(current_user, :update_build, @project) = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do From 2c23465d05fa1efa4213bd5d7b0e86b1cd521212 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 17 Aug 2016 10:48:49 -0500 Subject: [PATCH 42/64] Move deployable conditional to variable; fix pipelines_spec --- app/views/projects/ci/builds/_build_pipeline.html.haml | 5 +++-- spec/features/projects/pipelines_spec.rb | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 13088ef64d9..04cbd0c3591 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,6 +1,7 @@ -%li.build{class: ("playable" if subject.playable? && can?(current_user, :update_build, @project))} +- is_playable = subject.playable? && can?(current_user, :update_build, @project) +%li.build{class: ("playable" if is_playable)} .build-content - - if subject.playable? && can?(current_user, :update_build, @project) + - if is_playable = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do = render_status_with_link('build', 'play') = subject.name diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb index 29d150bc597..47482bc3cc9 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines_spec.rb @@ -193,7 +193,11 @@ describe "Pipelines" do end context 'playing manual build' do - before { click_link('Play') } + before do + within '.pipeline-holder' do + click_link('Play') + end + end it { expect(@manual.reload).to be_pending } end From 8e4ec561dc6771d13fee289e1c8247b71b289d08 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 29 Jul 2016 11:23:05 -0500 Subject: [PATCH 43/64] Order by build status --- app/views/projects/builds/show.html.haml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 4421f3b9562..ce9522b9929 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -8,7 +8,19 @@ - builds = @build.pipeline.builds.latest.to_a - if builds.size > 1 %ul.nav-links.no-top.no-bottom - - builds.each do |build| + - statuses = ["failed", "pending", "running", "canceled", "skipped", "success"] + - statuses.each do |build_status| + - builds.select{|build| build.status == build_status}.each do |build| + %li{class: ('active' if build == @build) } + = link_to namespace_project_build_path(@project.namespace, @project, build) do + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id + + - builds.select{|build| build.status == "success"}.each do |build| %li{class: ('active' if build == @build) } = link_to namespace_project_build_path(@project.namespace, @project, build) do = ci_icon_for_status(build.status) From 283c1dc3301a8583f21feac71aed2c4d6773ccd8 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 1 Aug 2016 15:46:50 -0500 Subject: [PATCH 44/64] Add overflow tests to dropdown --- app/assets/javascripts/build.js | 11 +++++++++++ app/views/projects/builds/show.html.haml | 18 +++++++----------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 3d9b824d406..3468c87f5df 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -20,6 +20,7 @@ $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); this.updateArtifactRemoveDate(); + this.displayTestStatuses(); if ($('#build-trace').length) { this.getInitialBuildTrace(); this.initScrollButtonAffix(); @@ -132,6 +133,16 @@ } }; + Build.prototype.displayTestStatuses = function() { + $jobTopPosition = $('.build-job').first().offset().top; + + $('.build-job').each(function() { + if ($(this).offset().top > $jobTopPosition) { + $('.overflow-jobs > li').append($(this)); + } + }) + }; + return Build; })(); diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index ce9522b9929..8101d7a13da 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -12,23 +12,19 @@ - statuses.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| %li{class: ('active' if build == @build) } - = link_to namespace_project_build_path(@project.namespace, @project, build) do + = link_to namespace_project_build_path(@project.namespace, @project, build), class: 'build-job' do = ci_icon_for_status(build.status) %span - if build.name = build.name - else = build.id - - - builds.select{|build| build.status == "success"}.each do |build| - %li{class: ('active' if build == @build) } - = link_to namespace_project_build_path(@project.namespace, @project, build) do - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id + .dropdown + %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %span Test Name + = icon('chevron-down') + %ul.dropdown-menu.overflow-jobs + %li - if @build.retried? %li.active From d3cd603b46f55c0048445578fbb6d4819bff8cd1 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 1 Aug 2016 17:51:23 -0500 Subject: [PATCH 45/64] Hide dropdown if all tests fit on one line; add counter to dropdown --- app/assets/javascripts/build.js | 10 +++++++++- app/assets/stylesheets/pages/builds.scss | 10 ++++++++++ app/views/projects/builds/show.html.haml | 16 ++++++++-------- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 3468c87f5df..4b28436b524 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -135,12 +135,20 @@ Build.prototype.displayTestStatuses = function() { $jobTopPosition = $('.build-job').first().offset().top; + $dropdownCounter = []; - $('.build-job').each(function() { + $('.build-job a').each(function() { if ($(this).offset().top > $jobTopPosition) { + $dropdownCounter.push($(this)); $('.overflow-jobs > li').append($(this)); } }) + + $('.more-tests').text('More (' + $dropdownCounter.length + ')'); + + if ($('.overflow-jobs > li > a').length == 0) { + $('.overflow-jobs-dropdown').hide(); + } }; return Build; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index e26f8f7080d..9e79ad0db89 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -61,6 +61,16 @@ margin-right: 3px; } } + + .overflow-jobs-dropdown { + float: right; + padding: 7px 0 7px 5px; + + .dropdown-menu-toggle { + width: auto; + border: none; + } + } } .build-header { diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 8101d7a13da..f3718b3cb49 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -7,24 +7,24 @@ - builds = @build.pipeline.builds.latest.to_a - if builds.size > 1 + .dropdown.overflow-jobs-dropdown + %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %span.more-tests More + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right.overflow-jobs + %li %ul.nav-links.no-top.no-bottom - statuses = ["failed", "pending", "running", "canceled", "skipped", "success"] - statuses.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| - %li{class: ('active' if build == @build) } - = link_to namespace_project_build_path(@project.namespace, @project, build), class: 'build-job' do + %li.build-job{class: ('active' if build == @build) } + = link_to namespace_project_build_path(@project.namespace, @project, build) do = ci_icon_for_status(build.status) %span - if build.name = build.name - else = build.id - .dropdown - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} - %span Test Name - = icon('chevron-down') - %ul.dropdown-menu.overflow-jobs - %li - if @build.retried? %li.active From 4d32a8b7e9a07f82b6c12bbed4e772da68da4707 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 1 Aug 2016 17:57:50 -0500 Subject: [PATCH 46/64] Change active state of list items; style dropdown items --- app/assets/stylesheets/pages/builds.scss | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 9e79ad0db89..3039475695c 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -60,6 +60,18 @@ top: 2px; margin-right: 3px; } + + .build-job { + a { + color: $gl-text-color; + } + + &.active { + a { + font-weight: bold; + } + } + } } .overflow-jobs-dropdown { @@ -70,6 +82,12 @@ width: auto; border: none; } + + svg { + position: relative; + top: 2px; + margin-right: 3px; + } } } From 5bfca3b16d12e63675233f1d91e74db35c2545e3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 3 Aug 2016 13:52:06 -0500 Subject: [PATCH 47/64] Add data attributes to builds --- app/assets/javascripts/build.js | 22 ++++---------- app/assets/stylesheets/pages/builds.scss | 9 +----- app/views/projects/builds/show.html.haml | 37 ++++++++++++++---------- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 4b28436b524..3482c580edb 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -16,11 +16,11 @@ clearInterval(Build.interval); this.bp = Breakpoints.get(); this.hideSidebar(); + this.displayTestStatuses(); $('.js-build-sidebar').niceScroll(); $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); this.updateArtifactRemoveDate(); - this.displayTestStatuses(); if ($('#build-trace').length) { this.getInitialBuildTrace(); this.initScrollButtonAffix(); @@ -134,21 +134,11 @@ }; Build.prototype.displayTestStatuses = function() { - $jobTopPosition = $('.build-job').first().offset().top; - $dropdownCounter = []; - - $('.build-job a').each(function() { - if ($(this).offset().top > $jobTopPosition) { - $dropdownCounter.push($(this)); - $('.overflow-jobs > li').append($(this)); - } - }) - - $('.more-tests').text('More (' + $dropdownCounter.length + ')'); - - if ($('.overflow-jobs > li > a').length == 0) { - $('.overflow-jobs-dropdown').hide(); - } + $jobs = $('.build-job'); + jobjects = $jobs.map(function () { + return $(this).data(); + }); + console.log(jobjects); }; return Build; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 3039475695c..5a6a35f42d1 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -74,14 +74,7 @@ } } - .overflow-jobs-dropdown { - float: right; - padding: 7px 0 7px 5px; - - .dropdown-menu-toggle { - width: auto; - border: none; - } + .jobs-dropdown { svg { position: relative; diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index f3718b3cb49..3fedba488c8 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -7,24 +7,31 @@ - builds = @build.pipeline.builds.latest.to_a - if builds.size > 1 - .dropdown.overflow-jobs-dropdown + .dropdown.stage %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.more-tests More = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right.overflow-jobs - %li - %ul.nav-links.no-top.no-bottom - - statuses = ["failed", "pending", "running", "canceled", "skipped", "success"] - - statuses.each do |build_status| - - builds.select{|build| build.status == build_status}.each do |build| - %li.build-job{class: ('active' if build == @build) } - = link_to namespace_project_build_path(@project.namespace, @project, build) do - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id + %ul.dropdown-menu.overflow-jobs + - builds.each do |build| + %li + %a= build.stage + + - statuses = ["failed", "pending", "running", "canceled", "skipped", "success"] + .dropdown.jobs-dropdown + %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %span.more-tests More + = icon('caret-down') + %ul.dropdown-menu.overflow-jobs + - statuses.each do |build_status| + - builds.select{|build| build.status == build_status}.each do |build| + %li.build-job{class: ('active' if build == @build), data: {id: build.id, name: build.name, status: build.status}} + = link_to namespace_project_build_path(@project.namespace, @project, build) do + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id - if @build.retried? %li.active From f97939a791ae1e71f8e9d366cdfe106decb9545e Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 3 Aug 2016 15:04:40 -0500 Subject: [PATCH 48/64] Selecting stage updates builds dropdown --- app/assets/javascripts/build.js | 21 +++++++++++++-------- app/views/projects/builds/show.html.haml | 12 ++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 3482c580edb..baa91e221e8 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -13,13 +13,14 @@ this.state = state1; this.hideSidebar = bind(this.hideSidebar, this); this.toggleSidebar = bind(this.toggleSidebar, this); + this.updateDropdown = bind(this.updateDropdown, this); clearInterval(Build.interval); this.bp = Breakpoints.get(); - this.hideSidebar(); - this.displayTestStatuses(); $('.js-build-sidebar').niceScroll(); + this.hideSidebar(); $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); + $(document).on('click', '.stage-item', this.updateDropdown); this.updateArtifactRemoveDate(); if ($('#build-trace').length) { this.getInitialBuildTrace(); @@ -133,12 +134,16 @@ } }; - Build.prototype.displayTestStatuses = function() { - $jobs = $('.build-job'); - jobjects = $jobs.map(function () { - return $(this).data(); - }); - console.log(jobjects); + Build.prototype.populateJobDropdown = function(stage) { + $('.build-job').hide(); + $('.build-job[data-stage="' + stage + '"]').show(); + }; + + Build.prototype.updateDropdown = function(e) { + e.preventDefault(); + var stage = e.target.text; + $('.stage-selection').text(stage); + this.populateJobDropdown(stage); }; return Build; diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 3fedba488c8..2302aa2d1e7 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -6,25 +6,25 @@ = render "header" - builds = @build.pipeline.builds.latest.to_a + - statuses = ["failed", "pending", "running", "canceled", "skipped", "success"] - if builds.size > 1 .dropdown.stage %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} - %span.more-tests More + %span.stage-selection More = icon('caret-down') %ul.dropdown-menu.overflow-jobs - - builds.each do |build| + - builds.map(&:stage).uniq.each do |stage| %li - %a= build.stage + %a.stage-item= stage - - statuses = ["failed", "pending", "running", "canceled", "skipped", "success"] .dropdown.jobs-dropdown %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} - %span.more-tests More + %span.build-selection More = icon('caret-down') %ul.dropdown-menu.overflow-jobs - statuses.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| - %li.build-job{class: ('active' if build == @build), data: {id: build.id, name: build.name, status: build.status}} + %li.build-job{class: ('active' if build == @build), data: {id: build.id, name: build.name, stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do = ci_icon_for_status(build.status) %span From 0b9cd0a1f635d1e3751aa1279d52292d23a0a9dc Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 3 Aug 2016 15:40:10 -0500 Subject: [PATCH 49/64] Populate dropdowns with current build on pageload --- app/assets/javascripts/build.js | 18 ++++++++++++++++-- app/views/projects/builds/show.html.haml | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index baa91e221e8..2725ddcf465 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -6,7 +6,7 @@ Build.state = null; - function Build(page_url, build_url, build_status, state1) { + function Build(page_url, build_url, build_status, build_stage, build_name, state1) { this.page_url = page_url; this.build_url = build_url; this.build_status = build_status; @@ -17,7 +17,12 @@ clearInterval(Build.interval); this.bp = Breakpoints.get(); $('.js-build-sidebar').niceScroll(); + + this.populateJobDropdown(build_stage); + this.updateStageDropdownText(build_stage); + this.updateJobDropdownText(build_name); this.hideSidebar(); + $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); $(document).on('click', '.stage-item', this.updateDropdown); @@ -139,10 +144,19 @@ $('.build-job[data-stage="' + stage + '"]').show(); }; + Build.prototype.updateStageDropdownText = function(stage) { + $('.stage-selection').text(stage); + this.updateJobDropdownText('-'); + }; + + Build.prototype.updateJobDropdownText = function(name) { + $('.build-selection').text(name); + }; + Build.prototype.updateDropdown = function(e) { e.preventDefault(); var stage = e.target.text; - $('.stage-selection').text(stage); + this.updateStageDropdownText(stage); this.populateJobDropdown(stage); }; diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 2302aa2d1e7..5f7c66e4dd2 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -82,4 +82,4 @@ = render "sidebar" :javascript - new Build("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}") + new Build("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{@build.stage}", "#{@build.name}", "#{trace_with_state[:state]}") From 3693495e6136a9dc1266fc25a1b6ff9a764de005 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 4 Aug 2016 15:33:41 -0500 Subject: [PATCH 50/64] Style build dropdowns --- app/assets/stylesheets/pages/builds.scss | 29 +++++++----------------- app/views/projects/builds/show.html.haml | 8 +++---- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 5a6a35f42d1..5ecbe038612 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -54,32 +54,19 @@ } } - .nav-links { - svg { - position: relative; - top: 2px; - margin-right: 3px; - } - - .build-job { - a { - color: $gl-text-color; - } - - &.active { - a { - font-weight: bold; - } - } - } - } - - .jobs-dropdown { + .build-dropdown { + display: inline-block; + margin-top: 16px; svg { position: relative; top: 2px; margin-right: 3px; + height: 13px; + } + + .stage-item { + cursor: pointer; } } } diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 5f7c66e4dd2..b4834ae42a2 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -8,20 +8,20 @@ - builds = @build.pipeline.builds.latest.to_a - statuses = ["failed", "pending", "running", "canceled", "skipped", "success"] - if builds.size > 1 - .dropdown.stage + .dropdown.build-dropdown %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.stage-selection More = icon('caret-down') - %ul.dropdown-menu.overflow-jobs + %ul.dropdown-menu - builds.map(&:stage).uniq.each do |stage| %li %a.stage-item= stage - .dropdown.jobs-dropdown + .dropdown.build-dropdown %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.build-selection More = icon('caret-down') - %ul.dropdown-menu.overflow-jobs + %ul.dropdown-menu - statuses.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| %li.build-job{class: ('active' if build == @build), data: {id: build.id, name: build.name, stage: build.stage}} From 6357c75c5784479a71a5b50204aa8633a3650756 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 4 Aug 2016 15:39:02 -0500 Subject: [PATCH 51/64] Update changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 837e9e27aba..2c00255f6d2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -99,6 +99,7 @@ v 8.11.0 (unreleased) - Make error pages responsive (Takuya Noguchi) - The performance of the project dropdown used for moving issues has been improved - Fix skip_repo parameter being ignored when destroying a namespace + - Add all builds into stage/job dropdowns on builds page - Change requests_profiles resource constraint to catch virtually any file - Bump gitlab_git to lazy load compare commits - Reduce number of queries made for merge_requests/:id/diffs From 1b98f15444449b075ea52346d6368c7e2de8f2b6 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 4 Aug 2016 15:54:07 -0500 Subject: [PATCH 52/64] Remove unused data attributes --- app/views/projects/builds/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index b4834ae42a2..956851c651f 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -24,7 +24,7 @@ %ul.dropdown-menu - statuses.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| - %li.build-job{class: ('active' if build == @build), data: {id: build.id, name: build.name, stage: build.stage}} + %li.build-job{class: ('active' if build == @build), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do = ci_icon_for_status(build.status) %span From f80aa9026f6ce7ebfb88c437163868a596b492c4 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 9 Aug 2016 10:20:21 -0500 Subject: [PATCH 53/64] Move skipped tests to end of array --- app/views/projects/builds/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 956851c651f..dba0a0a9f4e 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -6,7 +6,7 @@ = render "header" - builds = @build.pipeline.builds.latest.to_a - - statuses = ["failed", "pending", "running", "canceled", "skipped", "success"] + - statuses = ["failed", "pending", "running", "canceled", "success", "skipped"] - if builds.size > 1 .dropdown.build-dropdown %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} From 8dbb1b2fbeff6caf1ad91173d707b5f55469cb40 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 17 Aug 2016 11:46:41 -0500 Subject: [PATCH 54/64] Move stages and jobs to build sidebar --- app/assets/javascripts/build.js | 6 ++-- app/views/projects/builds/_sidebar.html.haml | 36 ++++++++++++++++++++ app/views/projects/builds/show.html.haml | 35 ------------------- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 2725ddcf465..1ee4ee5f10e 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -18,7 +18,7 @@ this.bp = Breakpoints.get(); $('.js-build-sidebar').niceScroll(); - this.populateJobDropdown(build_stage); + this.populateJobs(build_stage); this.updateStageDropdownText(build_stage); this.updateJobDropdownText(build_name); this.hideSidebar(); @@ -139,7 +139,7 @@ } }; - Build.prototype.populateJobDropdown = function(stage) { + Build.prototype.populateJobs = function(stage) { $('.build-job').hide(); $('.build-job[data-stage="' + stage + '"]').show(); }; @@ -157,7 +157,7 @@ e.preventDefault(); var stage = e.target.text; this.updateStageDropdownText(stage); - this.populateJobDropdown(stage); + this.populateJobs(stage); }; return Build; diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index a8bc53c2849..47d0c93b0ba 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -11,6 +11,42 @@ %p.build-detail-row #{@build.coverage}% + - builds = @build.pipeline.builds.latest.to_a + - statuses = ["failed", "pending", "running", "canceled", "success", "skipped"] + - if builds.size > 1 + .dropdown.build-dropdown + %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %span.stage-selection More + = icon('caret-down') + %ul.dropdown-menu + - builds.map(&:stage).uniq.each do |stage| + %li + %a.stage-item= stage + + .dropdown.build-dropdown + %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %span.build-selection More + = icon('caret-down') + %ul.dropdown-menu + - statuses.each do |build_status| + - builds.select{|build| build.status == build_status}.each do |build| + %li.build-job{class: ('active' if build == @build), data: {stage: build.stage}} + = link_to namespace_project_build_path(@project.namespace, @project, build) do + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id + + - if @build.retried? + %li.active + %a + Build ##{@build.id} + · + %i.fa.fa-warning + This build was retried. + - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) .block{ class: ("block-first" if !@build.coverage) } .title diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index dba0a0a9f4e..70e02cb2d13 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -5,41 +5,6 @@ .build-page = render "header" - - builds = @build.pipeline.builds.latest.to_a - - statuses = ["failed", "pending", "running", "canceled", "success", "skipped"] - - if builds.size > 1 - .dropdown.build-dropdown - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} - %span.stage-selection More - = icon('caret-down') - %ul.dropdown-menu - - builds.map(&:stage).uniq.each do |stage| - %li - %a.stage-item= stage - - .dropdown.build-dropdown - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} - %span.build-selection More - = icon('caret-down') - %ul.dropdown-menu - - statuses.each do |build_status| - - builds.select{|build| build.status == build_status}.each do |build| - %li.build-job{class: ('active' if build == @build), data: {stage: build.stage}} - = link_to namespace_project_build_path(@project.namespace, @project, build) do - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id - - - if @build.retried? - %li.active - %a - Build ##{@build.id} - · - %i.fa.fa-warning - This build was retried. - if @build.stuck? - unless @build.any_runners_online? .bs-callout.bs-callout-warning From 162ef4b07262fdc0ad75564225f311a67c11833b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 17 Aug 2016 12:15:36 -0500 Subject: [PATCH 55/64] Display jobs as scrolling list in sidebar --- app/assets/javascripts/build.js | 6 - app/assets/stylesheets/pages/builds.scss | 68 ++++-- app/views/projects/builds/_sidebar.html.haml | 207 +++++++++---------- 3 files changed, 151 insertions(+), 130 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 1ee4ee5f10e..c54d8fc2267 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -20,7 +20,6 @@ this.populateJobs(build_stage); this.updateStageDropdownText(build_stage); - this.updateJobDropdownText(build_name); this.hideSidebar(); $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); @@ -146,11 +145,6 @@ Build.prototype.updateStageDropdownText = function(stage) { $('.stage-selection').text(stage); - this.updateJobDropdownText('-'); - }; - - Build.prototype.updateJobDropdownText = function(name) { - $('.build-selection').text(name); }; Build.prototype.updateDropdown = function(e) { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 5ecbe038612..f22b5fb84f8 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -53,22 +53,6 @@ left: 70px; } } - - .build-dropdown { - display: inline-block; - margin-top: 16px; - - svg { - position: relative; - top: 2px; - margin-right: 3px; - height: 13px; - } - - .stage-item { - cursor: pointer; - } - } } .build-header { @@ -116,24 +100,70 @@ } .right-sidebar.build-sidebar { - padding-top: $gl-padding; - padding-bottom: $gl-padding; + padding: $gl-padding 0; + border-left: 1px solid $border-color; &.right-sidebar-collapsed { display: none; } + .blocks-container { + padding: $gl-padding; + } + .block { width: 100%; } .build-sidebar-header { - padding-top: 0; + padding: 0 $gl-padding $gl-padding; .gutter-toggle { margin-top: 0; } } + + .stage-item { + cursor: pointer; + + &:hover { + color: $gl-text-color; + } + } + + .build-dropdown { + padding: 0 $gl-padding; + } + + .builds-container { + margin: $gl-padding 0; + background-color: $white-light; + border-top: 1px solid $border-color; + border-bottom: 1px solid $border-color; + + svg { + position: relative; + top: 2px; + margin-right: 3px; + height: 13px; + } + + a { + display: block; + padding: $gl-padding 10px; + + &:hover { + background-color: $row-hover; + color: $gl-text-color; + } + } + + .build-job { + &.active { + font-weight: bold; + } + } + } } .build-detail-row { diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 47d0c93b0ba..726c816716c 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -23,122 +23,119 @@ %li %a.stage-item= stage - .dropdown.build-dropdown - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} - %span.build-selection More - = icon('caret-down') - %ul.dropdown-menu - - statuses.each do |build_status| - - builds.select{|build| build.status == build_status}.each do |build| - %li.build-job{class: ('active' if build == @build), data: {stage: build.stage}} - = link_to namespace_project_build_path(@project.namespace, @project, build) do - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id + .builds-container + - statuses.each do |build_status| + - builds.select{|build| build.status == build_status}.each do |build| + .build-job{class: ('active' if build == @build), data: {stage: build.stage}} + = link_to namespace_project_build_path(@project.namespace, @project, build) do + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id - - if @build.retried? - %li.active - %a - Build ##{@build.id} - · - %i.fa.fa-warning - This build was retried. + - if @build.retried? + %li.active + %a + Build ##{@build.id} + · + %i.fa.fa-warning + This build was retried. - - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) - .block{ class: ("block-first" if !@build.coverage) } + .blocks-container + - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) + .block{ class: ("block-first" if !@build.coverage) } + .title + Build artifacts + - if @build.artifacts_expired? + %p.build-detail-row + The artifacts were removed + #{time_ago_with_tooltip(@build.artifacts_expire_at)} + - elsif @build.artifacts_expire_at + %p.build-detail-row + The artifacts will be removed in + %span.js-artifacts-remove= @build.artifacts_expire_at + + - if @build.artifacts? + .btn-group.btn-group-justified{ role: :group } + - if @build.artifacts_expire_at + = link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do + Keep + + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do + Download + + - if @build.artifacts_metadata? + = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do + Browse + + .block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) } .title - Build artifacts - - if @build.artifacts_expired? + Build details + - if can?(current_user, :update_build, @build) && @build.retryable? + = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post + - if @build.merge_request %p.build-detail-row - The artifacts were removed - #{time_ago_with_tooltip(@build.artifacts_expire_at)} - - elsif @build.artifacts_expire_at + %span.build-light-text Merge Request: + = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request) + - if @build.duration %p.build-detail-row - The artifacts will be removed in - %span.js-artifacts-remove= @build.artifacts_expire_at - - - if @build.artifacts? - .btn-group.btn-group-justified{ role: :group } - - if @build.artifacts_expire_at - = link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do - Keep - - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do - Download - - - if @build.artifacts_metadata? - = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do - Browse - - .block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) } - .title - Build details - - if can?(current_user, :update_build, @build) && @build.retryable? - = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post - - if @build.merge_request + %span.build-light-text Duration: + = time_interval_in_words(@build.duration) + - if @build.finished_at + %p.build-detail-row + %span.build-light-text Finished: + #{time_ago_with_tooltip(@build.finished_at)} + - if @build.erased_at + %p.build-detail-row + %span.build-light-text Erased: + #{time_ago_with_tooltip(@build.erased_at)} %p.build-detail-row - %span.build-light-text Merge Request: - = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request) - - if @build.duration - %p.build-detail-row - %span.build-light-text Duration: - = time_interval_in_words(@build.duration) - - if @build.finished_at - %p.build-detail-row - %span.build-light-text Finished: - #{time_ago_with_tooltip(@build.finished_at)} - - if @build.erased_at - %p.build-detail-row - %span.build-light-text Erased: - #{time_ago_with_tooltip(@build.erased_at)} - %p.build-detail-row - %span.build-light-text Runner: - - if @build.runner && current_user && current_user.admin - = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id) - - elsif @build.runner - \##{@build.runner.id} - .btn-group.btn-group-justified{ role: :group } - - if @build.has_trace? - = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' - - if @build.active? - = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post - - if can?(current_user, :update_build, @project) && @build.erasable? - = link_to erase_namespace_project_build_path(@project.namespace, @project, @build), - class: "btn btn-sm btn-default", method: :post, - data: { confirm: "Are you sure you want to erase this build?" } do - Erase + %span.build-light-text Runner: + - if @build.runner && current_user && current_user.admin + = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id) + - elsif @build.runner + \##{@build.runner.id} + .btn-group.btn-group-justified{ role: :group } + - if @build.has_trace? + = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' + - if @build.active? + = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post + - if can?(current_user, :update_build, @project) && @build.erasable? + = link_to erase_namespace_project_build_path(@project.namespace, @project, @build), + class: "btn btn-sm btn-default", method: :post, + data: { confirm: "Are you sure you want to erase this build?" } do + Erase - - if @build.trigger_request - .build-widget - %h4.title - Trigger + - if @build.trigger_request + .build-widget + %h4.title + Trigger - %p - %span.build-light-text Token: - #{@build.trigger_request.trigger.short_token} - - - if @build.trigger_request.variables %p - %span.build-light-text Variables: + %span.build-light-text Token: + #{@build.trigger_request.trigger.short_token} + + - if @build.trigger_request.variables + %p + %span.build-light-text Variables: - - @build.trigger_request.variables.each do |key, value| - %code - #{key}=#{value} + - @build.trigger_request.variables.each do |key, value| + %code + #{key}=#{value} - .block - .title - Commit title - %p.build-light-text.append-bottom-0 - #{@build.pipeline.git_commit_title} - - - if @build.tags.any? .block .title - Tags - - @build.tag_list.each do |tag| - %span.label.label-primary - = tag + Commit title + %p.build-light-text.append-bottom-0 + #{@build.pipeline.git_commit_title} + + - if @build.tags.any? + .block + .title + Tags + - @build.tag_list.each do |tag| + %span.label.label-primary + = tag From b6c670cf9c1e86748aa0a97decdd5ed9014f4f97 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 17 Aug 2016 13:08:44 -0500 Subject: [PATCH 56/64] Style build container box; add check mark to active build --- app/assets/stylesheets/framework/sidebar.scss | 4 +++ app/assets/stylesheets/pages/builds.scss | 28 +++++++++++++++++-- app/views/projects/builds/_sidebar.html.haml | 2 ++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 3fa4a22258d..015fe3debf9 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -222,3 +222,7 @@ header.header-pinned-nav { padding-right: $sidebar_collapsed_width; } } + +.right-sidebar { + border-left: 1px solid $border-color; +} diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index f22b5fb84f8..d910ec0ccc9 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -101,7 +101,6 @@ .right-sidebar.build-sidebar { padding: $gl-padding 0; - border-left: 1px solid $border-color; &.right-sidebar-collapsed { display: none; @@ -133,13 +132,19 @@ .build-dropdown { padding: 0 $gl-padding; + + .dropdown-menu-toggle { + margin-top: 8px; + } } .builds-container { - margin: $gl-padding 0; + margin-top: $gl-padding; background-color: $white-light; border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; + max-height: 300px; + overflow: scroll; svg { position: relative; @@ -150,7 +155,11 @@ a { display: block; - padding: $gl-padding 10px; + padding: $gl-padding 10px $gl-padding 40px; + width: 270px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; &:hover { background-color: $row-hover; @@ -159,8 +168,21 @@ } .build-job { + position: relative; + + .fa { + position: absolute; + left: 15px; + top: 20px; + display: none; + } + &.active { font-weight: bold; + + .fa { + display: block; + } } } } diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 726c816716c..5b0b58e087b 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -15,6 +15,7 @@ - statuses = ["failed", "pending", "running", "canceled", "success", "skipped"] - if builds.size > 1 .dropdown.build-dropdown + .build-light-text Stage %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.stage-selection More = icon('caret-down') @@ -28,6 +29,7 @@ - builds.select{|build| build.status == build_status}.each do |build| .build-job{class: ('active' if build == @build), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do + = icon('check') = ci_icon_for_status(build.status) %span - if build.name From cc7b3db5a2d9a04d976c3db95059e3163a01ec70 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 17 Aug 2016 13:43:28 -0500 Subject: [PATCH 57/64] Remove params from build; general refactor --- app/assets/javascripts/build.js | 19 ++++++++++--------- app/assets/stylesheets/pages/builds.scss | 6 ++++++ app/views/projects/builds/show.html.haml | 8 +++++++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index c54d8fc2267..0d7d29bb0d0 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -6,11 +6,12 @@ Build.state = null; - function Build(page_url, build_url, build_status, build_stage, build_name, state1) { - this.page_url = page_url; - this.build_url = build_url; - this.build_status = build_status; - this.state = state1; + function Build(options) { + this.page_url = options.page_url; + this.build_url = options.build_url; + this.build_status = options.build_status; + this.state = options.state1; + this.build_stage = options.build_stage; this.hideSidebar = bind(this.hideSidebar, this); this.toggleSidebar = bind(this.toggleSidebar, this); this.updateDropdown = bind(this.updateDropdown, this); @@ -18,13 +19,13 @@ this.bp = Breakpoints.get(); $('.js-build-sidebar').niceScroll(); - this.populateJobs(build_stage); - this.updateStageDropdownText(build_stage); + this.populateJobs(this.build_stage); + this.updateStageDropdownText(this.build_stage); this.hideSidebar(); $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); - $(document).on('click', '.stage-item', this.updateDropdown); + $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); this.updateArtifactRemoveDate(); if ($('#build-trace').length) { this.getInitialBuildTrace(); @@ -149,7 +150,7 @@ Build.prototype.updateDropdown = function(e) { e.preventDefault(); - var stage = e.target.text; + var stage = e.currentTarget.text; this.updateStageDropdownText(stage); this.populateJobs(stage); }; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index d910ec0ccc9..81fce55853c 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -136,6 +136,12 @@ .dropdown-menu-toggle { margin-top: 8px; } + + .dropdown-menu { + right: $gl-padding; + left: $gl-padding; + width: auto; + } } .builds-container { diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 70e02cb2d13..e4d41288aa6 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -47,4 +47,10 @@ = render "sidebar" :javascript - new Build("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{@build.stage}", "#{@build.name}", "#{trace_with_state[:state]}") + new Build({ + page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}", + build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", + build_status: "#{@build.status}", + build_stage: "#{@build.stage}", + state1: "#{trace_with_state[:state]}" + }) From 5f6c6d6a0d8feee9b17d309e4da180dce7ca8ce0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 17 Aug 2016 16:04:08 -0300 Subject: [PATCH 58/64] Hide `Create new list button` on Issues and MRs pages --- app/views/shared/issuable/_filter.html.haml | 6 +++--- spec/features/projects/issues/list_spec.rb | 20 +++++++++++++++++++ .../projects/merge_requests/list_spec.rb | 20 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 spec/features/projects/issues/list_spec.rb create mode 100644 spec/features/projects/merge_requests/list_spec.rb diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index ffe8d4fbdbf..4f8ea7e7cef 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -27,9 +27,7 @@ = render "shared/issuable/label_dropdown" .pull-right - - if controller.controller_name != 'boards' - = render 'shared/sort_dropdown' - - if can?(current_user, :admin_list, @project) + - if controller.controller_name == 'boards' && can?(current_user, :admin_list, @project) .dropdown %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } } Create new list @@ -38,6 +36,8 @@ - if can?(current_user, :admin_label, @project) = render partial: "shared/issuable/label_page_create" = dropdown_loading + - else + = render 'shared/sort_dropdown' - if controller.controller_name == 'issues' .issues_bulk_update.hide diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb new file mode 100644 index 00000000000..3137af074ca --- /dev/null +++ b/spec/features/projects/issues/list_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +feature 'Issues List' do + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + + background do + project.team << [user, :developer] + + login_as(user) + end + + scenario 'user does not see create new list button' do + create(:issue, project: project) + + visit namespace_project_issues_path(project.namespace, project) + + expect(page).not_to have_selector('.js-new-board-list') + end +end diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb new file mode 100644 index 00000000000..5dd58ad66a7 --- /dev/null +++ b/spec/features/projects/merge_requests/list_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +feature 'Merge Requests List' do + let(:user) { create(:user) } + let(:project) { create(:project) } + + background do + project.team << [user, :developer] + + login_as(user) + end + + scenario 'user does not see create new list button' do + create(:merge_request, source_project: project) + + visit namespace_project_merge_requests_path(project.namespace, project) + + expect(page).not_to have_selector('.js-new-board-list') + end +end From 74f80465f6078b4a78ae2ebe4e367d4f1d7c890a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 17 Aug 2016 16:41:24 -0500 Subject: [PATCH 59/64] Remove index from pipeline toggles --- app/assets/javascripts/pipeline.js.es6 | 20 ++++++++++--------- app/views/projects/commit/_pipeline.html.haml | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index ae7369004de..bf33eb10100 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,13 +1,15 @@ -function toggleGraph() { - const indexOfBtn = $('.toggle-pipeline-btn').index($(this)); +(function() { + function toggleGraph() { + const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); + const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); + const $btnText = $(this).find('.toggle-btn-text'); - $($('.pipeline-graph')[indexOfBtn]).toggleClass('graph-collapsed'); - $($('.toggle-pipeline-btn')[indexOfBtn]).toggleClass('graph-collapsed'); + $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - const $btnText = $($('.toggle-pipeline-btn .btn-text')[indexOfBtn]); - const graphCollapsed = $($('.pipeline-graph')[indexOfBtn]).hasClass('graph-collapsed'); + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') -} + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + } -$(document).on('click', '.toggle-pipeline-btn', toggleGraph); + $(document).on('click', '.toggle-pipeline-btn', toggleGraph); +})(); diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 9fa54057823..20a85148ab5 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,7 +1,7 @@ .row-content-block.build-content.middle-block.pipeline-actions .pull-right .btn.btn-grouped.btn-white.toggle-pipeline-btn - %span.btn-text Hide + %span.toggle-btn-text Hide %span pipeline graph %span.caret - if can?(current_user, :update_pipeline, pipeline.project) From 43252ccbee64333634f13dd59509a1d19eef5c6e Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 17 Aug 2016 22:51:49 -0500 Subject: [PATCH 60/64] Fix tabs navigation and do not update URL via pushstate --- app/assets/javascripts/merge_request_tabs.js | 5 ++++- app/controllers/projects/merge_requests_controller.rb | 8 +++++++- .../projects/merge_requests/_new_submit.html.haml | 11 ++++------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 1bba69a255a..4e2273f5aa8 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -15,6 +15,7 @@ function MergeRequestTabs(opts) { this.opts = opts != null ? opts : {}; + this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true; this.setCurrentAction = bind(this.setCurrentAction, this); this.tabShown = bind(this.tabShown, this); this.showTab = bind(this.showTab, this); @@ -58,7 +59,9 @@ } else { this.expandView(); } - return this.setCurrentAction(action); + if (this.opts.setUrl) { + this.setCurrentAction(action); + } }; MergeRequestTabs.prototype.scrollToElement = function(container) { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 00a3022cbf7..696f7192131 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -216,7 +216,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @base_commit = @merge_request.diff_base_commit @diffs = @merge_request.diffs(diff_options) if @merge_request.compare @diff_notes_disabled = true - + @show_diff_tab = check_diff_tab @pipeline = @merge_request.pipeline @statuses = @pipeline.statuses.relevant if @pipeline @@ -520,4 +520,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute end + + private + + def check_diff_tab + request.query_parameters[:view].present? + end end diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 598bd743676..0b05a2693a6 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -20,7 +20,7 @@ .mr-compare.merge-request %ul.merge-request-tabs.nav-links.no-top.no-bottom %li.commits-tab - = link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do + = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do Commits %span.badge= @commits.size - if @pipeline @@ -52,11 +52,8 @@ $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); - :javascript - var merge_request - merge_request = new MergeRequest({ - action: 'new', - diffs_loaded: true, - commits_loaded: true + var merge_request = new MergeRequest({ + action: "#{(@show_diff_tab ? 'diffs' : 'new')}", + setUrl: false }); From bac19f4c5c62218f7c59894957548a25b03f69d5 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 17 Aug 2016 22:55:24 -0500 Subject: [PATCH 61/64] Fix failing test Test was not waiting for the page to be fully loaded --- spec/features/merge_requests/create_new_mr_spec.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index b63931d9d35..775b38814eb 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Create New Merge Request', feature: true, js: true do + include WaitForAjax + let(:user) { create(:user) } let(:project) { create(:project, :public) } @@ -53,10 +55,11 @@ feature 'Create New Merge Request', feature: true, js: true do expect(page.find_link('Side-by-side')[:class]).not_to match(/\bactive\b/) click_link 'Side-by-side' + wait_for_ajax - click_link 'Changes' - - expect(page.find_link('Inline')[:class]).not_to match(/\bactive\b/) - expect(page.find_link('Side-by-side')[:class]).to match(/\bactive\b/) + within '.merge-request' do + expect(page).not_to have_css('a.btn.active', text: 'Inline') + expect(page).to have_css('a.btn.active', text: 'Side-by-side') + end end end From 163c4f5658d0b58dd5925936b3b5498adcf60fb7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 17 Aug 2016 13:27:14 +0300 Subject: [PATCH 62/64] Refactor description templates documentation --- app/views/shared/issuable/_form.html.haml | 2 +- doc/user/project/description_templates.md | 41 ++++++++++++++++++ .../project}/img/description_templates.png | Bin doc/workflow/README.md | 2 +- doc/workflow/description_templates.md | 12 ----- 5 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 doc/user/project/description_templates.md rename doc/{workflow => user/project}/img/description_templates.png (100%) delete mode 100644 doc/workflow/description_templates.md diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 210b43c7e0b..9e2e096d5f9 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -42,7 +42,7 @@ - if can_add_template?(issuable) %p.help-block Add - = link_to "issuable templates", help_page_path('workflow/description_templates') + = link_to "description templates", help_page_path('user/project/description_templates') to help your contributors communicate effectively! .form-group.detail-page-description diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md new file mode 100644 index 00000000000..5f1cd126425 --- /dev/null +++ b/doc/user/project/description_templates.md @@ -0,0 +1,41 @@ +# Description templates + +>**Note:** [Introduced][ce-4981] in GitLab 8.11. + +Description templates allow you to define context-specific templates for issue +and merge request description fields for your project. + +## Overview + +By using the description templates, users that create a new issue or merge +request can select a description template to help them communicate with other +contributors effectively. + +Every GitLab project can define its own set of description templates as they +are added to the root directory of a GitLab project's repository. + +Description templates must be written in [Markdown](../markdown.md) and stored +in your project's repository under a directory named `.gitlab`. Only the +templates of the default branch will be taken into account. + +## Creating issue templates + +Create a new Markdown (`.md`) file inside the `.gitlab/issue_templates/` +directory in your repository. Commit and push to your default branch. + +## Creating merge request templates + +Similarly to issue templates, create a new Markdown (`.md`) file inside the +`.gitlab/merge_request_templates/` directory in your repository. Commit and +push to your default branch. + +## Using the templates + +Let's take for example that you have created the file `.gitlab/issue_templates/bug.md`. +This will enable the `bug` dropdown option when creating or editing issues. When +`bug` is selected, the content from the `bug.md` template file will be copied +to the issue description field. + +![Description templates](img/description_templates.png) + +[ce-4981]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4981 diff --git a/doc/workflow/img/description_templates.png b/doc/user/project/img/description_templates.png similarity index 100% rename from doc/workflow/img/description_templates.png rename to doc/user/project/img/description_templates.png diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 993349e5b46..3055411c484 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -2,6 +2,7 @@ - [Authorization for merge requests](authorization_for_merge_requests.md) - [Change your time zone](timezone.md) +- [Description templates](../user/project/description_templates.md) - [Feature branch workflow](workflow.md) - [GitLab Flow](gitlab_flow.md) - [Groups](groups.md) @@ -17,7 +18,6 @@ - [Share projects with other groups](share_projects_with_other_groups.md) - [Web Editor](web_editor.md) - [Releases](releases.md) -- [Issuable Templates](issuable_templates.md) - [Milestones](milestones.md) - [Merge Requests](merge_requests.md) - [Revert changes](revert_changes.md) diff --git a/doc/workflow/description_templates.md b/doc/workflow/description_templates.md deleted file mode 100644 index 9514564af02..00000000000 --- a/doc/workflow/description_templates.md +++ /dev/null @@ -1,12 +0,0 @@ -# Description templates - -Description templates allow you to define context-specific templates for issue and merge request description fields for your project. When in use, users that create a new issue or merge request can select a description template to help them communicate with other contributors effectively. - -Every GitLab project can define its own set of description templates as they are added to the root directory of a GitLab project's repository. - -Description templates are written in markdown _(`.md`)_ and stored in your projects repository under the `/.gitlab/issue_templates/` and `/.gitlab/merge_request_templates/` directories. - -![Description templates](img/description_templates.png) - -_Example:_ -`/.gitlab/issue_templates/bug.md` will enable the `bug` dropdown option for new issues. When `bug` is selected, the content from the `bug.md` template file will be copied to the issue description field. From a6985c77e03daea9af0077eb219cdb58724e29d2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 17 Aug 2016 15:27:18 +0300 Subject: [PATCH 63/64] Add new image to show the 'Reset template' button --- doc/user/project/description_templates.md | 11 ++++++----- .../project/img/description_templates.png | Bin 57670 -> 20444 bytes 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md index 5f1cd126425..ea7496af089 100644 --- a/doc/user/project/description_templates.md +++ b/doc/user/project/description_templates.md @@ -1,6 +1,6 @@ # Description templates ->**Note:** [Introduced][ce-4981] in GitLab 8.11. +>[Introduced][ce-4981] in GitLab 8.11. Description templates allow you to define context-specific templates for issue and merge request description fields for your project. @@ -31,10 +31,11 @@ push to your default branch. ## Using the templates -Let's take for example that you have created the file `.gitlab/issue_templates/bug.md`. -This will enable the `bug` dropdown option when creating or editing issues. When -`bug` is selected, the content from the `bug.md` template file will be copied -to the issue description field. +Let's take for example that you've created the file `.gitlab/issue_templates/Bug.md`. +This will enable the `Bug` dropdown option when creating or editing issues. When +`Bug` is selected, the content from the `Bug.md` template file will be copied +to the issue description field. The 'Reset template' button will discard any +changes you made after picking the template and return it to its initial status. ![Description templates](img/description_templates.png) diff --git a/doc/user/project/img/description_templates.png b/doc/user/project/img/description_templates.png index af2e9403826121a061c882ff509c8ba5653673b7..c41cc77a94c0e888f97e376603f45e6c3feb24ad 100644 GIT binary patch literal 20444 zcmd431z42rwm|D5ak<^rynXP#%>xz_sq)>=2=n(E4gc(ix`01&FGC_V)MNGteT zfP)2Ip`Bfs2Y}mvs^SA}ue8mXGd*(8xt*IK#F*@Y?@k>emD)@sjzr`&xwD>Pr%f!2 zK~clQPRy>)wL132IXNsi^W=8$k&PJR;O9x=Q2}?|oRw8Zzr_PY4vbs8_qQZ4-g-=E%IyRQ{^y*qC6`h+Ki|;`b^orJ@#)w3Dm6^UBFWoGr;;T|DS@{J_8~<-{roo0n;PREtLWtu*|8VN8(j9YOYTvopaou z*TmwC8v?S7aEwGXN_V^I8^+el45_N$cPB5zpf?X|lNo}CW`n1m$(oOJyCwe;&8l4x z^>rv+s7inu~i_XH2XMNM6hKrzd|7tA) zLPAq3E1aQsqAd1(@x#lcg;cHm978I74?a;ghUshMVDioJFUon)iZDpTh!z|FJlZ0M z%DxP;=eWV0VUP8g$}J9!t;%&Ct8N+Yaj8Pm@qW71x+OX&m-G}TG++6DAAsL~RET$5UV+=G`62*0GG8U%0KmX7C*o zT5OhRc9a7qNkgqbp);QK&d%|;*zL!ZBj}ts4pOyF7<5m1hx6>@exLd7;-F2H+J)ZT zIT_xvo5{<1TCh8~P54sJ?pO8?yUg;fwTryt7Mrp=T$Bds5J@8Xo(70s|Hh-oy<&&X zLT&-RH+QY#U6E-OqkJ$eH0tXmZ@pw1=j6kStvuPbg)Q9jEQa7YW$0JZmdfbv#^*1g z>}nbA3-iIO-_=B!1wA>o{Ua_PLcI#e&gg!b{`ju6Rv;01igsW$dv-#(Ib8`+dM1JQ zNMrsg%XS?q++y;~;@&R-S1srH2ctb6ITs92NgtBmAFcU5TP~^`A+!Rb1Kzo2Pi!c# z=Izd;!3+9^ItvDQYu)T*zx{gh{T^rc#sdx+*}90AsV?tRKE4w?kx`b?RZhuRbbC(f z;wlhZRYy{T&}aD&ip@7ZO(lr%dVMt&84`lkcz!}BW%}$$-}mQfBZP2>jx~dPM3oyR zX(MT3+k%$vjNQ#)jHym+GE%_(ou~ML?+vG2$IIF>4)ljDo*n#7n6xh~R-MmXz5sMJ zc?)>~p%6d@NtHiVpC6-ldfmHo9`dS+_C)r$!0&-?+dpaXg*g4~Q*60h`%;m}9=+TF{1wuDEP+`8_0zRxbLw7+z% zubbenZ_&XYTBIA$Y0i{)hma13H36-T7vEx^xwsnjU2S`Oq9yP;?L|X&;>=!oQ|_<_ z(4-D+DMHUE-SoRn=+=}5n(66l;ezJSOxr%^<+ex3*vi8fYc$`UM%Mn4dB#esjykABqe|7XWH&49g<6OKI<_rKHAIBIY+uEuj3(&?-C@_me1kQ zZMEh1)~h>CG#KHw`D3(|(GQ#JGq!wgdjRHtn_wIuQ)8(|3WGLABP@86b9%gIc;jgG zp#PfJ@pkP9PmwM0OAjIhtjz?*di(a=+JmocFvJBz_OeV5HZ>vErM3G|-L&9OjKH?A znDOt%-LRg>%7n=8Jsxuc0wQDmXRgvS)uauj9*QrIm;0;36e3*zn5-`1ybN;In-A>? z<(@Xd@_;oKdI^EJLQN(#?7&UkXSKeL=#6^jg*=jOsGW84bSTujhxok%z~SQ{U&z}U z#tW;Ui_8Wjuz2K($C)`eENxyn%2_zV3u zXvA|WV~+})5$b)+IqIS}ZSTpP_3;5U(N8&th*d0gbLmP3j;|fD_0M}fNj8kX}Q(V>3w_Fj;4=U zB|_NzJlfE6d`oeq1X*^X$6Ud8dJ_<%6{;Ib++r`Zpk2egewCA+YqDYgaOO;4wMt35 zi8j*v>P<2vQrhyaDUiel+dB!;pY(;vETu%AE_X=HqDSJ)^_c)o5HYN`Z&I(R>1W?Z zWX=)})m=)ap0PT-6pN@#a2su;d22q$LU5N3c{C{_T}4NO`Pu98D<-Q4?=|;e*SIl~ zsyBRi^YEEk+X>E~C=^zSzFIaNI9N+A>hkzmsN742;jd%) zTN&Q@j6j;gL~NQ5z)&Trd1(d!yEZ#m3023e4+|f*aGV?G$GFECDPK8!dSx8$eHH5_ z9brrq^a0%HPZ-eB%?F>h1B0wE<3Wns+&4SV9m!F9dYf~Wkz3B9t&WC6D|ujWIG_hF z3o(Nk-v*QXP^OSJsKE!?0^!;Pe?T^Xveti9Tx%Oh$6^4w6 z{oqP~K)jBilmoon&FBR1qURB%(q(SY_TVe=@3SxH>ivtB7ZK2&whGT-Nt@6i>1b@q z)bh1FJpk5C4uAi_pTx2G*wm~&A|}dnw@h}woYK!+MU>X5hKoAr4RL^(vd@nq2)&gQ z#xoygiI8rSHTV9y^ezlO*S;aI-&1ei%sSTU)DafYK|4E_$X6WNur^EVAIK#mrl}+l zig%8H`jDHqJ_bNA44?RG3=78qPVr#a)}5Z=N#P^n)bl6opaWL8ppDrQ*S<$7q61BH zhtFT|W#-aVaXgAmw1Mebb`A#S4X^DF_(%h_%7`2Ouq0SdKupm zDc>5boD`FLn~MQ-?Z@=zl%)-p(Bn0D?mN!!z4C3x>725jbWwU91GKF$M~ployTD$n z_GpC@(T?hQL51wd0#k<|r zBFxaL*u8`H@@4qpXUJaA-!Z6@J3p*1@UK7ppW{=50i=6Cfb8Y?A4{i>dwP2K1q9l0*hxUN>+a#P&=E=;dHW7VOi$qWxUQRrN68dD z#)H<=Q?c`l3saF)aWWGSPOFh%fM4a5my-@Ws;1O^7O@^qhE{W_#B5yIko6Vx+=JZQ72 zYBwt{I(PO!F_pCrv+`E(3i6=X00VrSlp88#9u>Bbh}h}*{{8z`rT37>B#1@i$;JGU zk7>C+wJb}7EhzogJ4~ZeQ8$$Y$R|wvfFq{D3$8HE>yHB#wKLmcdT+RbA&&tB zF`XaywM!kf97`NV#czV7K1v_T+PKgMU}GfE(~-r!)-^W)m?9glMFov|9yw!-WfyUv z01VgUd|o58qYWU7Wdli!7=5&kIKdBq9kvFkgl~bIP4(?>yO0?NOTgM0CHo%MdqqWb#2adu0#f!)srH+u`j%iOi4} z9S_3|?(em`!|Zt`FM9bK*HUn}H7Xg7G!G#8+V|Y*bx+_Sx6W(3`x+M$ZEMMgGNjs4 zW15oH-os{N9Cw#`1>J}mKKP~nDUYYLm=gZfO|x)pQfpsOoxZfyBA)2WrC@gYclBJN z?*5TNiJ{Mq`mhEqFaA;_u7dp zZcLP}uCB`I=~4Ok_zc704J^jr2LN^e%gKG_cRD0dEqGn;Sxg;bET*I^eG}D8)R@;z z7GA30lVO@)T3T>=10IYtFQ+|fmysy&il(76gnf0sA~^fWGm z8XvPpu6c_+yOA;T_5#eW2;QZi7?=^gn=uZ!3Qlq~Rx*ShIIo>>`C+Q$@WBducD6&5 zibL)qZTjKhdS>ZvWDiZhd1k3@T497=Wf15|ZUnR%E4ySrEzs0HqvO%hVR={>!oOiD z#$irkTQ{^v+G`{H%QL-|iFjwOf8L(6X#xUxxv&w_+S95V*1wSywJp=idoauEm^-+) zcOCV8(gQ%++BjDi0rwK|8cI9|YhNPz^29P<2dRB-AG;vlI~{64Ru#{gXLW_-s7=>- z;$!-7kJ3s%CH$S=>`Y9hR)OluxdrEf3@88|ZP*%*)NuaFcN!Dbw^cp7zYBDq)7+BW zsk*aIY5eN4o%kIO!yo%sFUGc(2z&%Hucfnb23$&sMaDzlx21RkF?cAEv#$d^7nt(L zY+h~GG1dD|G|la^-bD4+O$+Z!f^NWLv|7<_#5Mps${@-=KwX+@ipMTkH;KVrdBu}> z{=%B*?VQpV^}vM9Ra3{=SW;(BXBqgE^(eFW(QU(ctsDJ1T(E6R*%uIi5$+0Z{v~>c zD6i?mwVp4~FX1(s1=^GPhwPP7QZQF=$v&Q>uwv{s&+mktUd`+>_POE_psTR(gUcoE zCQaQcXly>~a31Z`c)$ntjL4t9%gdJrd>4`VLcVXtJO-qk?>!F})B2cCHFYXU!G?b~ z5G}04pX}n}%xDzOvp>in_3dKP`p@ZeO36rx)sYy*EAKC%uVmmUq^W2?k<03{`Tsn4=?Z8cAz>rGrOmVgF=IFtv(W}Z2By}~MX zV}|#)FMPpX?jE#sxbk4bOpzz#4j7}zJo)1B4#7Qkua>5hl}j3_#kP)^7lnC7z1ylE zrC~Sf%?o~(#Lc1NnfBZwu=B_t!>>1xUrpv>0@BI{&n{7GWR`0EPb;S{%IcNs}}PLXWYK5%nA71p%%K zVvlaNMrd(mjU`|E>vMD~Xrtr)RsBJ&j;Fm5+`tadxkrgdq`UC+i00KsrKar@FLQjT zeuLCYVtW@6#;VAHjX&pa!b!P*2;u=EI0{Fw_elCvVr1W=vbI++-ORW47cq0oOCKXz zr$K-!atYnGT(bQ9AoD}8|7**=u@%A@nQ<3{B1cIE=R=PaQrcq17ZE>xO3kG-A~3df z^ltM?V@jAycu+8O2d=!ymsQxTzm5^TA6Zn#*0!wrEx%2iM(Q1`JvcSRc z;@(hQ@xvCh$Fd>y#*f~cjxr0jkPx>UJ@=FqBZ^mlcey|A{fD9$bU40cRy76gYN6cG zw37uGS?ddfuxWu|FzxVTN*5t=bd;q_$fs`YOd^3?LVe1?ZQhm;J%fF3x6<8eTau^z zmm#@FnpOHmNMB=gVkhVqweLIzDu|mx#w0?nw|Q%gpAkhFgmUdDC)to6?C;j78G74% zya*c zHn9wH*dURuTd0sHj{la9n1{Yi>6yUcT{=yVYeuPfS*r_U;mNo}yy8I^19tS{%#!sq zN#p6p4`;g{LJtZ>vrkt)4_g-6R6KL~T0=MXS~7bge{U#=>W^5QmkrW5HT`WzY34#_ z+sp6$Di3@HiKe~AhygD!g0#1_OCy=0aDr+E@ck8AcjD725;wBHm;adw23tw7AI?kS{kJ}6qwB|(DE}ORo=P_j z+eT6EsyDf~=zlex*2j$55+1c7H0WWGg{`4N9=f^go9JEh;H18Pac$AS74BK}Gt^&Y_%>xPm&77a3yN4MpGKjj zuR%m}pm)K3p)^Nb3sf0|rlqA7#5O@b*0ssH?;q9#4zK^xf*woA9AcZ3H^$uBe$E(a z<(#Sk7*l*=E?q!})uEsI6J=OG6MLT$iGD+%Jkx!$!$!Z)J;6j0{^#Db1N;XyK>J-Q zy8hNY3(v;hpr72#uBkX*(ip30JnXApVB3Xa_>kv2!nrFc@_h|IEWS(t3(4m{i~TI# zIm?@KU8EV?X}Av{BkhVSgV@QmV4hb7;@m#1X|A*pwrSv4G1U8$48K7f5gJs{PWa>? zEcrpr*Ii<>)tVJR15UxnoAp`1P(4vg1Gz*MZOugo@f&E6ZgF>@mfom6A52m4Af69G z;7?SB|ry{XWd9icJw%)TvZqr$s4V1jJAYMB`duydnvV^D9!cfB^jU zU35+-@O9tiJ{D-$?u)<>nUxS`vG$u(kdv5aDr{X-u&YiK zV+`vdM)4AFBIe4QUCg%^3A-fS?LZtSKH=QS=^1((M==K==8r}%1My(unvRU`+9X=ILKQ$C`deBWdDq9oea8a_2w;e!Z{DUbrOjoW|GGanwd%?w>vSfU0 z(f{Im&Ul9&tkO*P9?U&P(xe8?iUs&VXJ9Li)x-9A*uy_hsP!D(!>+}{tshFSO($Ud>cT)DU3$08A`(6=?&GQfrwLeqXSiHV>q=Ach}}F>|W=q>st4x+IF8 zeOXwzLauJ|FWUCmUK%72eS93UnL9*PDFY6$-v-;sXSbdAWVYd!^Y_YQ^`WIS|I^bI zN5dAwz!a{D-HAftT@9Hv#MsNWM;p{B*Y$xZ>o$k=SNQ%aVOEfgP5uWwNsHY{))#1P zfv$DE@B1jNs(q17DA|vtur+Z`O}(MydXD~qh@H@YeLCj`# zrpVQ|Uqm7Bk-)l(il6>g+xCR}a8bNM0UZAXEeA|4MucAg&5gl@qHy^n^k!1SqLOae z_)txx0TU^=D5R#(g*m=AOY>t{VHpnm;Fmm8sD9q#t_~6Sbm7N8o~{9^XKUY{*I4)k z5WyuF7ZyHCQfD}|i>yEZH>rm3l)`{4+g7|9c=&bnEkF7b#g|N^#TbwqsrtzF0lF;R zds!MYumy9fy>lIg!+56HWJl1}aSA$E0{n%pEZ0X?FTDOR;jgTV_^yZ77d* za5e*kVq77h=6SV)!v-T8oEd!JZJ<6!aDYod4cguXT<{i)JqSToAWB1z6^bbt79j*; z=wM>CG#Fd6HAuNN>zN49fNNHuXUb$j0V$ zn;{r)Z}1KV(ObtZ`SQkv&VmcE{}OR-eddkwE#Akr9O0wCtmyuDN+!2l4&G3 z4fk)6weFrk(q)(_+91liPF6|WUKUn8UceST_;QWhxp>F(!ih;>4ku^@;$-6(NVky?HN3S9B@zqFoSz3RdYJTEOJDHP1xcYY&bv$?z~op^4gzv= z0~O%S8YuxIk^4D@m|vSHh9vo1U-6V&`1T*3(gA1Wn4?|l*cgl> zeVYUZ^@&k#BdnYn^;sJXXc<`l5n%#hKR=8O4~5gaKpTw;gi-#%9TeGg{zq>3mIkq# zvI~059ti#zW-A&T>!y_kG_Y;3gZX&Xgk>U=?lXYSd&qvJbMu$AT z(_t#0jRU)nzUp2n#~weQ$W7h)vmRH*3=4H>C_54ezAN8)l9 zonrMZmx1IBPqX?Y7$q>y?ecI#xF_asVLVPoWb<(gv%r>>D1T&Dnz`8#=oV6XBn7Ch zC@KE>MJnsXV=7{!HtR46)EdjIx7cb+$F3_kGLR z?ynuV9+rkOvl(oFy}Xmrfb{sxm!o}R*6cDH3yG1bE=~MyaNUcDeQ?&{i6Jh8Iv81)hB7sJ*1Px zRskzL!YL5o9!_ESAKj|$C=&c%(S{!`SY_4~qyiu9x_3+Z58}hf=uxoL6Bvo0;}a93 z=I^A18WZ@dcaZgikTwz=%?C-?$54Y0uU^jkY&%jh$Tj2Ki37c};`3qv?=79@P$G*? zVW&?NF1E%*hxA!zmAlCNQnoao)wn`|$Xt(LjQol1YzCRL*jQ~HE>NbI*Lgkub9igh zKT(rYP$7vo&(^820Ut?x%j8Nzd{*7deDF=!0u$J$B3;0kY3Skkn|qZe;dbzA6kB&=)8@& zWTO#Bkobz6;$F<3!*TH@OHg}lmEbWo=jo~NdAN0fFv!7nN0#`>`tSS=@3h3oOMLt6 zKBj=)6lWYa8A$)-n$GIJJQ9=;ijr`}mYl<}y0t|ZMd}C;^@)F9R(tj8)#E5P@}~lQ zEkk%)0~$`-Ndk5=37EMq3?d%_aEd+4A=78dj;s6AuD5f%P`k9y8E4c%9KVi*rulhB zUJTcMcJFlP#Sp^Os{P%{)?HVuGOIU`G+QD?iW`*n2&1ifRw*ziEk4b&UlE9*ajByx z)b(5iLu{9awnT9i*O9fx1xCTrgde~_*F>f{7T-91mU{WMb5-Y?9WBc*dpMYhqbLQan_u;~9&9AY>`q5PBAxxRL)C?f$mWVWre^IbtX-S4i2~w?<39 zv)z6ch^gPA9$iCjgoDy5CZ!a0%+K90e`Q|NNxHDwq4E+U!-7eCg?%5Z$91)10(gKG zO0AMXwX)tvx-M?lJ^f^ps9(xIKey&n%MtLvte6-Gf}x9QB(!F!zZJQIt>ftk!b;~L za0}}S!s`9VOGK(WHr(ZbVf~NjPtIe!4zkHts&AZJQX$>xdMw+yqn7h+7cu}^%2$yG zpLq$d>1BAYB~yZzHaT{Ens(3MwqTUTAX%>DD5uVq_+>`JUEz_F?iH?sM-aKiay!m= z&m1t1yn1yE8IxTG^ICtj3|f7R#ZVBgFyXzmQ?Aa_6R736 zDg)nWQ0PnV5M@s4Gd;o1+yylbtk%LE7}OIxZpmeOHB|y*-L+C4)amVf zu66$}*t;X8;wppZt~Ti<9-K=qhAC2;5)<_86UeHT(8uf@70#Yk;^-1YHjv5Q+F; zDe7do@84ZFuGgd*Q+YLEdVr7P)#G$w#Ry7PMoG><d*{l|6r})tKwYO*&DepM3_C-FXHM*v=&eR-HT}M zN|3Vl^b-g&aiqmRtaae z!8bki5>B7ZcnNwpviX-6bo)kq(gGc|swJ5ubjV%_JhuWtUVpMek^S}LDMn934NF)4 z8<|{S!ss}7+j7IMy;s54WVk5wseNmVCF8>q{oMtvCzv9%RhmT;W}eT5u^QjZv9v`s zAk|{O>Xw=`G;=rM>2l<=T-c>^ys%CnvmF*8POHeSpOC z`%+#AY&kiQj^-bJ&R+gc&?G55(>C>MLb{a|9Y_vEbQPM_15Z47|Jo5(ZhwhS%^KZl zN8I?eXMH9=veXEzzB%{e`9-XqUF=A)sE{sJGZ6MtRMLDfG|b`;SI8h2Q%ve|CQ%tV zI6be7KX-h-J^UKE`{etw%j6Xr$Ra@+Ot_dayzg2-n-J2q)(~t*4n))F2$o!IJL?MU z^HxHD%Wa3Eq&}@9d%x~2@O?f7t$yP({_2y=Q1&Cc{xg!Fnjy{=Os61GjX<=w= zp_#pOpuf5wFW9BYMx*Jok7scHg-A~c-{kal&Cy*DB4csnx;Ph-!fk2isel*AN2~GU zi-`kt?hXet>?8zdAsyWDNf?!)vTb$7Kg!&n-HQC-KB<2V0p`lm0u4)W7{&>x48JHU zDRqykh%(=)kXrEzv+_W(m^6rAi!4Jy17fgNM&om9ssC}M<@6SHIkXswx{0$FNz+uz+xp)wbt!l6I;me2qp`rdY*4#vjIQT$YU3hp6?x#-`F zu76jG?Na#)&lb705~=(|ynHuy3Z@2nXWcJDKZ747V!!|L>_PTuR@SN5RJsfR2LF&- z=_2+alQP>mCMO}fheC>7Ra2X zQ(+sw6vW`r3dGZgJJ(-Jv-C>Hh*Nx4RTG#7N1r<&Dl1e zi2NG`S$Y1kBK(*Uj4XnA9!1pzKmyzL{ii|YM~I~h$G1%OrH%@PLLo98qD%z0W8J_? z5x=W*N(Kgo)%A6G4Gm%+Uth4ICdnTxsJX3$hJ#~O>qB2>?ZZa*)I{L3nXf9QQRRUD zFI6$D_bL)#&Ay^)8W`0y==fh@*8hUA{+Ya^sQ34utjHxK&4eDc&Ll!Y0(nLJYF*Q5 zv*pgmK0j}}D9~eDd7eEi4h_(IZ*>OlZ8a(E-RqBdhJL?V*jzvIqQ>!uKSQsjm8#YQ z=S1;8G1stgVo(dr0~{#g60EaXJ#*1? zKhaeL?9-o!d0P<^q#S8}B|P2h&keiiNxR70s`Xu0UL8{vT)BtiFfFYYp zCUQ@fc(?5~*m6`*wqOcOF-~PA^f~8M5v#*VQnrodMw02|4uN||&%Jh=iD9rWU!Q2M zJyPf+rqh{(u8B`WAsxmU?O6f~XFa^s$CwuB=Klh>lrVPxf+g7-xf(nET3p)U9Ug;+ zr-ll!tepR)WiE*i1IriKe%N3So7)B%HWP8@3VQDUM5}FTmIQ+oe&iti@MrvMd0MEAb~v6rF~zOa~zxY&Sfuw8`T*~X|LHgD+4-{9LqI$lN7jg(M< zrTO1m1F0#$UQC*?>_BaEf0leNvV3@%!!jaUMIh^NiL&zW2}4Qbt=8Q>hmB_-9wSf> zSz6F#t)38S=LM<}m0KR?WyN5phRs`PYP6WCN?D1_YndiYDY2+aP!prn` zr#q1Ae;Dh3w9*pcoMb52ZV4cEG?dr3YXnP%p55KG&6HlK-`l=rDD97PFPPTSm;gu) z0I=E7#g6v~D#c&CkIi^9%^jTY%kTc8&9Y~5Qr`*(p#N|HT27n|L?j?n@;#K@m1$cx zAT%47H>D;|5yPq3l{m9-#^sjFLjga2s77w_-zNUw%=9o=hd4)j4Nf>3!a}@r5^BpO zWG#%cnB?rjj68G0*%Q}{pnDwZ&f6wu1OqVyAl$^-7~Qvmqz$1)_czvv+zhq+N9j9T z${A{J8Y2q5PRa?npA0!U1!7rW+~CY~pD)uMYmaC9zCtsqp*+GCd|zDyX>CW>chJth zROM3Y={Bh{FwN{yruizm7F&HI0a|6q4z&Z*06wPXZj^kLdUWbmo96RtUl@HN3q@;i zzyG-*UGyu{{|G{R>|*NFSt2??^U`;vV|Bbu#W|CRUG)@_I2g2Gp2Rx%<50ZbsbC-Y z<#vxVftG@TNx5MJ+hiWH+1Ny`DnYp9;=RgB@)54~bK|3Q&|kn#RM2R7`}r%{qwoEL z4}14)t@3~1V>&UrX3FR>|0{9jpHNkdMVSk&=6?$Swa@CiMxB@zvg3wM_2@ZUC{`N& zaR=HeebIwB2_|lT@-{9E*(oDLh^}>~Z-b{hhq7YdThIlP-GTj81PDIOkpj|Zl&xC8 zcpo+nrVan1QtyvcBB~(bKP%+}Z}{;S#c=o)zZ*9L!AiqFjHxl?{~#p#|6z7*b|-1U zw8HTAe(AAx7#T`QI$eRC%!1Y~j;lukNn@=588>Kn6~mE$T6)j1%}-P+X+9B7^wR|l zJ12PI%+)CCe1A0ZAoi2|11-!HZgrq_*^SWS`X^oe4EQG6-F;52%ZNH|Lc#n;SMi5} zBQb15j-98B+gCP_eh;4xEF@3(ovbI29(+5|kJGf_Uv;eDjXxJuLZ42xwj&Y3^sWRyJ4byRZ$0_v7q}e;5R}1d_TbaKoY2H*HGUCe8RkB#JnH_(SWHN^SXcSTuXf5oN+D;-`o z>Nn||aqA3;8jN9cHK47` zHk~Fnfb!@B{&IjfA|*3{!D|1OJgSX}UgzUN;Yz7XFxJ~ZcP|+0Lz$lW@%6#*rwqbk z$%8yS(vzs;+S8dCecq(pb@*xI*~QFD6z8z04lOyDLYSu;Mq}qbyUE8vs2BZ1pZ>de z+;f(yhPJ%v3n>&*=H476V=Zm;yYV1n#}7>me#{U!27-dJvpncoJ9x4Hg~9*9tgqKw z`Wn97&M@z%Zyr**$ zGQOSei!c6D!U5jT)0%<^>zd{hyvp{5t!>$qO1>F=vYl1E$X^2 z%5>s`6t)D1lG#|+P+22I+$`5JEY)>C5C=7jvY-AuJ{PG}W1WRJ)oQ*E9vvOcUySTx z^j%h@Ncz)B{WZeBaRm#^xlt{Oftk`OR7mRo)zL)!^9}9jUYzi-t(I$1OW@HTCr*Xk z>c9#gU#6kvp;UWu^ke`f@arxxE9*;;lzBKn^XJ*okpkW(OJITzRVrh1m*|ZE%$6FJ zyL&B?8e6|e3LdWIaTLnh)zaXEZ3T>(Jkq^aDS_@mFBa{I-qw;VAZ>-JnsePDw28je z@o9ot+LFVe>Ejh;4p<&H*G`JXM+z1(vFU)N^RhSQ8KiRmTI9A4A(4g|DDbRjT)%tM z29+w6B&ZU>EG9A@g^Q+7z1{pp`wv2z#=~hKhm%GM#p7A-{ep`!KhgA0Z(nKz;c`A1 z33VWc`hsTBr?FSedQyCS+)+ z+lBRjmW_NwRKg|~utJnISHo;v`p~=Yd*m`y5Btw*FzQxC&=1FI58P_qt4`&Vai^2c zCEu;e!PY^vC(wi2mrs*x{Dmx2gE*=s#g?~OtZVaPJJR8oV z5`=}DR0A$xPFBTK5kKo+3#uN<7FK{O&oL{ypuZ$| z2ds)Th`48xfLaz6rCsUnn&}6_D2RmT32pk2YlQ) zo1sL`4DiUv;qh@!OA8ZN9Y)Q_*bSaMiP)4p!{dV#jgmX1c>nqSh~0yk)HlOh8M;Z#115?QvmlxlF1rX$J~^x5B$f!K$E#vg z$|Y59uqTD5+j|#@)2kT8gbu`Le)E<5_S9|CFAJ}Fo>;8jQFLG6@H_m1s|d=xaGbRL zva5Nfp`4o+U0kwn_vcEKJ|ko6pq}U6;-I0G&_=fK^4$7H+ryzmEADw_O;oa%^YrEE z;XZ8w9-#rf5dhJ47&SwHH9Gh(+g;;VHjh_bF6J38(D8J5u%w`JyIDue0r9qJS2DnG zM(WlJewNi7= zm1PpHw`EHZk48(AWh2Dp%twqPPz=tjiA=_1LbQP80t$s4HTi#U@K-62nPkDzv@|p< z@B;aOGr@YY!cxP^Zs>Pd#JCm!t12c7#;I$1+F`ko6w=Txul|i%jHl2C!CBCFQKUFo1!|6pqGsDO^7dP$?xY2D!`w z@KtEfVfE7S!IO5Z!2b^60q>=F&5 z?VaO}3;=9O$2cAJVxMBl?ZP!pwdeIyg;)H6xyADP$sV(z03@38b8@*~^bt2ZELDBn zd~!L|wUS)9!tMLj(Mv0DFmcJ6O>QvzfJYCkj{<A&ex|7*7C?+3a5X8yil56?Sg)znZyTLP-`loW-yS`ftVy3kMu#eOaL z5zpP&?mTXpsvQ)&jL9j(FkieQW^a%wTh5U%n!k$=tN#3Ak3l>W&n(4*s;qY+W_IbWHazG0r=Iht| zuRr$pzbUfZg+)e2F8L8)53G292iwBIzzOXTZ@9!|X?<(D@p{-xYH)OvB332HN$fEv z1q(F+84d;;nd+OR<-R0wrVdjw0xaBMdFy?+;Z-;gtWfU#+r*VpOi-|6YKk`H3n|!j zMV6wH5;e9swBFNGufZOtWJMOzoS&R2Up_C@xF2zVgX+@79UL5tQ(+71S?o09A0HnF zTY$W_vAKsd7qc@f*H`m>*2@W1HnNp)TdAD})!dm7o${Y9pHq{z=Vsw|$fP``tOB;-diFBQDSh2tF9gMz zLKR(-`}^rf)G|vVo+~o8G;cdYbdEA+CerhQ!G43wuR3Wc|k+ z&%aDt<>$JxydVG4bLek>@#&{}PksO*Ch_k2L7z}%Y(vQQ}5FqZS8=h@skw?L8OeBy7li1zx}ZQYNOG|aJWtM9un>tK=$~6aBqf+$+a;j zGA=GoJLMK@fzQUK(3Y&9>QOjebh=DW6%|*(7NL0*<3vqb!M(hNN$Y0oRrcyVWuc8e z%v7{#UfKFhp_AxX=T0TFD>7?;hH4)wQAQcoaxJMz?V&{Op5CHc3YKMa2p+u1Sb3nq5$!|g?twic8~yp{qWipVauSv^Y1QHp`rBl> z(*L4%F`LFm5^hy?*V=rIxcljm=h$m3N6&eyGOeBZ?`0g{hOR``$b%G|hQ)a{*-A02 zR!P+pn|fXwCv>h0s;Q|_6P0Ebx!K3LybGo?H_FL=4P9$$<$a^y?J;?`bw|BvD0Axk z`3T{Mo{Rhvv4=N>xTA^!I>yiiR`6^ZB+xiT_DjyWalux=(?P8RcR|NSsC=Jbt&C(Z z@@0kZ@$%b1_2{wRkae44ALeE6<1OXA0mcQ^8$k%sF(c{Gybf6YqD&7hHdr zXHB$B`{G}>z!%T@jjwI1M7KzntK^uGeM}gL!<7~3Qg`ahi!*Ceo}{~gxy2@rv6DmX zs&vo&r%GaWn&5A9-Te@Y47S!;TYF>x?iQi0Eq4ZCoqb#Zxag`Srd0+XhiAu+gs18X zY^;wMhAs=>dt=OC6Xi=X$MB_7+^%r=mW7Fl$pm;HSh|J^VxeJ#>$KE>a9Z#Ld)#r8 zpS@nA_q2vd>{`kCx$w=mnuB(_-xdBBcx6YIsxl2?VGeRu9x(FPlmre%e4d3dm=~OP z@3tkWkUe)ZEl+Sh-uzutAU;q)+#al}KZe)+)?dF^t@}~H1!KE(E*O#9_Q|V>1y?#G=dlAbN1|TrK+2jIc znYcQh8mQ{%Q0nODXzM1Jd6%--zovAFajBc9yQjwGOmzQawIh|mTw>r?L?+U1;Q3(s z4Ocq)nnu=RG{Xpv@cR^&}n7Ay)ePPk-AAkFyuN-$flnh9R5L7%-dzf(V%TU4`7MWgL4J(rT} zc_sco%2RYlj<@71(o2>Ge;9Q0C*x{PeBrFpw@M}4nH((Q)uVTF@%o}yT}5s9jqC8f zqDr?vC#Vnk4aW748!&91rbhB+>lYJzZMY%4wO#XTc;98B`IT9)L1E(FD*RDG1FjP} zV)ylWD%N9o4gS`fzQK1dc@iDW*yRqcL}a0F=R6vS+5XiK3$|%`0c)40nJ9`j)v~;RUrQ*U5hH!&tEq>?>_<%#;aXY$}wZU z-5>u?08|63{6jdj!f0P7H|8UI_T_?uOb%U}qF$+`>)s$U^NTF|ygYq)6~)$;df#gN zb_+H~CzIY4G%bQ>e2C{8@4F5+*VFcRF15ljt9{wijLmh8NzW4Dke?R=y^L>Mh7OK* zq1Z366p9eoCP^b1!B$(#(8l|RLC+|yHYFv%=;F_G-Wx()T|-Yqd3K|T#;%Es?XXj5 zx12U^y_!$`R8uUTzGgEM;9mP})+Y#uK?ImRc_YAvGnIULednhpmxumIY59jlR%dCe z{hAMszfWmdC4c;z!nBqk5!t;Rb_#9w`sb8Y9m8U=q`m_2;b-6CiRru6Zx^7E)aYsa zH)l^&Qd(BYAHMva$1~wQ`*OiSCx^B?&Al5}an{>V>g=?2_AguU zBPQ?r993j;9E@0K0L6X@PhfLZmSkz_DfM+u+%esBTS3SLGRL#0XaoobkJ=T^)l_q$ z`V?nQpW#pc_z#cY6yE9&WO5va@t^KqXb)%65ClOeN<@HyAP7QH zA_5cyK@f@(5uhLlf>4x*00lu1grYkoaemn zTKBtu+*xa8@0RMSUENi?tA0IU%8D|WXk=(GFff>MveGIrFtAt9Zx#v?w5DVmgbxFQ zL2Mx!8loXeWqf7@5@1_YQVL-hYM|#I2s%@cBD!a7iQ;o z1^2#J3myI45gvEmm~4^9HGlNuBkTBEdShRo4$csf!&lb+*Tf}?^8pH^s*gNxySaX+ z8!UgVDGHI}+KQ}0`%Rq(`W$p8oW)X`wBebuv36(hD5}^2EGN~6yQ^uodgTl?VEb4o zAhcpevRM6=ThBb|-37YjMzDRVxyOi|SDkV+KB*{$nFT$EF@o*-Hga#|T>d4z6eySl zQ$#JEG>LIHER%hUL0VEiq=`mMh|g;7_T$Gwy{*s)SqhqMD}?h!r;qwSmXM|fMuCIq zwL4WNjuzraI?<%Wthc{n-R|YqX(*b6Utt81e+=XIVmtP%9b80^-mu~BNMkP=X}bwZ za9x0n9xu_2WV|4RU5NA$_#1(602$kER69PAbpK%Ux&R8YPZzN28%YB%C%_oi$5T%g z_{0E2MX&K8CHV}>(%E^ea0Xy94&UR%R;{GCt3W;U78?PA>LwDr@Rw` znJJkbZn>V>Bt(gowNf>|&fatw3jAPU{Mis@GK)kWgElqlVoiKTuqq7Zb#?Ej{&>K= zTrCof=p0jzpm_cqD9g0=Q&s$H0YC((f|O-eucSmvWlES>siiHWIEBrZIdErcduG ze12@)>O?eEVLwjdX@3Xwju|jiqie9tAZX0lmy@>s03>WoCY(t(;Jq^qkFg&Pw!fp{ z<8$U~=gWHK67o#;lfCJXfVDOCbCSAKARfaz=ix@z5IqYYF$zKvnAy~D;cHNYk?lOa z81E-9JZg>LRio*<9w&Shs9y{|swa7eGTqION=PB>_W_ zjo=zgKYMd3<(xQAnhj*jZyfs)Xe|OfCo}CJdr=1GSX+i>ufwLL?Lm^ zIb^JF^b=@`{Q(jwBvc0B3-*BqPHSH3faTpJ!3XS)?p2cUYmcSrdO;m}c*M?{@EKFVY68 zAwzYVfH-vwuUsN6whBR*0y8KfnlJVyHZra<#zPi3@ZDIX1|S%nE=d#}KDfOlvSqYo zxn;H`foOtggvcMnh*PwB-2>R+-oa+>tc+YFO7WJ;O;_#en*$P=onE4!*HDW9sWPMGuJr}DD}eHPc2 zC@aXw70LC^=GmLM!T~3VX6e4Q&DYE8R8vvr{(SKiw#=j=4CW~9! zJ<}oK0(X0H2%Z^(P&$>+ z>Nxx0$-H-JJfF}%4y&-JE*8yH&Oj%NI0mtJA9sjIe*S7e##4Ht#ndpIOQA*7OvYQD=~qDX#sv8?Ttft~#sD|G_om zglkz>BsjB(QqMMH#lBrkIUkHYnr}>X@NB_*!Z%ajh`LbChy|U%lfm(U;p$?()XJdje2W;T2x*%TqIhYbs=)0 zXk~6Czu|opf4sjLy@61$0^G2)DNY2qDQGB?1R4Z=>>XV21pf$KgUJPpz_tz@$GJOr z$2CjaZ7kh?W}Q1M%MNAu85-dl;ca}IwVTaaPwjn0yPe={e~zIpQzB7XMIBS(DJ7bf z?(!*xC3O%lmMt7sH|FrqdM4fS03EYfv)V;Dysf?7c7%AZJ~rQvUJI>9-6P*8Jv1U0 zBlRMyAqk`LqKu%FqaR^AV59<^sFQIhh}yt+E}@Dj4EcQdV$*_7HhcS=Up1^WOy)N2 z!tFk~eLx_?C9CG!kjS^s2h}j4Ne4++M0k)`lVuP;FG_Fp?uC8A&+41&8wnF69uCbQ zYGt_5Nw-}ZYI;03J>R%%Kqv?tM6E|X#ec@1rF|dU$1tkU5?`J$qGY2;RY046^qw@C zfYahRKXWiL?gRa5sv+29Cmvn3zHmog1gu~|=DEBh|%SZCy0&KT2)J| zKdRR6(*JN&r~i1{{cPqev%oVSBIldA9#hSi$3MkVD_k3O+7H?496}pgNWB&nV)k^s zQC2imlwud;e{&lu{wbPGKt#v&VPro;@`FK`a_nwmqbNj=fPNt%YD;O*mR&hOCS%x; z=Ui!{QgZxf=}&APJX4DF5^hz_%L~?U&0$j!7Btoe6F$*2o>uF~m-HaVmYs)@Wl`*u zBtD3B$NJN+OHU#Mwqi|L&E)p8_tgsLsT+;qc*OOrGn#Gs_P0tBVt*1QK1kCwCN*)) zw-D$J=`D1O_{{g2=zVA|ARe|F8X6Ym*k$d!t34hk6vM8N)tl`2ZLyvBsrQrrr^e5o z6_uKKHHGF1w+o^55r-Ow?`PX}#H^o#96~NC$Qj9d$!Q7K2}j8z_$n9fnz?H)OcQKB z_0S(H4D+?Gjx2jwhu^X*f-&~ujiHr zkVdRPfVt3~L)A<4d5%H|X~+lim_E&jXN44n*qyDNF`68i1+67*tXI?L#gos^Go^V4 zGU}9h6xv=^dv25Ge)vD}y%~5ET^bcETYZ`Lrz58Dl^U~9g&be3*5*}ry~Y4yr}iD! z{)<60#5(MJIu0$nxG#SdBvU{JPE^3rp(@lfMOXF6Z7rEEyn=dSae5TYUv- zG5kWdzgAluJuj>W0Vf%|u8t=iE$VKzXD|N9fyf2e3BW|L-#%*3t@o#xcnpJ+?5~x> zQAWVk(;cTyx7gP$$MKD#x}xpztIR*5jh-tDbdGtkfhE@uiD3op2Y^gJwKUKhgHlDj_2(AoSnSke#b04F%k0g^OURR zjqv-VM2LYN5Mvt)6Lp@t;fV2DcW_}5{Fw(oOr5ct%ePwrG##JUSXAmxo*|q&B@G{$ zI8@>0qZ=fU4=l|3*^e4TKBLf*swB$J%{JJtwqgsxbYjaZij?7Kwp@YOQc|`~Fra#v zv$wpwyiM-rd&7#dH|#JB7hAWYB{=J(zG+uBh?OMluwp}!tQ2m2ki|5BkTr--ddYD8GziZ zt!x~@ZX(qG>H&t{|E=br2K}pxlcfl?hJrFk%GSXI^oE_6os(J=4Fm!SI~bdSRixkj zha6fGp*DANvIBE)xVpNsyYjHxI+$^A2?`2waB_2SbF)EvusOQhI2pLH**Mbtdys#| zkv4HOaWgqr#Hmf7Z_B^Q{`Fk{W+(i2FtDVGNj-?Q?sTIjTh zq6u^ScZx;Piaot&U|_^y)1%!qZZ(-Tayz316HaF2YT_Vd6(_1scqXw4j255N*{#;dI9MN z+VK1Tr$D6)i$ap4E>5FG_zw*%jZ-V~KLtND@h+5hII_voe-r;D%UKlpZ$S+P?%f{Y zMCfP4e=~%Y%0578SL<}q`|c9N7qojb2W2V#6~W=YZA(S{YZ(jwnd?L8D?~X5XFKS6%ELs(hMv2WL%m;#ANQB zY0W=!MH9i&T-D*E{G(MWPR=g!Z0rPC?EP4V(6Y8|`=0@FCRCbH z6sc?k9D%*;fG4%OM0&0uy~tPTjD0yx{{L{Z()JxW!F4AO}XM#Hp9e5fzw&t%$?R_)oN($1}GvN=0@m2Au1R<%j4 zbl<$e`DpuIa!JyAU2r~kwl~Pn$?3y$55gF{a4P*T&TzWk;jlk%o&13=dHukGNW7S( z$|lOIz6<+OiQh)xE0FUU-1pkv$2Je!;!I`N8b{+zYurrF=iW=-)ei4U)gnckwJyI( z>%|5{I_LT{*xO2-Ry3&{viE>}C8Z=Y7ndtEt4q*K35p+7Xg5Z>ex6rLdny4py0v7rOfyYcUF}Npr9W8b zawO=))Vb-j#pZ))+*9L;!%>{+;w%)s!0qQ(7{~I)QgtVjhtlrJEsBdTDFf083awB1hiW1H~s{sLA(+mz{~#cJha+`s2yy`)IPT)_GD@7<&Klq z{zdQ9;p4AdkQ_r6Z@%nz)9~3}EFVgF5<0pUH>R9CHup&vQUnqZu%|g-Pu4j8 z*Y(Il9R$ZzFi$P!KQ0{y>UeN4h4}w*@0bdW$^)66s z+{Cu3lS`F#*v6GV@tTII)s&)&;7e|l>5|p)Y?Abu<;pn6_ryF{0D4;X)mMKuz4tMI zCs9_ffansJVb<$NrjgC#9o^UvWc8r`qQ+0KI3AUjLmV~^FhT6m|1?YmhiR1{Rj{4? zeT+V?YcZ|CYMB}mJRQkP>v+YgQPowUQB7URuDX1VLFeRuJgs^-&|2V}=r1sq#HzVL z#HK~N>*ZXWewc>lVYk-40JUD`2NuB|m+neQ!%CBLkLe(E;+E~ zUKet%ug|xw5TEO^lV~Zd7@$B--056(UfZ8A#gm&wTi2ctY*%7EuWDY{hde+Ao-xlt zvA!k&G1^j4)=pHRMcct2Beh7k1ci7a1(`$`zvm+}F-PF@p3E{TLptdA`*CU{hIN{3(NJ`u z2@S+~)|2|9u7Xsi_{-UKcH1ZU>(UO7!I1+wUCgiA)IyS^CyzoWO+A=oyaV2A@W3;~ zKeKNSNr&+I9e+IzUTaP|#Lk!9qkETcX%fZGY_GHc|Dxxzs<_;O?RW<4TVRs0^8{~&2_ymn=7}c3~ z-($`72vtDZZ?s$&tsCXc+T1TqZ`)73$467&d^qThQa{UCK{!w}s|f7kDi_*Kjr1yY zs;ANPxeBz%(RISF`IGZ7LFhe2DmKUEeqro-){Pu)V_Xc`S(go6cq?pAHjdkhlA3gB z+)&CYBcj*tQU5rs_d?wpDLM*yxGFZWm?wkun|p!XFD%yn9;Lc&fAE_rRw$s{?bSLWZLF)^a%8UoM6@&<8h5Cfp^gO7YvB)Y_!H|CD}`^osE5Mr|x4 z#SB{ULOPHDxFx-jR%Cb@we?Xw80*@(+l+Gce!NL|P!S8oin8DAi`WSBdwZ9M=d{G4 zUP*u*0SsF2H<*9`9{iq>mrD87<=nj=uI$@AuD=FSO)cS&@X$R!+~O~P=Q4?%Y4>z> z-OCAiFIuk`rH_~clA&;z`++4aBl-oR*Qr}LT|&}q!#0BAVu3cjcteY`#8aBng@Q)y00Mx_2mem;%KiaJoP(fqSss5oOOX>Yp4ZmUN; zTr)0C5iB=ktc;!Lqo~)VdsNH8XU>Mx{uQLB+grX!n>kg4C)WFS?!` z*VKzHH=@tsR7s!;*I6IVRM{?t0zh=4f6;W^2D`_2s6qm1j&qYQP`sW%c!?aSxV77X4|WTX5?dDr!qn zmJe8z1iYK>xRfs!myfwKo^^WCdMX-1DH~Sua>*gE>U=UiFLJx&c#`${R1{5&;Iflq z))J>As(H1i4=YsCBZVzO>j@;Gpm5)cImtNnDtdxJKE5BIi5d_Lh@92)UB)EkmM)B; zlJ+;Oaaq=Kz14ATol+DT#RPJFIf$^1kFaLQbjzK_lx#h$Yz;)9(nvHDy~@FE7HPe0 zO0(%0%I>3vUZO5Y?X{Ko0J98yYScn}#+C zowiN=nVD%p=V((#%JhA?%v%15h;E|OT3Gn*K<*wxRFm7EGwH6Ic{|-@!Ghc5<>CWJ z>paJ3B5arE$|WVgVJ`}4y2x1M?Jxr^f`8Cdz_Kdi_(E^L&Wot-*?ggcds1fDN-Nv z=O&km?A{hB_PnoD>D_vB0<}`tET|VJWnwSqQ6^R54^EwJ z=f1DEz9-YFO3qmi(?$8^r@pU`HCGbg{GS-zyP}VRnyowPRajp=qcO^TZd+FeVfM*a z?}BlNrBUp0>#*j+iO0p)9J3zNK#xcz5 zqf64=uuh>0EV`gLH1;&da( zs@))ugo0si({XR=I?UGCG->oMJY&myYh4B>$wR6GV{a(s}~!FGsk+E9UU? zgjdRHM_lV}|7L@P=Jp~3aE>3aOXssrosRJg!1lzeY}!D(Clp6w#JHg46_~r8C4C8s zf3wE}XrS2@p7Od9{pc=42AAM3{s#|?IS^2xn)v`Q@}9dMsAHTel^?!7npBld-!}jY zw}IO*gt`9&s7r;o{hp39CQD(GEj-KeeQ|GeoKYZ9V8t92dZftj}^o;k$Hh zh=0R9_3buFW^DRyF0X$GhJc(Yt{d@=n`A8$@aAj$z?5k}h2xJAyYkg4U8IJ+llj`B zXg24(6}NF#+`UEtk`W7?$tZp-m);nl(C3OTbS6+gY{)+jbFDuU2n&$X)s9H(gZXB9$ox;lFnOP0V9l;`3x2L~%TvaoL; zCSkIN5EkGSza`rrr&`0n5dgr%z)AkST<0Da&72RTi8FyL2NXPzO{si`j4(Dbpx$vo zNHyA+b?f3fSJe~fWFFNIgc=J%TGP8ur-SM-^7e8QDN45<9ArRyr9qrbk!MVScXQzp z2?q`vY`c6KkGWKEH|j8(-qQxLiARc`LPXY%Y4bO3NXr2;sk!)`RiTGp=g&qINu^^t?D&SUB z7hEQEANo4Bw;t6DBe)8ShT`FcRB1@#nCt4p zOrvp?WJa@@bSFo+<&Y|CiI^_y)WMu=21twU-O&`ziszmmYY33*uXPci_GBrf|g^ zJh-RO_fY{xq3a+r(?LA<3QdCnIxvx90-e-RF{4QjWHP|sb}LYXVH$#hLl8h{x1~$N z5da5RGbew0IA83M$x6U~w4UCAW3H4a2(q}sD-Iv3#f@%6#1DZo0x zT)@G2xH_R3N1Cn68p{m4PJrWik^&5(n|j#lvmDnt&8swXP&p!j19uYRas=s3Q3w$I zN^d8eofLQ9?&f7AG$5s|?-j=%QH6HCTtiNvdk{sd9P0RqxwnNzQ1DVPG$=`3G4sE6bs9JjR-V zy)NUjS7u^(01{ofTy6Q!Y`+_1;LsY`MR1!2!%pb4ReT@}X)zFA~jj zTZgm|>oV0~4C;0gnA)_OGm=_m5WANnwF31#+nmUKh@9yS!F^oZ?sArmL1z(q+ zk`REf3;*B=DGq<_s5@QT>kSohxQeNfN>%in#?4y_jiK4FOexhy8{7mQg%qFdf#8pO zuSkd#C^7&tGwJ7%Dbz)oYp{MbC1{d`n+17=9S0UQHsNN{yK>QQYmhkd(@b4wANeeG&&KsG;N|*2A4!8j*4AA7kK@G@Cet1VR_Hy zmSDF^XD#wp(jO9ITdF$KQEn*y4Hf4m0r&x83n`cuOXSmGPDp6+QKN2$@oxojgl^%s z#=Zp$90b4Q)JrYn=0!%k!10(eS7AE@5j;sZ&mp9!h}IHMCtXxW_-(9+c$!4NZT-fk zp~9!j6ac7Tu>P#7z3D-5)aiH{&_GuUk0TI3uz*g{}kp8FbgIXT9i~-JwectPgM7qA`E2JBipZIGbIQ zf4;7@`SX@I@H~fdH18pohb~po2h-q%lbt!wVQ`3E*JJ{}ooJcMEv3-xHKbCEN0th* zvrEFJ=j@;+$VSta=%7ab`-ZuQyBS>Ce*Zg*MMxovDw+j~W5CnKiQG-z?Y&{nwVyo9 zPOcGD_#7gAKDdGRiv=d1?t@ZNAy@^F*V1!%Mgf>SRJP@fI5bZ`kR^av>^lYT500ts zE5#EuroOx>(paEZUeQ9T9Ah$w@}>XELeL}P zM=&SbN3eszGFgVq(I=f!p?xlMfBDN$ibUn{xsTWQGNA{*-4dWCRY%ZJ{DZRRDV-ra zYG}TmN*N0!IYbw$A|9UHIUvqM-$y%Xb$vQpWAhEW4*(w(LI;VmCD`Tv{#Giv(xPO8 zS0QR^vSzySoqZ{7 z4<(AfbKUc1Q#%K_%unPmd$)NZl<-0u+|_V-vpInCIs^|jMU$)Ia}SJ7L4JRA<0Fm8 zzd}V;BnFMPRfH?P_f_N$FQ@;CqJ|0AwiSH3cIna)y>*EtZsp>~zkB{!YK7js8^@d- z?xwJ5hgyB(9%O%<402qd89S7GQ`tMpe~IDePu0cKedy#~U)w>?@8y$fBwKj@Df0B` z^1G?5j$YGLx&xb%^Iu4;`xuge_-U(s{((h@@QX=VDpm9wIAMlp!Ol0?V%Mqlu@uH# zSS$NBx*k|v#?2H=?e zwibZ1&28p#Ge;LM862Vix0tU6LdHkgZ?0_F%l^h z;he+5H$nbbzvM6ur=%^Z4_%73%+*6xps`Jcvm9RvsR@c2A>5=7<56BI zS}2A349Z_K@CO4XS#py66-)#E1sr}vh@Y!~e5amjv!uD&SvPI|54@Th_HB$hcDe1+ z)IS-NhU)>R%sTWJOoo*L`J&W;C(gpb2L-#3x`RopKwU&@YixN!-kML7IE)MkWOqx# zR}>9UBCKv!%})up6-jN9IuVENe>8TT_+t!Wv2u2biZV=Zy+3QE>=5f!3znV!;*Qql z1eebxVlumACvbVd$UN~LRXGy{)$AO% z#_Z_hb>WL)UfWe>A;GrnGanXjCfL5nUE{Va(~Nd%Gt)8vh^Q9AaI%Vd%A&tAl)tO% z(8r7Tkjyn6Gbi4a>45sxk!fv~Vr2S+afb}Mvu`|2RXkRBZyK8#?6xM{27IHtDVwx* zY^adwR1feD+^g>^VaG*#;hkh7ZOfcnqb^lnxsTf-iWvIt9V3CpJvb!YxB#IK2IoD< zmWfLKwW$P@J$UJD_n2RaY})xe`wR7!Pu3og&X*R?^&sM<^M2sz*RNk`0r$q(>bbld zC=W0WnF=pz_Y(zF@igqLH05PObnkaXUZLrR@N;duds4R`nd4ZrC;Jy!`E8aM$Mv8H9ext5 z7z5Q<6I->*=L-cU;2+}l&jo`UZ$y^4G+&I*2d_*Rp{9h*oUSkZ{@&&ibYZ@oMO8O$7|fpkvp?(dy{p= znVb=qoYE%Ekq2_LdE4CC7E^^0P!ZDpCi*vgo)7g}n;m!iPyvDo5eJx?)l6+z<2?1m zf@}e^-^Q}U^eSZ{Dfaia>L~}_UOQIomr7k*cqPk>f1;lz?Wj1apVf6#*KtmGob7y} z3sfX?PdLlFpRLq6gig>-Fec!@B-+Tge@9lPgzRs@O zrg0!@Jb**B^5qY#$Wp-50U4xdN7yP$V-c^+3^m-uaiJ_aBVohGeEf zJE4i;e8<-z^$zN{K{xM)BcZs`;TsvJvbIEEp%f#6F^Ck&gSp5er>zh-g#Q&f)Wq{u zkr<(*db2VTBnGx&A)EV`a4B0Fzio_P{EI%?xa96w#xl8W8x=fZyKBe;EV}b=FShy& z&k87xd|??q`B+)dQpVPa>p@u8@&arH@_QoRhVYuP?YIW>V~9RC?A6r zq(~lxpkxe%0#5#@>k&fgZC5*NplRvf-LQn*mph}5^5dJD6h3$BHgi=5_Oa5Y>pz3A zIHDqP3`w{Q1LGwY9RTm*4sk~k$%6p`YC8S6J?OeTymeT zXfvmB--a7=`ty5k#mIaYfv)#h-oo!GD!%&#NniRS0R>o4U?t3TVD5K|f@SrGcG``B zS7^349P|0%8vhaG>Ewrc{SIhC=`v@aBI+!1w-!*4IUVr86M@YKH5-`UT;k6EPG*+LZffU!H>Yp=;zYc@C?E%)KcE&yr^N}keNORt5_M;fHjt7AzJrkEvn=$Kc$>xo5_fJ}3Xj?Q zN1jhnK34C*yWyKkts%&;T(TnXvOVwT-J zKarOR9~0wy@IE-2(OV>a!Dy3?f=II;#4!x5t>r&CSWhKC;SL5`k3kR zes_?mVXiJu>y#i#J!yE5Kz|cce7mOhsB^l^+}8jrG1pxOH+Uw6-r^? zY3HlYZ65Gh7Ayj``%TqBCiYh8E{Y+y4AQXxQ4oVd5#zouswil&2H9uOxP0s{WFQ&n z`!JQYb(>8^9!l`|H}4$N`gqb>@XVMrme9}}o}=>$O@=oqd2Qf3hIDxXN-7>?>5I_=YT=6Jdk{Z#) z>38UL}Er*u|TN@YfH8Kg@>@dJ1sRXad(E*C5z&VTd9`j0~_ z5ERpK;maS|^melE>e1(Nz8McZ-jHSEZVdxa293aa0S3v zdG`ygP~A)w0$=a?0aL#*1iJ7S%^e4xh&%6s<}RV=6Iz&3s=%Q2V#nz7os5_Z_Cted z*Lh>-vjUPChJSHZY_eL1tZ$v(TBbe+YJlzuM5on7uGNsp0z<>Os)wiGUdxd07POt6 z)s25E!G6V;G*W`TZTXToNA1y8DT#DBfga|98lO;xT#Zorn-%{o{WCKcyr8(+iU8a7ANN7(N3Ut}0RH z8WdRD$N*#jRdXAc9K!dEaW3j1-lNZttFJB$>304YI5QP`yTd0NFSow0>L_7;aQkK% z?uj^waI#raMML87?KngM7)T<~SAR1gxCL=pFTGAN2H!ih8*MgHxJiCGB5DJKWBfER zs`6_4T7x*dAIQWeK-j&Q`#|rjNp2EqC8f@rkr%`&!*5pw9Nu!WftsONPg@Kp>5gN# zD!*oIy*9}ur=vw`X`}gg*y~WIdh=C9hnaDBt$SgPt!+Ki28=kIANFL#4!(xWw1faX zG!g-Y@|hcBqPs@|-R2o2tOi0ixk;CQp*Mi*lKn}3oMJ32HGDCL@BJ`0ewX)Eu-CWK?+WI_W|KT>pW_O8$dk|VSCD9qYuE@_w%xK+f% zynT7pkUb3tu8nd8yR*=}Y>*D-npB$$^p9BtN+~Yiom%qVML2kY7(=lk*uRCKD5c3) zO33416!Nxr7rk4;MjlfSiaJQ1;lOSUOw%3SV$gzO_E$CoVb0plea<{m#!Nx5u#)d> z+v|JS&ZZV|_+U5GIRHxd%TTYKBiZLw4D5e?#72ST=F}g`l7N+Nf@aM3a#zTb`GVz9 zqk#eb&@5Kxg+!!c=Dw3KZ{hDh=qAcJne;p)(Ty7ef|LULYrlepWML_inIt&uF)~rM z@_blbt6ahX^23fDOS4%emPokz-FYWKZUrix?SO}!U7)bQPne_tM$Jidv z4tZZI37<>LovuPUh0X)L85?JJ#UJR@3cmK#2eT7{wI!O*a4i{o<6tTAo)3_|0a{P8#W!z~oM<(HCbIFU$T zyGpsuo@u&ma}dCPzRjf8O8D_%w)5l#`qT2d%kPaz&NuD7AFhY@`37YYt{&mkQo7NPg82(S3UwBSb&5NhF7_>h|w&(j18tE-Lc4FFFb zObIvO^)Gt)_uvOkR0=m0&(V}0oD51lOpL+s&S=`N55Y%WPtT{mozc|Xso3OL6vaUO zkH}T4+lS#g(SiYZ#>M==EAdi&e*i*7)B!E#Vlj<<-S&x2s}8x+(c|qO4x>8D)iFqj zYh)vj!`75%KmlPQ5CwyvE2vve1L?TY?3OYnCvr(j|Lo|8*{^rTn%^fy5UDk`#1>5J zaCxXOWKrj^GJB_N9Ct?&BybxnGs3M}2!RsN5DQM0PWPhgLHE6I?E7-P1@L;PiZ2lK zZl+YT)0BpSk~o7%mwKzaj{|H6W|j4W@fp~If!XNuGzBD=sKQy6Zq1(7PQh*rT7m%F z2ljT)U=isnXjb=Ap@n8#$7ZC^((BXsYiQCBey5vxv*B`>-XH)DqJ+?@vrhowmcRPl zv8}K6K&*mCbg4@N4x{@U?E6pHy9U`lTDBXL;UXi#oS()C1e_~MN@Cua6ds>8-%?T; zx49z;lq&`J85U8pK1|1%U}iBnBMD4LOyqbY3kJJ5466_W)b?!-qr9JtPL6FoTc-nR zim?MbU9)aupTE&r@f_d-x)=pjLC%$nn!8%{J4p5$*F00__v600LvM+>F#@Y1ym3a_ zmV6$MxmCNr1&{g-nCn|pYNKFy4ah8~R2aCxrB#~VIQfEx)MhhZDdXEg* ziFJ0YIj0!R{e|Dv_w{M5Ams{`J1>luRovsslYwKHG80(rjv8dNjyZV}X&Hp@u%`59 z11N5vcC&mt9yf?P_k5l7V8lk5jyVM3BM(HK9R-30=OuNZ}FzO$_r|JFq`qC ziiDwub*S2AGG#T2Od^tD5dcD~=cX{-l{PaB_d`qWyBQ3|68C#WdB$2#JX!ZYwnB>c zj8k-X==hy#Jj5jVyg*i~~t&elVef)IVS5jI7HOSv2@OVHNsZKNge!rQD(y5a{bSs~RH!&=@ z(%!&wtxtp?)Xb6WOv2;wV~+;gVVd}NAsY$k`fyg=uFIuEOGea=Zor%EgL|R;$d1r=ug6L zlYSIzx-0xZgio7mep2YZyQO{aY z(qMUZVX#^thvNUv!>|bdDAN?MKuKT=JV2ty;p1pqf7*&uatQ(2Hyv^5(~!Aa;3FYn z@PRbiKA7^&b^Q_#b((3vZ=teAaaT1%VxIHEoK8p~a+#uWynk)|QaII3vPkN6e5Lzz zcaAU3v0YOg-B6%lf&a{$jwOB4vopX>e@MC%!#s|(wk{0 zmZZ`FJiWMndjd4oYQp}*j>J=^=aj-kJkEGa86bCJa^h$=>)@Evx!_cH!LQbS7`vNl z$@S}N`4?e<=7|`yEtpH;-?6NL-chzEab78Yj94x&7N(_Xmx2>Y4>RLd46o3sUVy2 z9qg|Y0RS>7d%J7#7oya$!zRb#mB1M^&Qw^BAX1%SGjOu-7ko%dG0?<-;o4|T^0|ck z7TbP=SK0Q8XL-C(0tHoaM;3%Ro=%F+H*V0nfx?6{5u!VjP=fC~#ryjYy_KK&U{t$R zQ=ZS9-2GRUUiftynO3TB5e)MxT=FWtMVv3@=xwfv!X!sk9N*e9o*(Y1LMZWWlbHfe zlk&o0$+qnxj^`8vr$bzjmEe*u+0Zz9O;3OqQ<-yfB}NRhMYg~cKgQ2`jKTDGd2^O{ zvw>a-{2_7S)IXngX&j#)w_w0SzW~w~|b5^j3q}$XUX5LybW{R~I zwR;;Yn52!ouqT(>pgI5T@Gco77@5FUN`!W_sNY;P0y`>4o+Jx$NgsG3GZ)lArRQu} zvSO|s@{nsWX=#lAs|4#e%3#%)x-Og8RNg{Pzx*X#+=n6t9gkvu@_EjzOtV;^|2B&| zYrq$u`|Z?Ek+G|{CQswMv@ATD!XByxCR#R?+f{M_D?MQwYJ~+dG*^k#a{|856e9K= zWT2`!@MB9yxnB%dp31w)wlDts95%7i{3!NbygIh@BbeKt-j(H8q(-@o4#$Hm)4Y&1 z_#EsY7wI14ywSi5qb|P+`JS1X>6R65k8l*R-`%(G=3(*z6<><6+IL760`V&Ei+?0q z_iuor%Ok4j<)elZsFnePiS5_KCLvd#_H-y@nYX{N;o-S*O-B3 z_$@_8^BmScPDLkh8{L-a(vSz6x9K|4N;{jk986elIaL7_Hnp~|itQ=#`0!=K6LAU; z>k~ux2S9)*N|kjojKMT!F^_2?r;@h5p#4;$g>Ls`?0XfoISS0Mja+UQ9Ws=y5h-<* zUZLFrGPgai+e9Wtl+4pv?q9z=;#MZYK;|}D)H?FpoX7J;uE5zGqvv=%YQg%95^-J8 z^L-^>tr>94FQ3A>4x636a02#@y!_J z!h8EeL-|M2++j!>gI48*ZkH2|iwlC66H>JxQJ1Wmu;4CJz)F0tcFtEzOB1%3Yo;~> zzS1hbU&9>HyMBfu;dbU^QqrB$ zEht^m(%ndRr*uk(4&8!ucXxMp=zGTB|NT1G1#{+{v-etit>?Lyx26=tX++*2z>Om0 zy?c46_t6E6TI&aA?R}M(76WAU%r`z z56Q3lfsm-?us8n z$<>$VT-aS4$|BSP(F+q1(XC$}TGpPbj=z|d`kfJQd^Xua6R<9Yhes<~UU(MwJNC%o zWDm-@uNJ<*e`CEaR;1thcyZgY%;GVRJ>)Eu;SquFUK-`#@pHVvQ63-m(a%0tzsxmV zFGREmWxGeB*xpCQnV-~-#Ex{+MBV+8A7DSh=%0(j|Y~s1@oM(wo9AM z8}XF5CoJE4c(>t+zW!4g8A2?wSW37(Q=+LNMO+UV!Z!ZTAr%klL&zfcVi$)Gt88Z- zyGLTJ$D09YEOFI4=Tc1DDDy)W6j+(1Zoahv)t%?NOzm%nP7Qnu;XMRB2RE%KTg4)^ zUTwN_;E^7kqsSnW*2%tN=%zHY8v%-Olb@v0s8r6)e<;|$;}|0JZ*|`XlRb`$er~=% zV9mm?05PepDHu47485bWXm1$AVm^0_1Z3?4)BF@{YA4=f&c~`ZCPP%Ptl7V=d01x* z53?EU;M5MiX=rOHMD==|P&xj-olCgd)w+4EL3RQJ^>yh~VIT8bg`;j-j zvTTQ5)N?$T!i(<!^#Rvy0{qm=8|2h;s zqExIwm>vv99~ihH7oL(u#S3J;UX=?Ly&>GKOKd+#lQ7;WgkFIu?UuSSc1&~?F2HEp-xhrgkjs7v;!@f&FOnpX1p^F@f!15qb}@U z`mPzeiyx;@vsGH-JL-Jfi$Q9NOS1O2*DF+aAuhyx6vj_O^)jPNVW}V^=XJ%}!0IP6 zm_}GG!#)(%(cKC60jk3Kgv4^zIl>|DoX;`5popTT65DDtkXho^;x$+u6 zrs&vBx7sEdyQ8Lj2rI9w#J{AD*3e{E91GzR^b*`DuWFWaP2R%adbldU?{+(!R$av> z_dIdGfJerN^13X%FmfVaW0mF+F&>!PL>Y!@J<>Z%5Q8fm>*djpkyw!HZ>7Z{|9bzM zGN@mxz+6M1qgcUEg6w7rW?^gOs2t4Iju(qAOcd2kx%uiUNDkYnW&l*eiLfudFVWTB zIC-d9b~CULX<8d_xC6ng#29Mk=$7&$0vmQ!Wwk6Xx>#+UneHND6>ey~-PlZ%NJtO$ zF8oOwtM8M_p?2TgTM2QWo%hGI-k7VF+0v*w@BSI?;uWwsSmmnE_L~b~EIK^K-Y0ua zpx4&7pec4ODb#nyU;mP7EWX*0w3{+(Fjc|h5q@;tUugap5Bd+fkA?&RVwm78Yeu0I zXxn-w62^yaD19-fSUfu)~v zw8g~1koWvJJ;6J>SAuVx!?MysoaZgG8+8FMz}^Grva@-tXj83X(|no_nq~|P?8%*T z^@q6W?P-)Y)Hlo#{SQe3v-B5|%xd}TM1$|ExeSP^J-7_M65%HW-sZ5JoW^M+&{illaXt1>p0?mX zETs{{NEbZTQ)K2Y4es6W0NXS`NI)`kNC%UooZp3qlC!73YnHu+S@qS>9gKEhw33#f z!iVz!U#lZZP2phY4s!A+UP;GVC!k5NzYJ`043G{cJ$Zylxa*(ZFlsza)rmDa3M2&&EqgpU)~LH9KU-b09fNeee@)F6par)HhnM zA55APE#(R&Ox3=ZqWAq6cJ|=Zi_ObE7o1LG5>AeJ|G>pEO=ttJh~^Ty7d}}Nt>z&_ zJDJ0%c$9aSKrv9T=Q+(glX_gj-S5X$4_E21J+Q6o5_)T?s~)QF)IDng*3tV?IxA8p zpe59G`H{9ez&RSr^tV$T{(yx@o~5c+*K?+t8E5r&_{V-b1BeP0LRri>T9B12zS`e7 zIv!N^y^Wp598=U$f4mYprF%mV}A>$g&e z6k%`()You+x`UGRX%gE{&w(0$Wg0H`{vewjvdS%Z*NgxSVmSoZuvV9-{sTeGX=#+w z)Ua@TyAW*89v+9sSK`ZY4-|i!A1YgJTW;)5+*)2@r^POBwob136}o`GEhB|(wHLf( zIe8WkX|8o8UwTC0Po2G4<$tyXcL2uPJ;JqwSxmjF3Cmj-f!mgry)#GohGQpqf&01H z1VD%Y^*W=2EVhVAiw#Gz;ZLK$C(GNNC%U2B*<*E-@1(|HdQ!rP@*2=PApPVkKX6DY z3rFiyf{*T+UF=Sr46^43ZcdgLPcCd*gaX0ZuGy$?%W4x(v{Q`=*BTIoclL7XX7xmo zm}~nstvtd`54LV|`75>xYxPxuche3;-y&jHaTijIE)Sq~-dNQI`NCzqy`1-Evv-~b zZKJUjvB$H$7{{lfGs3mTgTw!sueGSy7I7bp_3)MW zT%1tH^k`roy}tLvzCG!Jf2Y}5b6(;TKf{=_$bl2J$3>x|U<-eOm4|k1KX`+Wf|^3sZ9N4I;*Qfk$_gKnYke@zc@+G#XJ9%bRNBhk2giUh~E!m zEGbHODfC&0O#w~IUL7vrS1i#+==uW|976zw>3lsu-;rIEl79U&8*HTV2Y_wmU)+8ojFzI z7$C~uI`w_88$~Sv8*Y{WPA#29(&Qd-I7`57kkx4RFI-V;3@!DyDKsP4=m`*&iQvky zxUmohKS`f?F$dvf^bn8u7-CV=xN1JRC~r@V^#Xgtmzgr&ydhDrI}3@eqey>L^qb$$ zO@0hz5|j$etm*nnbCyS4H5rVB^2*6`la7(1#0`jdM)%A0+Y7InLwoOg9|fGxmKw$Y z6YU7O^SBt)O?%@LYNk&1QpZp0;PsiEL>V@LATfIA;ojY+KTt{NQ4J&7gb?08<^Nd# zpX2v2Z$K+DN+)VG8}IzdFScbK_TO|5MeKfj@O;<#nuS#3FcaGoMoLv#ZH0)LlWB_~ zcYY+HqS5tau?f(baLdK4svlHkEH_5in)>EpeXMOA=Hg5lPS%UAGjp9OWO+O<>epBB zQu}5q`yfgc_yi{G3AsxW*67|4+v3~f?0yuume>TknQqstHx!-}0>3jqzpfSt5$ASQSh2tFgxIrq6*xDe5Vnw2tQ@m zyniEaXW6tZ-BFb{Wv6KYb+wQ5RarY;vYFvchFy%?thKKIo?oT@(0BnX>h8rFb7f+q zF~CMBa{!nSU94s+9L7ZcwhH{Y-&*m5g*W*UeZWPbO@3izj$Oy9)jAkI%DIB8EVv;B z2m)ewH&J9iWB~i3l?qe7!?eGjO`&D;pcNoqNT(1odmTGmq?oY`xR!Q+&g>Zy76lWz zg*xF}|7Tp@4;oqd3ZG(nL!0wmCTmr=v zy;?uI7RWZWv8nVv=j0OQbw7y1%B!jxZF@W{x7{Dls{;DuIH?thG6c%BG&Ux#XRFL} z`NMxF{Nd)p`9s*0hNx^O@+H|b*j$e_RVB4wlN(WB%UscAwWbtkI!uEW%N)qU4Qf22 zb$&Vq3+#R~Hlh0NneTzqBW)_a;+ZW&tEUG@!#?WA0zyyVX4v*UQ-kVZGxY7CZXlo` z(gO4^)w7W&MQ%!!o18}Z`dm(zwZeEN+Ty>;*Szh-8mJjk0Z#&wtjRcr@{O~TDBE++ zF+bt{I85rhjo+b>ZlhdN-Gb+dv`YDGsNFRzMGxnvsjLR4o;QayK~<~#UFfff(47w^ zX-;Vpb1;Mb5n2po%^$O^;5;u>9;k+{1xT&F)zhxK9I0o&zZdCOW%1y&9}z_)m5L(5 z9*gze{z)n{U*rOg#iWqwqqU}Hv(K|WS4p$bBMPIccD2w`@1Ss5fb*`?Px|RFg>;es zgD`5My3r7;G59t?8os`Oa;b!4?u*rYXCU9s5j%{B%p8O_fX@X5*=>Icf_otG=`4~> z@1(|f0~=5!1pd=e~A+^EBCVw9TOz z91XtxnWs_S+N>3g2YY6{>-nk-Fl1E$-AL^KGVrowBq1u_dNA((^}%_A^}?pU#}0n@ zZ&Kf@5gyY9!EIr?Rlx@P4e^Wmi{%GxvPT}f)nfuyeI}F6Q%{-8Zd_$&pv#Ld>_X&r2aGQI~to-<2lC9KpKn`f!-)$Y7Yb7 z2UB8B|EFBB!1p4i`5FRB$A%G<8J)=s1pM}gQe< zQFX_vVkYl9{XcPFiNdUlWi2UEUi;j_V2`b?5kaxk;AN_~t;)!zcZ0RtME-BG-JB?? z`3+D|l|hriWGHde?~$=VwKcuQq3&SrFr-Ppq=RFFuP-ilU1+S0{((qx(h!F&sXl%| zEWSr7tVu8p3Jy6*5&{ufE+vjXn`Y!{vrGB5Imka6Aq*)YH0&c52jRSg2Z&3b0|AL| zVnD1Lu>0b61u~`HTgt*+aO7k#z{4JQ&He>!FzgooB~RA}TV`X~%t4`ou107<$nj#i zpxa2+b{R+SUtq#NSoh*(K2O)lmz^g~d)c^<9;r%!RSr$|Z(YLY0lHwrFv{4six2sv zdh)3(E}>+OVSv1=!4R_v*Ft%c2=8Z6Y^t!+u0m(5vkaMa39gIzIUMo$t-=iZxN&YL zOym#i2lhVEaQ~q~Szn~SP+|`A{ugS(+u{-wd%JfiS%Bt@6!4hMcm0ZX&rTdjq#N>4 zYQeo;O^oFHn&2!H?Pyw1tt!=G5#Z;suSivz9i~8055k~93eyzgmBE4ZIPIcz(rYE8 zhPs?(KOUG1atj(29WC4=Vy}}s`A&Z;2vSKFq;kC$h4f1xJg%3M?}zr!U@L!1t@v_ScA?V)qPNo*+k!;l2&SmR1IzU^U=%QaCp<3~V3ge%JK} z4;}W0xu{&rJ_iB8XxmU*XHBAwMb%WC4Iixmz}@+-Y$>eQgp^<$7||QBXYu>l_f5isT%KyJH0-(T|DF zz=7WXfdw8%Ci7AB2gwg<8(O6w*VLRy$nJgXh}cdhq9CN>KFqpgh`3IZ8CzFww4){E z4^cv+wLd;p<_+06eO#`2%12VZn{jfez%{;hxz$9RG4ss z@2ENC(bKiMeyS{@aGpxaEq$t~N<%4J1xeG98?X3ztKmzUN9_ft5+L#0om|tbmfP-p zrR0m+5^R+s%tAgYb3Tt_TcyjUqHv)nO-Vi`^8pb^!>zJS^(YXE$0@wqnxK7^XCwEm z|FRQM82I_vEKb{X6`*ha>`*-N#LBP=*fnngYl#w&8<7*Efc7wj0B|=)KianSRlVwT5vxchGAnfboFCzO1&=*Byko;Da4i* zP3`YRf_cKa=f3^sPtK11#sZmKQs#fBs4?~q z_~^qaa#W$u?I+PjX}~du2e;92us5V}8!R()eo%lr2RIua{X&Dp1T*sX z*{(PCGL-_TVe?f#d_MdL-<w`^=`ItD|xW^rpiHJSjT(`!$H#6>{18* zz=x|tH~*>dAMjZiYOz9pzLB!KhgGyq9@Nk(h5mNtFzuBXmr$X2|67U;qe6B|D?bCK z^WDc13c8j<)a@BRMfcbW+#1!Y8J5{XNJ}o#uh0^knmnRZm!vNmv zv&AO)?ZzT>`w2x)g*Ayx?=y0CGuJ>axvbVA5_q(s|fk%2gDm1 zEDC}-bj%XEA;gOKnh**cT33}%VX$-ibbg5N)Po3K_NYgLd{3`A<6_xAQhJPslI?t+ zbN+i%EKTX7jzo&C1Q_(Nrn9{Be9Gox9;9KG@xQivoLAcU$Rl+OTR4p2^tHJIM2)7J zQU0M7ud_CY2g`8k*SC@CaC+}e))`aYM6xEN8U2*n2b17?j^bIez>#ykXSd#G^XUIr z?Aq(@#KpyRQK#M?WN9En?r0o=4`;0RZN#+jsEB@Z-m;v39D`Uws+n6p)AdcJ#IrR_ z;P(7HqU?u1CDhWWuvXGN{QS+lAyz$Kc%-jIMdP?{Ozb=7oT(gFUZTl$af#SU<_5U} zlgB@`S{+hgQqhx=$I%4!I@jA8PCdtwZ}>@DTy^cy0$j(4xxb4k_TSiV zr@OqxJY9t+YR*FSqcUELG1`bwx^P&;I^ z(!S(A!C zb+OH=0rXl^mDUkmm&6F?3dBjCzf&OAX7fX3xDGv8)=hmSuI5lCl}^=DlZD^X%X+5t zj!j6_H}WkRTrDEl+qaANHkXaa^jDt~iH!i{>8HO=gH6uU4Vug8O2Nyqrdx;DWFPr- zZ%02pEcWFvJAAKbs#k!RzNYWS7XxCNv`~HGlW)xgH)EDx($GH~%-39$xvxt=CNM?Y zbR(vDQOdC`O%0XladtJbtar6Hi|fE@?OnZ5Fk(}`pSy$k#rbXoB_6EDz#K>#Pq(uj zMl5o>$7qIInoCMIv2Sku8V0!-$K0aAC+qZ`>%F_;6zS4r+)CbNI1Nft`acHMFn608 z6J8QrhWgb9*K26vI0%ZAX6LD_jz$IoFf<2yR*TZdTv`@Rr~1C7USkFPT8>-=;eo0N z>}1rNvXSk%D>l8cj~)6>>K;Yw7mVlh_lz-^Qk+8Rbko7;IczM`l^1y|n+bX~vayyn z?WvibiVeyjv z!q0n@H&&@XUJ?fI)*cV*0V3Y3MGJJ| zT~EwTplGck$4|jdCa*;63p*uF*`Hz7Kv8Lo;3s1u7UgGsmQE>F45RekSF`K=dCG9P2C_)Q>b|S^ytOxNCUwj0vPo_I+R{=(T})!unX7{A`?N zzWK)R`w3)93Umjymt{7eR84TI!Oi?)-nRM({^OJzW3CG)Qk`g-q&s^3+_`NQa>|PJ4D|l zr3n^>o3MwYMMS#|J7feTsyB~gAA)u0DYMNJLzu*_oTeQ)l@Jo1RTth5gkM^bJ)n4I z?dzK6Tk6uG*7omTItUu)5rGG2uNL(>E=HMLna_M{2zTM^Bvzk)VE{t^%u+LB)pgU zxb&_8TJj_qOPEfvo|1jxz*wO?KYLwW{4(MV79t=~#3H=x9G28-8qX6g6fjf!#0@cO zO4}ZKPdlAfef8e8_6#BiDV|b{QHz{nx8r!Pp?@y zmLaLaJ&Db+)A#Qem{|V*J7-dc<@*How!Z5BzByW46l!&hed@Dvv6X4ywvGL|f$onC z@!zE>o5-=Al6W}RO{R^Q;uI=VxR&8}Ao-zV8aBmQseG+{wX;c-k|AgCy6L^czC~%< zz;&6=z$8bwGHpR~A~_djpG8=GPLcH0`yEa_U>z_CPdRUklV{>y`<8Vu4zG>JScD%O z%cWHD-vHGUL2adPRje@{W;gnC1;$o$bv{-D@_!Q;gS_-U3}|V4LOF}Z>bE%Vi^$p! zb#@-DBxMCyj9B?!@ta5&Ybph*Z~eS1NAw$<%l{&>uxdxely!;=Z~!G^M=?L&?hgG< z7u&VLES}n|qpr7`-hQ)3V>ok#6AFf(u_H&IxHWWtM)P;Xu;pmDr6^e1M?@QLw`c}* z=(i$K>_4*H_aMkDukBL*^-SN0`?UvsZ*l#D|6=okXtn<*x|$vS+o-&9;n*fb8c;ZT z4Kp$#V`*FU>xay(M&9lD)(_X+%^iQL``1Mgl|t#3S*Gl!t~QWRKFNyIKQd{HOkm@+nd$}sB z-MI%787qY|!pcwCSoa1tKPuUv?vP|XMbgKlS9yHT6jhq4Ll+9U*zgb(Dx#h)xAa86 zLbVkJf5`#Bk;4TJr7``uUAHP3g$g*I>mv-eV+V1_>)s_ZC&UW=!JTCoo|55l{38*T z97td6XdNc?+ClV3ra1cJ2hIhQHar~~O78C-7T2&z=0tW`_P;38c&H28!s}p!X3ec0 z)^rad4^gXsJ27Ea|GxUc4b-dHm<5$nt02VvTwekaE>Wj&`z_<64`odh>fG#AQxgfCEKOcTnZaa(RJR7F_vLg^G%#8t_oxFyZIO6 zs%sPN?17IIa9Ya}#0rBN6v7^5@s9=`AMC!!!@>41|BtpRDhV|^PK@Xn@U=tC{;S7- zc%OS*v@DO^{Y_D7pJtiYEd5^>3O`(Q-FQC9>r{IlMaWGvrK)IsgF%magdJLFCqaN{ z8dsAjII5^F;ibZZgxo{h-c!@R_YJGXE{mXK1RZroXC=je?6*f&@EQV3Yx~n z=0q43d3;TC4d_dofQtJoZ@Jr#k)|<>wn5p{R6~-{!U{W|s~0SRh67E6D4PvS5c$)n z)d$Dh%S!hK$u|c1u%h9!joTwYkw3vMf0^XlwNWJg@83OPEhH3>FQw+TXH-*WD77&D zC}|C%iKdEvKoJ2|w6wNFVQzfJOJj2GNPJyPOFKIy=gd*-jM}1UV30aXg}x*sgOMTdjSQDsPdb0@kbpr#|8yDDKO1S~>eEqcj`^QK8w0|FuYwrpC+k zokcF-NF}yNEX6vmw7lHO>-IR_V&v<(=*#t2b)GpAI>=AcfDrF&A7k!N$4p%Mvr@Vo zXQ5oR>UH?G%?km3yi$12Z7c3}{h*iZ^|@-jquEESwPNxQYFqry z`|H3In05Ueku{fVg8?z~YW+4{X_57X?IFEW2eCe&KDcQxCsvIpk*kHG{<~6w+_*6N zU^Uy!Nb7rA)OdOBM$H8Ucx=yX)VVm*vkbI;-A73%Zo*F%>smqgq+#GQBUWo8biu6K zIGzQN(QGS~Gq|i$1g@eubmweWmI1fi1_7(~HemW7o9p#@0P=hTpy9*_KK96_Qs&)2 zKyJdJGAepRb?-vRGY zR389o_3FUoaCu?IpT2 zGk|VO)baxgxQ!R>t;ugS_y^NCEfZSKyU==$d;zE4wj|y#x+nyfP6=;Qzs9pJ?q(WL z0;>Vl1HmscshGwmi!N0ybiPRRKsmce=b-J&?1-$@mN`zX+QC9iDD_`reY5Ej)zh6+ zgB12JU#xkXg|4($KDn@WT98_=W?Pmf!& zKYMqy$e~|{w0U0PcD8!LeScsZM!wmjV@8ts3yHT?8;*i|FKHmKebwvPT5#z&oLJc> za_l#ZKaYH#RbdV=z9MUUOt~+Xj8EKIv}hP~e6`Bs2>?mALt=?GVzYKQrk7e)j*QfF zNvoxK25t_f=9n)zUe&ya20}_nEw>BwR|IJls<(DHG!119+jA@p4gD3ThyIH_J@0{S z64>ZUAjVdo(g=_j-jQQiX1V=&s50pAJql-cN2qV$DWD{K{yL@9m8Y)WmKGJtIyl;r^$2&~c z=OA2Vybr4<16tP}9z|DJR~(?eC{nt5V^-Lf@tPU08w5dDl&pKenJrHyu(QBv{$FhW z{2B=N3fJ;KtWiMXZ$zh+MQ)PZr>$P@CexZj=z&Ixm`K~oidW5vNqPo@T~Pt}?CS%k zYhle6*`*Yv+RZWE*K>d5WtEOkqehvGZcTafa4}?@FB$)dVCeDS zD!q}Dc7m+)6lxr`1N?9}vS|@24dCUA)WXADzRZS$O`Sy#y}8gK!o$@fTX_&2pE?7q zP??3&Q9LgHXlr>vh7Sa!6I2rIlSl_*lOWn`mhL`{p*S>n17e+Rw>~wbsmu8hmPcma ztaOH_&cM+75sS9(*49&8&h4N2`~qNMTP_yzlt`wwu$I4e9Bm;JBuSDJ(_1uc>+B5) zK8H~kGR<}~BsLohIL;4-bReP;G&VfPcOqU4rLf?W#Tm;6C^}86IiMez?tP4cX|pQg zFj?qN0TrTGJ(!`4<+2#ju*aGQ=O+E;Q26>g9#&`!3*J6izVP?v2OxFJCdq*)?tuL> z*=d{^(^=W&ih1-ri=sg@zjy0e{`_&%sy4^fa}rlKXCzCSQn@l0UDFLcMhf zs&~a$rt$U5(GN;5gB}X@$^scg+jT}AVg2vpmOrv+e=1S7i5x5wR@{RkJh0(R`FB?H z`E_E_h;c%`tzX#F|)?|;fYG-HjK4FY@3Ju4l_ z3Oo{ff<2CxLH)1%2?PtYl8w9YBY~1l54Vj(Pj^d2*F=Zs3{`ws94QFG0W4!Sgh5Im z;kvZ@UCVWzIA>|fsQOE$6-Q_0&;&>z2S66{%t;Den?n@-3Oqf@K9ZIHQSsH^LBTKM z_cMn;jxwc<|4)w<20Rdo(0oVbVTtPH`nq(0!r=$b1@fu4H~`%De<+JVBH-)TZCe!- z8yM~s;@SEBy9h$)McA}NF$14Q<*|$X_bHU<53y+;#u!Tt9ldM)edcLy8!!7mRR1;! z>=T7&6*jY&&%@>TiPtL4{JdELkgKaR16vbtS)>=A#He+k*Oq$-6l|&kbf{9NHp?Q{ za5@0z+4kL}KilJwb$2P#9!b6fkdHuxZ2!$@7>3q&$lfMiqtJuxR+?v4+}33jfz%<=?zE=_I^U0}!h_Q!#%jQe72$FhZNf$oEMk36E6`KgLp1Oc3N5#AJ z^iATsKuYKez;D~0&JzE+0GflMmd%K_CM4PURM!B_U=)A`ztz7yUZ`0Cdd>TQ_=U#d zZtZJF%k84o3Lt}724KF+gze{uck6*zgD!%JStc=}m`i+D;);_X3ktZ0M}C{Diqa@u z;1Nj)5L6HxzH{o%5Qb8G1y(OGFJ=KU^jicGz`Qos&#GM3zC!3m|$pIPU%7xBI}< z0sK`Q)$Gt1-?P=WDxl>Lr5r$FV)`)SYpO=4`K|T~Yi6tbox-v?i1BE3@$9`}^PwN$k?>|I56_lEE@Q%?d+}m1sqV9Se-aK#_;3EW0 z!(x2a;H%OSzzR<%zXUw)ZbPZ8sfvyO42d!T%N!t%|D zp!~$4x!ICEz(lwQbw)Q=`CcUk^ULU(6j0PHe#j983i3DzMZOU4y%=aEYLvGRUQMi-yG(~86mQXw6bJ&i4yHV z7ahVu!EVs(T0}!9_eEs8nmwneNEtAv8tkcl1&Cicp2*y;CZpZ1yH_7_|%RR7XEJ2i9q?`i5O1D z&gO&JE-rAFW%93&W5Fu6DCaWU_M&>WGdx~sk)m(cEO#4QSgjE&YP(U`f=8M;?Tohi z-MX$MV?5DlCRwLm{$Nhk#uYyJr$Vv$(WE^5+=Cbtg7X*JnzMTnP2F6zn!qkakBHz- zhsYYSsq|#@Yd~bYOu>ia!5JfMEt-dFOHp8-98wr9tf_|?ElkSup_}$Ul$bqQ5+qa~ z@jTT4|HO-RXU@#*CwTqe-n}I1nyw)xR2ji1{)>BV2SrBB7zGa+AG$X6k|rG=4(IaK zf4$=RFU-h5L!Ne+!oS@dI;DZ$2e@GQj%aP`JevrscR@w}?ehsh{jY61qhtqZ*GY}6 z`Kq*Ir;Z2XUmZdn8MqNM_0|Se&!~I3zqn$&!om%5vlQk zqCEt`W80C@02!faT1)U(q#DFXgKW7{9Xjs;SVzez^wa`L!+e0sXmn;~rb)levk5pn zbG8sjNsWXku}=~_Ln!Vu)B27kvaF;wsyF@@=Hp3&nTh$VRL2kc1STJAR^djvvtle}v;jmRvG zLAiY7y62O{Zreu>TmGy4CbJYZhfv2)O4(J$g6n2NC`a15={h>J8yiQ zk;5uYVg8%8r`re~cc{q9A08Q68{t)o6L33qZgD#!7-!Hy4G9Cn2HzyAoVKnA0FALd znEZ~jwynJ7;Ag(sixWr0FD`EK%fCS`Y;l~|?FBy&Ed^6u`t`7kba9cYbS0X@rBu5o zOEjUFCmz~JEQ>SFo3GY}U3{)8p+NDi$Hh|Ennn@%J>z)0k5^wG^jN1(_b>Hm2@P6I zI25OH-vdq8j0yfj838!=zcS0)1Bt=8lwQa0?x4sKsB{$I(Juh2TxnCIO83BQBz=c~ zMRVVE`Xuh)p;WCnqAHZqcb?gxeOU;IcM#fG78y?VFhhqC(t zsJ@HS-e|Hv6sWHoib-x#MjYS`_z;!>;H-(>U*I_5MlBTu>Ix!D#v@kv@dt|7X@PQ# z0O4B`G%*L%fh}rEe3FAE=kEY{5;VzZ_}ugA4gjtG*_5 zdP5$^?VU`1|DMv~>_e$&5}KVUig{#LXbv$fry%SAA+_&^vCQVOnr%FoDh9rYK$e_V zF@3ui7{L*U?A&oV_}2F3aK?5#S1dmj7T@Tv|4THIGLGd}AkyW20kL7VeM#qc!w-%- z7o1s|Ww?h@@lTb%J|+rWbmN*b{(+KXtJ@#T@SXtxN+S?{mZNjn^igih%Bvh%Th9Xu z->J;E`?SQxd;S6)v-)13H2@QX;{C~>j2tKhtBLkcsK|33K!|Cl(?7;~@57ksx)|+oP``^A47teD!q_s@p~m0^fQC+?I3=DHceUk~j=NQ_71LBb z+CV%rY9iieC?F-(z_%$*_>IqtT~(_0ybLiq8&Q&q2ofa_s&F2Fy*=V&dIJibH|+OA z)UEn6G4oj@FlNcFLk|h9ILkHNYN@_*ns|84`%2MmE1IVn0Ohm*;4PrpH$~8Yvesa5 zz4UKEC?Nppc8Y7Pf*McpA_yGxyOt#g&Peeao7gD$6v{R60U|U(HOY(ngnN423Ey7k z)+qlG)5Q-S>v<~D!*hK)rOf?N!7GJ}PsAwa;Pp-l&;2~{IGl~Y&L@jAF30n#EMT^x zERp98tVr{iy%#_U9@h7=Qr>=qghhM|O)hx$>i9nI0sQ`tiqA4WkGCgB!q0hFEtbU! ziSC1qz!P3UJZlscbQt{oN1nA!-Q*(bD*)kdg2J;PZhi{GRQ5iQocLdfA=4c%Cw48s zyEg4|uqe5CUZ2*Kt7NU8!EPF{Z{p_{`UfB|tiIdlvpkfISwO#~6}|N)qF3nvKu(QN zwvo%(s-AUEi#be}pFd|-czo&C_#Yy`X0DWu0SV(Xy4vz0nfz!p@bOk5U;ixbb!RrRJvbxX$FurrBcYJy~} zmUs2&IMuAB>1sD3wGF5w@<9ZDso&`Q?P$A#Ni5*Kb(b&Bbiyn~7wTmz9Rd)AnBI*Jv!D8%1#7$xWxAad?+9sDp6Qa0u17tB&X5VhZ+5on`krElhc(| z+qzmFjujwi{KcI4xKh(%6v*^SrG(xY&pkBeT`HHi!&rgg&s7H8vrb^D4a;DrGaj%I z1CS0oyO1QKlCjo+S0<7@v`F%bz!o?k?rJ{3NbTGs2T-+&_NZ^mN5R#H-K zTl-K7$C9I|;@EPnnxgT7?w|WwOh0KRAf`jFPa=jLNho@u$tP7Yh%GomV-NXMSX zH|v8^6ISbN8Py1?vzk+bltU?jP5>2bg2{2l*`)JEk)p!xXFtl6g>Ka6YAw|+ zUfUC4vBL7cMn1d7Lest7mZZ4deK5>g0}h)II%Nt$(44MQG!qql;ER)u8UY3qljaXi zsH? z=B>v+FNz&*f3N2L#+}eMH+)bag-RV2%C8F-s8Fr3293}BC zp8+3blQ|1K9oI-=tId zQ}&>@x8DL$L=x@y_wii(oQDxfx`)RS1g#zB>K^_seJ+Rh{!9Xl$Av2c-HE zeF>6HvAmyyhRzbs_+5_Q$YnZL80#Y%|4s9;#H{cE>iscO8=5Qh;<^*ZdQZ=?0ke^|j*07X^B`%8fBg`G(I271Nrk8kMB}xUn{aD{z zJ9hm>NSt{^f{4Q2-p{8^>=FV$;;S?QW%JUH=&(q{a)b@XF((4LQ)Y^4{3`q0NuBFx zSf82P26s8@&;Inl(NY{`{c02OlkFpsdVq-LqPvR)B365>%aS}aVZCM9vMt`XCKOJ0mskIE7)qcz`onh^QphFCxR%3FVg>@CL+XdE6Xz(Vz(; zY9^Mu#g{pkEg$OFv@9R0HdDmd%%&u(N0f;!jKMDxJ~vnP%(+Qi>pT8jw*BlJt+rGB zz=`$zJP>G}3`@fFCjkx@^=VB(hF<++*&uSZcJ}3@W8>lLKu-6AO$5`ZP1ArbnrgA5 zLXD&Y>BCWAI*`A zJqd=BLzt^+Tn>DjlCO9v885Z*S=Kl>l=&h*$<(WBGn8kx77&##VY8)r?biO#+E1Gz zI7saN)Ez$$`K8L^^+t@3`lZs(5%R3eIU`v3k)L_t*0`4C8VPbY4rRSOcAcsjAz??FZHSG^}pI4y(ra%d?1qQEEN@U z|FGOx`fY8*zX_S3U_XXydUXiXC{r(!l}dTcztbN!$~W;?7@DPg6Xm(((}mfJPtaV2 z4Iu*fjy4ps+y5Cqy&TnPH|EPpFnylrTBuHS6VuAxl z6NRO=pEj*Y8wpk+gTt?5D=%@(Kh#b$l^!s52oa?Pt90E1aY;5hnMF%ZbLM-w=9bmJ z8Ur?hH47u0bQz^WUNOl8*;b#wz{~4ovhwDSbI{UfgeWS3nbrcak1E1(q-n%ve6sR^ zrwxW=Hp{Bk0tjD|LC-|%Nu6r{QxaHZ0Oeb-{s4vwcF)mv_95iMccwVyuUSkp?7Ji) zHdc!vvBG;Kn4b(Js0gvy(1eh+g+4}T^}9~g?X9iQ!alk0BX(5w-Vb~?MwYMF~`ChCY@Jm&F{rjg1aO61Pd=xEn@Q;NqLo zB%P^i#b{(pRoXZC5$uf0@o<62%!%c*L&d5T&HJdu50@5;G3#z;sD+Xj>ijLxnw;xv z;B^&Ps*|bQ?$e6sPWmgBypYai6(kFkcDA)^%oLk!7a%>*)UM*B-e!qFl}U%&&S5Bp z758mBkVI4|W$`DN1Lr|o07SI;`V=UGZPUQcIRN#rtubhdZYn8U@o2Nu(6m_hr3Jt| zpfT_KoYw@J-N1CAv~q)v_5w1KPl$|&A%Egxmd91FE^SvJGj(6|=&5%u#IgZoD6dH; zb>0qG0vM^bwWEsgilDteat2)ys1oVbn2kjABN(t1cyE74?@8l5H}Wd*xZ15!Es(n4 zB=~qGF!9GZ2(D@Nm|MfkbIB#5T@+;4jJ0Pe zybC8_n#OF^_tr1Az@!cr;B?y z5*T1;6vScZkVd3aLO>Kr1*8!WkOl!ky1SI_4oL|Kr8|ahc+dF1_r70T%Qei*ujZV+ zpW5@_AlZ(g44_fm&Q(w24MX4Z7!>HK1vRbmHxPPCc6NKUKfv;7_Y;|#s1%G^;uewh z;mwX+Wv%b~1YTph%6D78p7Q6EI`bUI9f56d?=m9A&3x_KxW*|6N(t^ ztBKxXoPhyXvl+#>y=jVlB?e9XNNsdwx#;P%g;j-=mqsr}zYj>swS4(4g!n$MT;D-!RnTJ8*uMr$L^ zX5UIwtPqPXpdfRUKMl~Z1qtrW)|4fSfA|4mEH;>1?TikVq!#x${?srVV)v)GPOo_` ztJ-1M6j=2#6+XmmxwA^`S*_twUOum zN(xG;o6!!Ky=Q)%PQALg*Ng<0E5^VCMx?Amb3|`*rELkmTRm<$iaHDok_syqAfgrn zyZ8tRU1t_QZQ1HCl9j5$ks{g(QDzuM`GZ9W<mLgYT zTI5O`_)aYx&gV}a`g;3EiIZpq2D$CGde=G7<8$u0k{=f9S9Kc5hb+f*TitdLOMFW9 zyuH3C1(JhyGR#Qxr9#v$W$KlQR&f^$Z6`MzoX#v2&CJ^T2~z7PC<#6+a=c0ShgB#{ zEm(8skmqp^tf~~6K^pB-!PTnyRw+0Pk0uIRAMsq+W?Hg;qUnvzGnOo6C^dH101K;G1_4@6t!Gfm!wnv2X#>%m<|LQq97jQSr&PRk(FYy3bC4eXo)h z)C0fgo1ZnfsO&(9?nWr=y-v3@b>7T=cLnAwC$NH&a+7sk@3)?ae;9iGzOO6c6RAvJ zB^4jDo-6ubKVEAp6k8Hh@Jfgzu+X4iuiF(`HrKldQ(1K8DD2=G8 z!`;mpzW6$+32^&4u2(gU0-Lesc`S$emo1xFo4RICZ?D}y$kV__os}6bJuODe<<0egsLn{VZ$B#q&{USb9$ zFem14KO!KQj<@qW9GDQ^1K2X8C>7nUcAisxyU#r6gTKjlg8$vyC_Uc&u!}axv5;19 zU(k_vByo^SbL^%D$>pWaNKfm|i>k7r7kuPTuA1ohwb)L;FQ*QP(PVzqP=x%oPV>?s z>Tu-xCi5O*(OerO?iNRv_vd#cdphKC_?}_fPSmS5|;%sF%rE~!~Z z`&&z?w^@XM;nW7Fy^d8{?7%NLpCDwh#vlPzC}2WhT}rw&bE|Yf4VD{W zgj}yhq&U0nLVIR#y)zdh^$m}o#`oP(V~ta)^N)=qzAMx8t2xrxzdHWkEPzENhSFa6 z+dY`(MoOZ>z2!=Y{kbEfeRzeJ_r~GTQ4Wlf>9y8Wsg@xn%dfWpc*rzT0MI9|6TkU^ z|H)5ttS)?Bz29_+G7M<2HiDw$dZ!KhbHrjLNxI2Gx3qsF^mP<*q!ehPS6`!id2HGc zhDkgL`9OKEnoux+?ZXbn8xeN^TuFwKn9)^~bf5FskJ9b=9II1S8c`EN9r_q^m7^0M zx8)k3N^Eqfw4f=#vSEAFTPEF|wE=2=V!uH#B&ZP@eL`p$$yDSR9Thr`wPmTfVyDRY>vEI<>WkE~pa-^?8As>FwJk!mZ_ zL}GmH_7Y9C9*bvB*;_wwzdH6H=8>YX-Qhf?Vk_ORxjMKNzqJViY$C$+qun=B9anpodfDq!PzEe4O^TCU`)`>_c6->fB&DW zshHvSwK3zcDQ(1z_B2D;B|pVDP+gC(6xlIf!O zk|irHq1yMXKrg!d1}B>g^#uA`^eK^*)<)l=bzKn-8k;J4AwoHm+s<{otESlLeiL() zEzYMYuJcEDun@Y!DjCPVnkcUdmBafq!B9y_o{!IU*^E84c=8T}Ft{-N78)X5MvHV; z5c^upRDUh?pH1fsV(7oi;Er%aV_1Z;m~z^7xZhY;jd%%z=JY>li!_N@MVQkHAyG58 z6wfgpCrlY%0AF00cCt*zp>)Ql`oq88P7n{3yun*gsho@bGv_haq}l4d8sWX%L&SI3 zzb-Tz%}=ndLQB)^%OR*{a|pL}xhJrjJpa6V`>$22#F?gxhx%7s=Z)Gy-3wK>;do6AA;)r_>XNfvWZ7)`FfljS{!#Sy!N9od^BJ zoTdW(DyW|s?`V2s$eV}r8l%`cWDXD6;Ptfz|g zg`S#%?n-a<+GPopR7OQQC3B$^+#it>6OBOQ{E^ZfjKAPc3nHYgpMN*|(~<4j;`C2uHc<9n9^Vme!#f-*ZZm@E({7Luaf{f*JoH<6%gPexveQ6 z3W$!iJ{Ptf>*O)0?ga_v>Xn2j;bp}I>p;`OCxQ7J2&)y6vSWQfS^Z38iGs09lPkv7 zhf6srK*s9JH``sa!~1Afvf!}+UX;+4S5I?gPtovS^~JC)G5iI*lif^p0Ysd9Nsh{R zC5VWgOU?^qNdN+#fi!R+^&C0IV-u+0T|g*9PBQ-se+c5c-BdlO=jzg^uA%m4$Zb4s zlR21f34XOBbOuHt4*-#Ak0!g-4M-`c3~T^{8_z)SL@kJbsm)PN2(7eaMgW1#Cnuoy zX@+cp2y@U~!S(hu`yqO#>%)Ne(QSD5Cfee7W0a^aUCs}D@oFWC{&%PN4+v9YC2EK_ zxd;qth_1Q2y;2qjy3sX;e{&OB?tcd}Ip0rA==S-e{EF>#Xd3O`(b1gHs3h77A zj3qw-2@2eM#~{gLpb32Kzij^+z?NQHBYEVy7ahTM-QhG(C{g~Z-A(=zc)O+hT4Q{< zF}=Ig)%5R4b7(Ydw5e#=%uSvH)8S<^4y(eDc**v!g4l-J%WY%u%yfzMESc6iWHcOf z({}Z@fS2b$7xv*jn85Ri^bXNMJdd0Q6OBZUz3cBK0WlL_4TzUG1B036C^7X8CJJ?( z)4VSWW`WOl9+S$5>E!zF-Hqwlq~Y5TY3sjf*8QLz3d;%P1e5v<+R?3$nIR2eS?E7P z_Pg)?dcB{jcikR*-()T2^PTHHTHQy<#WhRb39+6nF5WZp2Ehtmq>eNKcBrVSQ#%$3 zzqGk{V_}3iS|5I&qSw>)xhpKJ87NHbXMWjc3Tt))?A#XWll;ScJ~L4tCo1nQ{gq&`RiI-y_sJAXEA+2%|4?o+bh#Kwct#oFn=v$^oX{MlK5je0aM8pNWs~vKe zFJK|d04Po7V6>s}A^6FVo;{w3g2eIjcL? zMAC}zdQ+lwMS3E7+NYwP?S}|{F9HkB$^*2Q8S9c&VPlF1FIb^2=a+aPH!kEfSKT{| zSt)s-l~*DbGupn|t0~KVfqSGx^I@K5`l>rn-5U)r!OQyG##dr^q!J?OPL}8AwBTnx zB}u)LkmZYOWH(vJ>4fMl&T1(v4oIM(o)}WpOmDIQ`Vr?hmeR)E6})VP~CuW zMx14f%tB!dmdx3QuLJn2ku+e`I1c&Hv&V!1+RZSW^l8toI**B06@!|3J4sp96)&$& z_BpVp((+D4a>$GT-)Bc2!yn`#Zovz_)K}fH z?5Z(mm5wzQ8AZvoauG2E+z*N33JyCd;EqeOjukSIODhPP;86&+7$xCit2t_5kzXT} zzZyE0&TRJ3S+P{%j3IOIu^SS|cuI2&FOkLa`84iyz=XI4?0z5Thb>W{xkLJFM1Vv6 ztPPv7S4vDgX;{aP%jHo2->%A}4It1|t_RLh#5zJ8Q|nlHv%iHDLXaW!mAV9UR3>g2 z4S;*=oauUIb(cuCg={0MJ;Rc4G#ye$`%Of2go3x-PQp80*8TvKTvRlNAe)U_?(KF7 z5)6!EzXD5L;sP_RO_3Yw`#bkb3H#HO-<67HqirGYwQD^l8Vfh{H5T?-kUB#E({Sb? z+CDa&+m(W~;l6;hiPf?!jN{JzJ5j=InfD2I#ZG*h@jv8R5fg2~zpsDr<8!7YDVWVO zI0OoPN*>vK_pLZ_Yi;fLlr<8@qErMRuS2E#`=6W@e-cApG()jg@##b96JScj+(}+8 zMk^SWYz*~4;P8~LayUu6=cg&Z>FF#d`qU#-5s~A@j#5hgW4dQq1m`4f69fh(wjF?% z(C8j1F>D$7nIfRoh@GON&{y=@Xfaqp(rSs~v8CK5>l3{rZ!tVtD|MJ?`KN{XGcch> zs*$?4w@qS&;;mHZ@IytbT_7N9!{J}k8*~opmarqGl)3aIDc}mO`*_e6U(q&g3F9;t zZ4poS47S}Efx;Maf|y$AzxN==xfOgaPk#s&j#MPN)9b`U#gdzF@jm_vQlRIlaVteTbH$i(wIBjT;&`$+f6s=W90cIkP zXN>*ncT=Uj@_rVSt5yh9Er_gkDjhFE6n>jp0p}L4R@y%r6{RGBhu;;$dh?DoVR32~ zvf_5$W9OZ=0cAm}5^Z^-)=Qq=omYaFXN$tAJpVX^C@!#D&>Bkd3m1o*m6J{UbB_Sj@^w*uu=TdrgTjb&bA1mnx7e z9PV1J5dQb`CGa~&LeWjw&YzpfK=py4zpMJMzquHAN!z;8!^N!3~N z6kWHT=BqRxy_&ogvi1dQ6BLc}&#kgf%Ikr*c;d4L!kKXMr?ZlwlHHl$9FY2Aa2D3; zhU~>5bS6T&y3(Fdq@jTe3w57xGL)0QM@b+s)q>D|;-3I6y^;jSD^SKNB>Ma6SLxxI z-0z|PwAf&ACPm8~aW42?@?UZI|GT61(7wno&)xUcAAqhWz z)2r&(r%C1*gU{3j(z}O{$4ie|HNxG5%O&Y6Tvykwq{*`&d+QWv-$jomu5++`0qA{NFw&3DTFJH$xv$oIbuI986z4S+x%k>`{x zWTrk;D+}SGQ(I3W>5@>PUE5q|eglx#J)qxqHC4Zw0mTzX=}5+rtygpB)i6RcskY`H z69pI~a0g=zSdA;qGV(^0&f<+&tn#%-T_hrN1Iv_(4i>GKLbyY8pEuJyd{v9opW1`& zy%a>sJ(Kb|bpu1s_P|!+@Ym(L*N4iQ2xLVJ1UZf; zTa(TJ{vxN25|EJiyIVJvT8b2f04gGLM4Pk2;_`UY{&1yl46k>z=B;V)p~As%qJT_) zyXqY%+v>oo7ot*P@SW77OPT>lTFr3cK1uqG^T;Q#55EYJwgh2`p#w|Z6NLq6%a4DP z?L!36`VYYX99J+i@XPLn5)m#>|dnrS#}c+DsOvG zla_*2cn{=;{nJiCEXlVAlZ58M=(8thNO%jWP{Sgm`9zvWK<)u#%=6$HZ6IZJpRVXU z+?JKfw2QuUmKjxyl^8cnBCkT`=h2U@DfTz0vEN0i*pX}p5|T#-*@fiXBD+9j4Nq%dD^+{}l%pY}5@6Ec z{Po4rJm3nEJty0R!sq+U!jQ}Nf82ZKK{I6!;Jo)BaZJ(kFD+a6VNX~qOF&n9l65L4 z+F8oom&oXsOad_4lm$W`^5sC()ifXLZGvUzpb>Q!S>ZAdAjB^s#XtfjfL^@E;s zI+_md&emY_BJ4By9ZSfNqWi*iZK`d0k<(wCTJwN+>wFm5Kuh%`ZvYpxFqDA>SJTNg zumAZ7ATQ?j^sYb(Osw7(AihXzMDbCK_P3PEK#QWcTst4nH+csJ*l~Wj1w02^7~Td> zoLrq7)M20s^1?Agu)MZaNU{!N?6;s~64^An&F@O9x%LFbf7s5dG?V_@7j<|)4^_ai zx}z(fz@~Hxp-RQNaS*&1-|b}Lekbtv5lwGXk(juSJrG5%Tp|^06X6esrPF~1KE`Ku z@=c{eI4N5>hk9#e&)066;Ilv~la1+)y}7z7WK%(>boIs1{$3G{@>e&bi`uC)(DnS| zaEzUQK0GX>2~7^fLH#{Vrh5_6;i15ulM&%5Y;$wXL!erPEQsUKP-jK>Fcnuh1OiRG zcM8?i-jd`fQFAhCyXj3pWGM+`f(W9;-!d{mUB*04cQ_|Sf`=VKEDlI}_qj(dPqxC) ztod#eEh3^_(ip1&ee9ZG#1hv`p$yVnp9cR4x#V!njK~QJT-=DX9$3X&S@525&AM2T z%vFsHL>1{hBj?(4FoPV7>pBL z9c}V4$CSRFneF@SN!3Pdq^LNhKKK@h+1`ORs|M*uMtl^hrg^tBSE2mv;m`jzt>=%x zgQ@!t|M}>lqjuK$FRSpS&XS0R~svnE%)RlTfW?S;@NWE4mHR z#K4^-V?AQm>fZtkEi2U*=jz=J-|Z&r!H-6__>IwWS>s`7O3ZrPCD5(SgM7r5xF^@{ zKUH`Rkr|sWqK`5N;)IoMGsAde#t-NsSRG<+KMM_!h!GvTf~-vYlGq_`N4mi*lmAQ_ zy}}rUm}C_fk}mC7zSNhbJEkMF6`yJMF%qUk_G{1&**3G+q`rxo~u+5oZTg;_iw& zwTs>!7XR^WjB~PuEIrAAREfg{k108nVTdc`{eInTg76juN$zP6Nv|db;v;}*C$z=U z$uI*W9&11TEYQ5{$8CC=jpaKgcTirt1hflHZl2JGA5j9rQTrCOl(pl_OeSbftAJ$v z`Dq^YvECGcIXhahx!RYG1W%f3x>ATEe9<@To4RPa?*ZjG>Tif)KtEmy!{E_4Acqbm zO|ysE%7y)zTEPQt>6BKZTg-)agP=R(n5edUDfFirU+mC|g8+ zI~#ImPhZKHAn8&c@Ao)ycifi1C9^>o zJEW&}!0U==s#^b*(OynT0hi2%_Qm31DsZ^h4m&1WP;Ya---zceBv_@&<+StEcT*Vs za=)iDE=cAR+ZQ{G-+q!5I})p|bwUyq<<_O`$~}gHw>mm*#Z*(Rix;fdMn-=ez7BGJ zUi6of*Iz-n$LnJK%+pP?etPyYBjKaQqikJ&{}39C+Qtc*vll$7J>g5(k(J$`jP+t+ z_-c}C$7W{Ce(_nf>RxZg?Q-N3m^19k(aJX8p&8*^=f?J%ay4G+X!AR?GFh4r-#|N# zGEo1wlojHM?Qt-7@ylYi65*Qn}lC-cjjn0=&cS4oJgdnrw zgp=k7F5LZ?v9K4$x!Ivy97_59&?ic%mdlkTa5#g>e4?&PMX7Fgy-y_O&{+*x-Vw^x zdgE3%E!*kLS7Q5%J@B!_HG@N4lH6|~_G$UG)nkVu^)?udef(dUSbk*AWM%e;{@Y11oV z8KC8qq8DvFXIEwm|GJU2R9StgQ?et4(lvM{73XTNIgrAa)AmfEqUdBOUgM)RZkXB6 zWZT{^+NG6aS0@hx_p)A`ftn0i>Fq z@?KLCOHA(l>lu+MOJU}8O3|I0D`AQRq1UPz>nd7?a{pfJCBSguqqmKzP!!5~l*Agy zc|2%~AKQt<%Khdf(=pE6?R}#z4ZcdNi|2MD>8yVhe7*w`5ffdBH%#Bv)#km`sRsu| zzL$JtQXlvWS$(?dy(Hi;wRIk5(EbIF9}!6Pk0dH~Fi~YS*$n()M)wNNHSCoVG^Hjj z3@;g>YW%vi9_w;oHn-o&?F*3eJReg?K@5r>Bhvs_BaKn&lDb&ZG$M!#L@Q3-(YtQ z`0@48=Jf|v{65Grru$+W2QPXgD)Ehiz+(F9h5M4Q0VT{!e?h-h*Y?IGe0i&9|5?!C znUG?x-p3Vf8q0+=4(tz`x6X;-;t7PPS0Kl8QQIp2WNPve$h!bw+S=DgZwyxQo))Rd zsc0a(A_I)XwDYcq-fw<3bMjui{BbrC+ryvr7_mxt4Mdl&+eSe8B4nULhU`6@&{1JQ zmq$Z`nx2ot16S?=4gwZLJ>VzRA-VWI2pRYL=h9v zAs?W%_~Y7FIlj8zrN6dDQf3h^;Z!tMh^FGtzZUr8Ct2!St%w@v!jEgC&7RmRBjwEy zU}!3Tu=6yZq?5y5eLv;T}?3HaZ~nzJ*;Th8ENsnYr3+p-|N`kldn)9v)As~=~8 zf*T}bv7l!LC?NPqV)NXLF<77SeOgCjbD|vSvWgLoS;SDUi`TQc5oFU;3=m{K-eVKF zj^h^bH(}G$Qm%GWfskXARKmDvL`dA0IA5_frfJc({Rn&-0VOqL&zr4?Z^JK34$W5cR$jzB$CLb>6NE`8QUr!=eNUj zX4yV!sr{?IiJc2;Hsln zoun5oN)*k{j7X@i`M4U!E@16vY@<cOBMv{ z#r`eEmZ)fjBR3aMDe5^-R%oWlJ)F+1Ufa{(Cm=@~*0Z|o(`!@ZM?TFS7>SYRiLR+L zxsL)<4PZi{$z{K^Gpyr$**}A|Vg-s;_{%kQ%x(xR#y@n{&W;nMf~y=-Z*4&F;1M-g znV6_Z_~laTbspg(*Aih4T48DsKY`p0?xQ_rHRHQuBg_!^pVbJo;*8*^;wiQ%Mg4c@)w>SvCg3m4kq?8TCtDH@f9D{rm!v^v!#Y#%*@DeK?xSkKQ3c)|Zmq-ZIl^YGRB9O?FjljubDNM;1ZMk;vcslI;C;!7CcVvDu zLwrx<`1PhR*cz@rS&ufogB2cw*0BK~#*drFjm5}xoO^rMICOxDe5L;VKCeX8*1E1< z=92KwmQv8;mgrTqe}6U6KT&2X0mq38N>g2#`g4~5D>Gxo(Jj$OUp z=A6(P%LPP7k7>&moWJ^j_%Mcq7i3C-Jg~2w0LzSmf|AGJ!JDyMRoYGBevZ%|wY3g# z$_O{B4drTq^ElyygDRvA*A+Q)V0u3^HUnzAATz(2aDDqu?-pXx3sz9UDX>V#_2*d$ z5mE9QLEcPO+m0Js`bW5QBjFMY{A2$>kGMvwkdDf4AOrmuz6uVPE;*QMr%M}M06Kp` zYuV9UmeT4Q9D)LT31U?SOS|dv3_D2@8sEJJgL#(A>Li4@)w17G z0t6`~MFBc-DY4VA<`vJkB%%^V_E^B3hGo8)EdI?)RzdU$w7vBp_h|;y*aJNJm9lVx zC?xR-;FrF(;HOIfd2wjiZ*Cyq!ivVSyaesj9Doma1U2D}6K}ekzU1g0df#5G?*YYO z0w%VVWLdRGV9lQ^T?qyg7+G8}{K~zSJk9OAiyP1O?1jiL zRJr5UO_yN8%vS%>=f2lF(-nI_$wz|O-@c9nA^=(G)NnG*lP_7E8b>HT1(5VMFY#Tw znx51TpE8*u77{W}hz1A<{aoMxJ%|m&aD>p41M^GE9#ZYsng@UZ#}u#Iev7r@q`je* zzniD??@gy_9W651T4(d7K9I`&ko^3uWoHI^X|p}YW_~lTX8v6=GLhU4bjPT2pVJQ4 z3th4JE-wJEzKP_%H0x(3y&rp2j6%ROpslXwLjJR3+DCc5p{A?<;Y#uWhHNw_ci z(a~fc+L^4boAxQxcOMj=87W&cL=Q;EOG`e%q{kU(03bkvi(rd10gBjKe?0f9QeIX7 zgs~}43yk-zv2XLojJX9m*}m4zk&FNff3s)i=D}3D(sOuxiB#o-(LHzcmN_R@hx^fy zu>Ju?a-}@M!aXT)9cn~ng z5wo0tdboZKg6@DvG4bT^1yBIL;S%WD3?p}c&I@VQN3B2{d;7JayyWVJ4HVIqL_KO=> z^IU&|%hE&Dvg~AO>_8~~%);}qZutwFEW59NZG|InBO`M!%5CyGLd7<-V=XhK3YObp zz|cGY=^m@)$pBNz>4`A)X97t|atQUDbM0STBu=va?Y)Un23fFOY?o}nB_YxSv;57- zs$<6D2})NfYGgZrtt{Kx(>9`! zr5LbNt^+wGU!=+pjN<2WmA#?K!wH(J!9;AITsz|BP%CZfeg#~XpSPoD93WmU`M#Uj z(l*!Ez;Yt#mHBE=0~ym&7uVh@hSg@ewcVE+uKGK)Ak!30XyT?S%y;lBc0Fg{i&z|O zezFjELzvY7^+H|!$yVs+>kP(nLUaZmgC7px?I`TA&qZypy280H z${TGg5K@%A}+A_I0P7rm1%|YNEEY(di!eG8R}UhM5Vh$t~p~L#!ILf?dJ0% zoMXZntQ^8gq!y$a_2g1a&t|GXYwDZ#<>oW&Ew$ght#0X&QIQq0)Ag>&A5JE8t(aYV znS&KmZZp!n!EyQXeBri8tB||s{o&SUezwJ3R*$4;E6a1F>0LC{AKc*XlV0e3vYG3| zC7T~*jr`&*YnrLf%;Ah>;dhE^h_Hy3@V<5n$!gK$*gER_s6IF6?%b6rziB}sL=~mx z-Cc#%pT2i2goZzc9Oy%dHN$(n2z=z~GYcUcsZnoGwyJEG+0R~$=p0LIg-bKK4=PT5 zSDtdCy{DcWo92|}vBlXIa$P+N1ST97$--T@uuZ%Drn^)_Fj;71bidXAahT^I^$7Fb z&f_ZM*+*}TtA~~BUQHFtNItP~=gN)!q}B>y+Djt9p&4FL=SYURSE`sp7}*DiB`!5K zG}?#ZSH&vjvLhKuP)Ay!kW6gX)`CWeY#gQ4i-+JkvkJDE0^fq5JIGADpIF?_SG$0= zdEHAu%wEl*AVJMXcDpmM&Ggb2cHSBwNNwcUMIorq1Z=P4X7r3HD@ay|KyrzC1EA97 zDoQP2H@8B2dC086yQ*b+DrJhx-UGzJI+TwfLT&Q_X8kJXCBTu$9E zgP=g3OnndV_Xvjo{=)UI?FnB52G7V}lS=*Xu#Kte%L5A4GOD@3JcPE*G3Lt;dM z>(_Gh08ls~W9$+iE|9c~)e~RuE`ML|+#9`tObO(ypGus@jioTlLykL;ofOD%U+~3p zpxHfniR{;Ct`?@LyWvReIb-ngup}*so+J!pK#bizU^x#!OA&K@)#$TB&_Bu#k{XaY zB{n_&M~=TjoFUcGr)(R~?He^xxL-)A&{m8{>m$7^?L=biUN+@oKn3>NR$)klUq{39 zSPEWWZf3MZ&jdAXR+$ME?)vN&#}Yf40g^{TZ~sUJe5U#4UYKGy0mUjjy@ zxVrUbpFbR4xVLWAIM8hU6;}M4Pl3c<%RrSL`B`AA^W1HJ{!I2FHaO5FD<~X8V#^N| zEdryP!0U@Urp0EhMP%&7Ak@W=qv7^EK!X0^xLQP*;w@$@x1XPyS-Jodb+4WU275bj z*z}5rLV)s%91k7ey>Fm!wubIus>;{kJ~;{$rv>TmhYG$7n7#h~egQe%3NnAay{uY~ zR7{HRop5LqVuq7;31Z^M@dhLj$3Ah_a8PlGQ@$xTii&Pj(dZ4r_Vc|q zlWIzgyMGN~j>8ZXI>;zri0tZnhaJmCMg!{$n}hbZ;u$4cIv%_Oc?pdu5WYrK6*mk- zFx7pM@(7pAoLxQ7h0F&`0bm%%0}LOJv#ni^lN~l!d6OTn5=S)Hd*$f)n$OMkZK8sI zSZ4$)e|bv3eYjOr_U@H&<+Nn3b)Qz3}UbIp^rAms( zEx9abxH26@tNr1W5o;J-u}e1-48gweo5kVoa@#^KiHXchkt)3dZJ5Am+;lSyI%uQS z2*=pnHo8b}MZie%?~t;+gO8(+(!5U#kfCEa>e59wF4Kq?r8+*46~_{#WLzLHr40E@-=F(4gI3rr>KN6bOBv-|T;w1WP2&)wn{3*b;u;6K zqmqG7ewqiv;jb`D`iRb^P?i=SWua*`ADSBihw!aEv;a5I`U(4p2EjUL)G1Qs_N0-KAUPK3J z_kYYsSHeMe8gn7FL|D|BYoBaSsn31u+{932IA;prf^D^0hQjo)Ao_7=et!L}UG<_zitj5tT+#W6D;V~HS=h1BilCGrU5?Hm1GZ3-kj}>s#3O*%U zh%_%t)iqAFNZkAM0scw$Wmm8@v=5X~A^z#SDoO^XIaBWz^wC{A=2en4Oy~L+e6J1E zzajbpc6#M~+DL;Vz z-Lf3RbjGlTcp;27@p365hhRRKozS~i&7`F2Z%cKPOU6_8UjxJt-1f1Y0n5?fmD-CT zH`itdzR>N-vM`H`z1N57idz_AlqFAeIlYYQ5+K?Nn(?<5he#eIaahEDkwIz26KX2b|a zPPU^%+v0rX1~dOA5W@5bw%x|+Wz_G2Q{ND+l!5fOpdnql6D%2F+?j zEq4SnV&^i8p5KU@=3Bb1>J*Vev%}Z12lav|?Wk7L!5t^LQrNk0p-$iGJ?`7G6dNSBCRGeLpDtFy%9yFnv~O56&zsWj9lTr_uvD7!-8 z3S=GCJEQNT{=tJP$Hrr*v8U2T@q2-7!n) zwacSs(cPj}iv#B!H0-Tu#EmvjiVwX`|MaLPib~C0{R+GBcI%!^mwZ${D*0jIq2})> z^>KX^4J>EeS!syjrG!$vAg=e#=6ER81%&ECc%-zGGdztE-f2fO{L8OWzr0+-?2p+_ zTQDa34T-L&w|Dbvt;=PsKQliJ@{Fx5;s;B$X;8&5{DwOEqtsNh6udSPF~s8D5rjax z7;U4%d3 z)SlG$K_gvS8}ZOybz#iHj7$$DovC`S_TV^9B$fb=D9pgv#en0GC0GqdFI+`dkAeWE zvyC`GZ>(r^F^v#D^cA^qNMdd1#J=iYM%#|SBuX5y@4eq$>43*qFDaw8;TrS8sk47V z+_e~uQbj2^P?@gw*;{g2C0*THM07o+u$3NBc`I6^dcG9f!p~qia>}VEZ$W-1{tw&* z)yHPJo*JIjd2oE3HpZ=RtD$!ov5#~FS8XJ8#3UHcdS!k?aM@S-wAhvDj*qDwrXJ}M zBzA5FxDjf=Y9k-p$oU{ROgV*G{XpHyTTh6RivdGFHl3lP?j3$md_$CajPAh1gnl2v zoR0E%7F8bOl5n!*RAkA;^vhF*0kz>Lt;$W*)oF*P0xT_zt>eVdPn+-E>dNsZI#5gd zgE60+ob}{09LsoLl6!KdU4PC26nd}TGuU^PIEV=<BS;t+}U- z&YwU0cw+OU`-v5<7Za_Q&H-yg6-+BYpnyT}lIw)I`qjMBOt4^dJRGl-4kn{>UI<%R zGz#X@Uc%RLvg-)wy?Z_z3u1kj?i;lPlHalw z10=r1Ny6ixt4@hQGWxY)_Xky8Vq^2ZH_j<4Cb-WovSR&gjh~zhlTj^l?0_2Cc)w0e ztT?aOall?;vHzRV^g-0&H$6F&F3~p$;>y&HjqmFvNyIAPdyYnTnqMw_?H4d8_Cg@> z`*k?&qfmT$eVkC{faE|t?CM_px9ohUr_b}*g4rh%=n?JVX#B_CviWCj!{sR zLM%(LhxL?3NT#uS3QSeW#I2qY;q}acs_&9#U ziq<&P*iwnsob(M-kPLk!HOn;+U+ z&V=!Kw#jtDd8Yq34KOah;l;%EaoByPU{fHn3x7Fy)V5g4oqZc8moy{NtB_p&GwTG^ zr{5x_90yf|9J)LFqN2Qkp219AqT8@N-ti~uv(p>R4UE%r;2hVXRyK>wq@{GLxEA6W z(expwK6b0u(;$JMf4<;9Eq{Jd$>kukw)t0$m@=fNPyA#QlkLKW@lw6LnkkwcDEs$_ea>aB=h{U~eU^zNmK(9nHoF3 zqUTt|etu!*7Oh%3J*{1xLROV5a*v9irzYKhoG$H~_9bw&7n{v5Mm}0#u{~H^!!1_+ zM(?Lkk|1hpSclq0TYs`}+`Na}Krbbf*Qej}alEhi>vj z&2mIJeUi_+Wa(@7NsC)GqYy6-UmU*7ko3|t!m!LGN+H%5_{56W`xDzrK3%-c4;KZh zDVyC9)*5fU)F#P61?8IipfahqoV_e9%gb+BnfWK1nRYcjP7$5slGM+sfY>XrUPv=T z#YX1v8T?#>DxRVlLd7mPgK^PD{95yYiV7k-O@pJHo#7%H-(V)OkMU1$pxv|W5Tg|- z9XE?_&NyMJ-P zr0McR_Tmw)TAtA6tK`aFS(B}4epc&p6`9-8HU1f{a-iO6_V2r3USyNx$oFIcYI!CJu`MD!<|@ZMjeSH6A67tG5~l zeBoXeiDK+hPZlJy)uPPnl||v}CJJL7E8u%0_2CjV^_Y1kNKW(y`*%n%)kSOIsreu) zRp{%5bid4~wzPwI#_D^n=|dxaLPs=S448cxk~4&VekF-%h;+lP{8=qxF_qR)_$=jk z3477u$MjntU$0oCRO0AUdMcpnnSFLxqIdt!*dcSIMKwc#9v~q9__M?kIfp|QbW6st zTrW$#{6?z9VyJ}jI?KnuO2T^feVtv{YHK{v+Izvm;$fHCN{pXWLu3)e62Y4{)?$3I zpRs-x>4u+`EyBKjfuy3U8+V+X3}|f2Q6#42UU%rS%el#~9vo5i(5_~Xv0UGH7P!gF z`()f((@%N*Vg}*NVw5qC622spVk%+D$}`71BG;$d7?iGyJ!l^L5>{%a+3NhQWTCU4{g%p z?4b8s^>$^>gf!hmHf%Apsm&>iPHt=88yjgV#COY$R)~X_Y(%?=BP;|NuJlZ0oMLR)%?TxF5&p=N6RkqPVe>3+xxNnK536U8P^7+#vI!&2>(?4%kfBMKm52E zr!M-(XDyYaH2JRB^SWrbBQh^_<>ZJXlg(6A)e*1fs5s-npT$N4qN$iet`hY;N6m{6 z*3M0IfdCVOb2{Zet7V+W#!P*Ga9WOn?5Oue7eA7wW{Ex4Z$EI6{~J2PCVpR!CP9%> zRX5sga$)@`IdMrYr7H?=*)9^q zXDiBa!7l#8vp#FhwT=BxxQHDwayUyr-?w^UOtz}U0B>T<)011Bf_V^*Umj?}vX-jp zeilvfzh&%jpmDzzU1W~%Mbq^a*F7nkM)lrCEdYk@exJ2wnk6g+mF5BhcSEOke2*PVZJF@ zmz?ma%6MHpEUgXr%zxHgUuGtp^ZpK|0veoY&a=W%HTIl;ngI%bUxTpb6J2IZLuMc_ zz!SH{3I9(`pbf+YE7~oDsu{XBkE&_@vp5K&y^Q7{e32vcsY%aZ(>xL7e{@+$=>Y*! zdLT7?_&?F^Ab|^MBqgxLA?x^G0hJ;aQf85E?f!oXEdnW^B@7b&81mvj6NMShg8=X@ z%aN+a|NUsmAf!$~hNivq|AY@Q$ae{Du*KFO?0PMX+K2lPyvip1s?XnLo>wA7W_~e8 z;kUcK;zdCrO@AsY^?Wy)$93?FM|SBMO&*?PYx6)8+W+h6+yj~V|3B_Bx#zwLp=3sK zzn4qyml`XVxnHtGpInwSx0NZHC1i+l?IVTUbIUb%<1>*n_Q=9OisveyVGl?wt}2;$|M?UomqVFR$wojluU^r~kA?x|)eY3JmY8x9Hn0IS-{(s;TiJQEoI-5m47KM-g7wyoO zmvj$XtR3IA+kR}^j99(?*WfevX#Ea4wQ}uw2ZW4<6x90GI!6?~2Mdsx!G4}&=qvv( z29g#H_UfgZd`X>ujs={Rp6X#tM}r_H9Zyk&D073Q(uPmN=y<^IC9RKwR6{DWD*3;S z^%4e4sk}A5B=%=hvqkApH|vd*_CIR|u~`7RI1-=zj~NGR($}K1G)Md8h=0}$cz0lA zsf2b*gaCXsmFsDTQ8x>u{4E9T6oJX%v76H#i?6Vm8&>)J-%w$K_#Wr4jk@o^Svxpv zH@ISw?Wp#v_;AHB?RCe&T+!S;||01b{V3^+1KBu`)w5r(f#&S)|8LBytcQ_ z&xt*>fVM4(iL3>$*``%IRv`eCt)rbFWC?~>jqyTi{-JSuQ`?y-P6y|vh{c{>{g3k38(KVgelo`rV{;|9%_ zsziPKVR)mc$@r3a4xTSR*R`0g4h&tAzV0z8rITut7J$pM5-eHjc1@ah8#PW6${XE7 zVs--$BAy`wefpyZ_XF*rt{%G6^*>%X(Xj}p)dsTB(`!T3%fd|HM)WGxwr*!Yz8+BF zhR#|nuWuG(EUK^k|8bE8uP+M4VOf&B&+c$Ajm7>RP7;Q6+tn#<56}NbV7EkqO3Y07 zu}X)Myl={}4h}R~tIPrOB-erh*}I`gr8RJVSSRy#VIO7|5US)X-pH!CYkS%DCRgjG z9%&uuhfmEEqh%^Ib@MJ;CXeTxqb&X&wq|NfCkp%1`qR9UGHxWt^ElhQ$jT&dce@+9 zHYPq%xBPKdCh69;v!CJb6Jr*}bmC3F?VEtP^Id7fLx22O1I-(-mL<#J!@Hh4@y0hU>*GSfr;+=l=>J>GpofHzz@bDAh}SFSG{N_ebd- zrF+S*Qog#`>gpl>-QOS(Wi5U7D+6@%y1UP|*5M~rDCXg0N1vPV<)`i5Y@T@A4|>e4 ziPaq9M(Qq$+?`MJj=eJCuS-ma+%vU?XGh#zY#x?L#8IymQRvJMd<^7`+J%a&7RYBW;q`>`>DN%Y@py&Kq!P*=aAG?H zYSfu7@Y;G9qj1+i3}?`y6T*&j5H<}*HPTd6wL~tZdD{&-g?ZV9kt8m6$OOSOGR)FG ziHB?=HN3ibfz`ObIdtrD!B;C1ZvVsj?&BGHo_+#E55E3H+{Vj(pMhQND?!slUyXR@ z4SDyqzN=Bqk{4Jj>(Sez8SZ?H$uW$HHM{!iQXuh|LNJ}PszRcloX&qMXu0f=k2q;x zVbX`Stvn}`YJ`F-^qGAD9E)qu8N~OSb(9#*%$=V>2MzOsg;~*^*JryDpNgN&$i~FX z4eT8ZEzC_BzTB}@pRozni^Fo(#xU$l$=(nrSX5{lO7PJK*HSN?4jXKGz9!$ahe`Xp zX+O9Q9t3lgYXODF77yFN_@N1q^(v#@+g*~T+uOPo9)m#E5chG#X1b4z)Y=gz;sRz7 z79w39q0qE9kfRBhvVQ@#b|z&EKtHEaWY$7(87rVDCWNk{XA3|@wM}{#`0C8GNTyoM ztVac<9)XS<{&&GNb{lR`q%%;eYR+l(lOX)6d}aq-rB*odsF!MRW1{XIio6>^L#+X* z2Lm|uRPF8MAu<>g7B`SraLwF@^wRz33U6Y0OcjV@1m<-ZFuKD1d&m?@>f6MJL?%l; z8dHA?Y3j{YX^+C6bZ9G5M~rc1k$INo8m<(tywLZ2!FQy1b>sZPdqBE$Z-16lne*5O zQ>dNu3OuOCTgd>&G?@d+yNIgrMU_t{(Nj{TREN!ktj3@F+f%HC+zX*UmSr|#z7JC9 z20H-PoH*p;rbTVjB~P5alMP{Hb(Xn}jLDXP2Tq(?_wWBm6@6V`TgJo--;A@<_dv^d z0+WMZ$6XasyVDjsAQQkZ#T`%gn1i&K6GZI7lR|*K<*g3NagY!PFuH5b2lz zMtNh8D50Lm?cC)rAD2=A40ziC9;vnE&G_KMOTU6owYdqMaU#;3FsRROFsIZ#(nbIc z3W5rCp!D@fc^@Rnq`0MO0$WXx>HWoy?_{5xP%d@P4aW{1zyrpFSrGRr(qHuwm67#H zjnB@gZpwH~G~RmuH_~*M8#9mscVL?P7%itRtl=lO_nLXV?pZkaQO>hUyt`IkOD8Xd zjI9oUVBUNRud%?s=+?!o-oH5%ma<>G6h(o)LPF_yuyG7@x6M>!7jZ^pMXNr zEHK}DHDwq&w8rqnJ2}(Z9Zj^9*6(d}WAE;7X>^^^lJh710KnZDi`bS2ay3h=+`?zj z^zyRjmWPj0?aS8=5b6i%!&}u8^G`HE$eS&3>#3!DV!did!}j>uu%FB2(HeKiZTFe9 zfm&|@*eh0CvC}N6Sy{mATy_aLifY8FnE)QoM>JDJ<@#eeJg}jQ`0Fi4v-EiwLD7{W z0)iYbs@f%$(Hnv-{%uG}=hh?K#;0<#Z;rex2rK~eHeC4fGx~lnd zvks=L`2?n)ccjrUY#kbR^1K~XO;loUnOx!(i5C$+kdt3hfn%X8G`;Bu0&Q0#O&_!~ z5=EAyMAOFgD!Er2agfRg{VX={4gKBOwq+2ISHgrpv9bcoBygWsIMTljq_cGBW&LgzERcjO+ zv553Ukwr9a3FZ-%rL(!zvYm76DGcR&HYQL^&6h+T2l(@Hww=f;hoE7RXo~;8x(zN1 zhzzOBb`H)`V1aQ%Ojp<%XjN)pew7bK@Af7KfAXI7{K9}n==<$5&t26U-y*d#F&)4$ zyT`D!mUmWJ9!ij4l2tf?kgtTM@A_G7yhiblleu=h9zCS<}k0R>p!U8i!36k7e$TR&}cZMCx zW3;`0h~^k}TSEoZ?R1z`OH9NZzQ&r04Q!ewJ2{O03b8h$Q+KEZYE)hyp52OdEAM1! zcpzUz^9TH~aeSrk&^5;xZ>)i(Q^9G-y#Z_oUTS5WPqg$qqBF^aAq=LC-(U`NUaJ{i z;;cMrAz05Y*M?#Xc;c~g^ID!8RVq7zE;S*DgYHBxa+=A84Q%P+JDP464`*<|X>#R= zovt!(0?%jb(e!w1a-u4!>c`2_wg{`NQ>6@^{LrDJX^Gw1+Sf(tuMIi+cX8flTlsGa7))T9!Xm}5>Lzs}x_|y#2J8lFbr7i5*MHD@{F?4d%11rhv|nFymPdwtt@+rz zdUG^a$DcE$O0kl#3nxYt1SHok39@k5@^tM3g!&O# zXs*>WD&i`)qkTm2N}&P|2W@8|GgY0z6lzaL;9p$t?CT>02IR%P7J*6=&lLrbPOL_j zM@O4;a6>G&3Nx7@miD_-(FP$+pnZ|Y&Q;b@V$Z9+T#k4 z-N-EYL;zL786$J&(d=zPiS@7CJ-+~B)8Cpf2SN2=kiU$Y*kk#Qo@pQO_~3;xCL9&J zfO2kzGq|cGxPXbdOPf@;~I z1@Fuy@Icq{1@wf-sk`^jh4p+AdSN|AWuK(Wo#DQS)R#0F;=>ab{0c{6(~X721lEw7 zkfKmQ?O#-P;yX(m(`u{1hl4yVTf=*^d1op3m_@S6vBtiaSc|71cd|FWvQU-Tf)p!7|RnR$7?otzsQ$;X5d!8qNb`XwPR$@;`u*?vXZlBtDHk4 zt4+xVnsyp@OmGw5>>SE)WxCS@{P%TMfdKoJ7QA6((+aRcvC6%+fsV0#$WyVhk;Hlb>UuTx!0<3GE!2tWeR}d^xBL zY7AIxb7_XIJ-K4E+$Wp%EufY!`v8QRBYgm|3B3Ut_lDE From 22ce76d447407024d9bd58eedce6c166eb7b6eb2 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 18 Aug 2016 07:33:27 -0500 Subject: [PATCH 64/64] Refactor to mark Changes tab as active on new MR page. --- app/controllers/projects/application_controller.rb | 1 + app/controllers/projects/merge_requests_controller.rb | 7 ------- app/views/projects/merge_requests/_new_submit.html.haml | 4 ++-- spec/features/merge_requests/create_new_mr_spec.rb | 7 ++----- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 996909a28c6..91315a07deb 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -83,6 +83,7 @@ class Projects::ApplicationController < ApplicationController end def apply_diff_view_cookie! + @show_changes_tab = params[:view].present? cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present? end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 696f7192131..4c1f38be9f3 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -216,7 +216,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @base_commit = @merge_request.diff_base_commit @diffs = @merge_request.diffs(diff_options) if @merge_request.compare @diff_notes_disabled = true - @show_diff_tab = check_diff_tab @pipeline = @merge_request.pipeline @statuses = @pipeline.statuses.relevant if @pipeline @@ -520,10 +519,4 @@ class Projects::MergeRequestsController < Projects::ApplicationController params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute end - - private - - def check_diff_tab - request.query_parameters[:view].present? - end end diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 0b05a2693a6..00bd4e143df 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -54,6 +54,6 @@ }); :javascript var merge_request = new MergeRequest({ - action: "#{(@show_diff_tab ? 'diffs' : 'new')}", - setUrl: false + action: "#{(@show_changes_tab ? 'diffs' : 'new')}", + setUrl: false }); diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 775b38814eb..b963d1305b5 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' feature 'Create New Merge Request', feature: true, js: true do - include WaitForAjax - let(:user) { create(:user) } let(:project) { create(:project, :public) } @@ -51,11 +49,10 @@ feature 'Create New Merge Request', feature: true, js: true do click_link 'Changes' - expect(page.find_link('Inline')[:class]).to match(/\bactive\b/) - expect(page.find_link('Side-by-side')[:class]).not_to match(/\bactive\b/) + expect(page).to have_css('a.btn.active', text: 'Inline') + expect(page).not_to have_css('a.btn.active', text: 'Side-by-side') click_link 'Side-by-side' - wait_for_ajax within '.merge-request' do expect(page).not_to have_css('a.btn.active', text: 'Inline')