From 74438cd1be759903091e093bffa3ec32ec84a874 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Wed, 27 Sep 2017 23:42:04 +0300 Subject: [PATCH 001/104] Trigger change event of the markdown textarea to allow Vue catch the programmatic changes. --- app/assets/javascripts/dropzone_input.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index 1cba65d17cd..bd45da8c422 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -237,9 +237,12 @@ window.DropzoneInput = (function() { }; const insertToTextArea = function(filename, url) { - return $(child).val(function(index, val) { + const $child = $(child); + $child.val(function(index, val) { return val.replace(`{{${filename}}}`, url); }); + + $child.trigger('change'); }; const appendToTextArea = function(url) { From 3d3e043ce4587195efc4dafdc30b56dfc7c783ab Mon Sep 17 00:00:00 2001 From: Gustav Ernberg Date: Sat, 30 Sep 2017 17:41:13 +0200 Subject: [PATCH 002/104] Remove unnecessary alt-texts from pipeline emails --- app/views/notify/pipeline_failed_email.html.haml | 10 +++++----- app/views/notify/pipeline_success_email.html.haml | 10 +++++----- changelogs/unreleased/issue-36484.yml | 5 +++++ 3 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/issue-36484.yml diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index b7a60938132..8eb3f2d5192 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -31,7 +31,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } = @pipeline.ref @@ -42,7 +42,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } = @pipeline.short_sha @@ -60,7 +60,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.author %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } @@ -76,7 +76,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.committer %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } @@ -100,7 +100,7 @@ triggered by - if @pipeline.user %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } - %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } = @pipeline.user.name diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 3f16885b8e3..574a8f2fa50 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -31,7 +31,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } = @pipeline.ref @@ -42,7 +42,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } = @pipeline.short_sha @@ -60,7 +60,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.author %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } @@ -76,7 +76,7 @@ %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.committer %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } @@ -100,7 +100,7 @@ triggered by - if @pipeline.user %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } - %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } = @pipeline.user.name diff --git a/changelogs/unreleased/issue-36484.yml b/changelogs/unreleased/issue-36484.yml new file mode 100644 index 00000000000..a19126e650f --- /dev/null +++ b/changelogs/unreleased/issue-36484.yml @@ -0,0 +1,5 @@ +--- +title: Remove unnecessary alt-texts from pipeline emails +merge_request: 14602 +author: gernberg +type: fixed From 5d6e16d5f12ced555f7a998c843ba235ea77eed6 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Tue, 3 Oct 2017 09:52:52 -0400 Subject: [PATCH 003/104] fix the import :milestone from adding the group_id --- lib/gitlab/import_export/project_tree_restorer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index 3bc095a99a9..639f4f0c3f0 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -2,7 +2,7 @@ module Gitlab module ImportExport class ProjectTreeRestorer # Relations which cannot have both group_id and project_id at the same time - RESTRICT_PROJECT_AND_GROUP = %i(milestones).freeze + RESTRICT_PROJECT_AND_GROUP = %i(milestone milestones).freeze def initialize(user:, shared:, project:) @path = File.join(shared.export_path, 'project.json') From cf3de110c2320f3b944b40dc257199de28dab2e3 Mon Sep 17 00:00:00 2001 From: GitLab Development Date: Tue, 3 Oct 2017 14:32:11 +0000 Subject: [PATCH 004/104] added changelog --- .../35580-cannot-import-project-with-milestones.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/35580-cannot-import-project-with-milestones.yml diff --git a/changelogs/unreleased/35580-cannot-import-project-with-milestones.yml b/changelogs/unreleased/35580-cannot-import-project-with-milestones.yml new file mode 100644 index 00000000000..b28105556db --- /dev/null +++ b/changelogs/unreleased/35580-cannot-import-project-with-milestones.yml @@ -0,0 +1,5 @@ +--- +title: Fix the project import with issues and milestones +merge_request: 14657 +author: +type: fixed From 6aff498426519a806e905902fba2e50e7efef83b Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Wed, 4 Oct 2017 00:08:29 +0900 Subject: [PATCH 005/104] Add sort runners on admin runners --- app/controllers/admin/runners_controller.rb | 3 ++- app/views/admin/runners/index.html.haml | 2 +- changelogs/unreleased/38720-sort-admin-runners.yml | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/38720-sort-admin-runners.yml diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 719893c0bc8..38b808cdc31 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -2,7 +2,8 @@ class Admin::RunnersController < Admin::ApplicationController before_action :runner, except: :index def index - @runners = Ci::Runner.order('id DESC') + sort = params[:sort] == 'contacted_asc' ? { contacted_at: :asc } : { id: :desc } + @runners = Ci::Runner.order(sort) @runners = @runners.search(params[:search]) if params[:search].present? @runners = @runners.page(params[:page]).per(30) @active_runners_cnt = Ci::Runner.online.count diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 43cea1358cc..76f4a817744 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -63,7 +63,7 @@ %th Projects %th Jobs %th Tags - %th Last contact + %th= link_to 'Last contact', admin_runners_path(params.slice(:search).merge(sort: 'contacted_asc')) %th - @runners.each do |runner| diff --git a/changelogs/unreleased/38720-sort-admin-runners.yml b/changelogs/unreleased/38720-sort-admin-runners.yml new file mode 100644 index 00000000000..b1047644891 --- /dev/null +++ b/changelogs/unreleased/38720-sort-admin-runners.yml @@ -0,0 +1,5 @@ +--- +title: Add sort runners on admin runners +merge_request: 14661 +author: Takuya Noguchi +type: added From 79c80de99d511d084cff072bd90192ffe8ba4cda Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Fri, 22 Sep 2017 15:49:56 +0200 Subject: [PATCH 006/104] Making private project avatars use local paths + Some Group Icons --- app/helpers/application_helper.rb | 7 ++++++- app/helpers/groups_helper.rb | 9 +++++++-- app/helpers/lazy_image_tag_helper.rb | 6 +++++- app/models/concerns/avatarable.rb | 14 +++++++++----- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8d02d5de5c3..cfdb95e2498 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -41,7 +41,12 @@ module ApplicationHelper end if project.avatar_url - image_tag project.avatar_url, options + if project.private? + options[:use_original_source] = true + image_tag project.avatar_url(use_asset_path: false), options + else + image_tag project.avatar_url, options + end else # generated icon project_identicon(project, options) end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 82bceddf1f0..091d98a2c94 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -12,7 +12,7 @@ module GroupsHelper group = Group.find_by_full_path(group) end - group.try(:avatar_url) || ActionController::Base.helpers.image_path('no_group_avatar.png') + group.try(:avatar_url, use_asset_path: false) || ActionController::Base.helpers.image_path('no_group_avatar.png') end def group_title(group, name = nil, url = nil) @@ -89,7 +89,12 @@ module GroupsHelper link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do output = if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? - image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15) + if group.private? + puts "GROUP IS PRIVATE : " + group_icon(group) + image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15, use_original_source: true) + else + image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15) + end else "" end diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb index 2c5619ac41b..aea1fcc7e91 100644 --- a/app/helpers/lazy_image_tag_helper.rb +++ b/app/helpers/lazy_image_tag_helper.rb @@ -9,7 +9,11 @@ module LazyImageTagHelper unless options.delete(:lazy) == false options[:data] ||= {} - options[:data][:src] = path_to_image(source) + unless options.delete(:use_original_source) == true + options[:data][:src] = path_to_image(source) + else + options[:data][:src] = source + end options[:class] ||= "" options[:class] << " lazy" diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index 8fbfed11bdf..f8c0327e190 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -1,7 +1,7 @@ module Avatarable extend ActiveSupport::Concern - def avatar_path(only_path: true) + def avatar_path(only_path: true, use_asset_path: true) return unless self[:avatar].present? # If only_path is true then use the relative path of avatar. @@ -9,10 +9,14 @@ module Avatarable asset_host = ActionController::Base.asset_host gitlab_host = only_path ? gitlab_config.relative_url_root : gitlab_config.url - # If asset_host is set then it is expected that assets are handled by a standalone host. - # That means we do not want to get GitLab's relative_url_root option anymore. - host = asset_host.present? ? asset_host : gitlab_host + if use_asset_path + # If asset_host is set then it is expected that assets are handled by a standalone host. + # That means we do not want to get GitLab's relative_url_root option anymore. + host = asset_host.present? ? asset_host : gitlab_host - [host, avatar.url].join + [host, avatar.url].join + else + avatar.url + end end end From 892b02e890be539a95e6b52feb14f5d188513700 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Mon, 2 Oct 2017 13:35:01 +0200 Subject: [PATCH 007/104] Created group_icon and group_icon_url Tests for these new helper methods --- app/helpers/groups_helper.rb | 29 ++++++++++-- app/models/concerns/avatarable.rb | 2 +- app/serializers/group_entity.rb | 2 +- app/views/admin/groups/_group.html.haml | 2 +- app/views/admin/groups/show.html.haml | 2 +- app/views/groups/_home_panel.html.haml | 2 +- app/views/groups/edit.html.haml | 2 +- .../layouts/nav/sidebar/_group.html.haml | 2 +- app/views/shared/groups/_group.html.haml | 2 +- app/views/shared/members/_group.html.haml | 2 +- app/views/users/_groups.html.haml | 2 +- spec/helpers/groups_helper_spec.rb | 47 ++++++++++++++++++- 12 files changed, 79 insertions(+), 17 deletions(-) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 091d98a2c94..60ac4c63e62 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,4 +1,8 @@ +require 'uri' + module GroupsHelper + include Gitlab::CurrentSettings + def can_change_group_visibility_level?(group) can?(current_user, :change_visibility_level, group) end @@ -7,12 +11,28 @@ module GroupsHelper can?(current_user, :change_share_with_group_lock, group) end - def group_icon(group) + # = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') + # = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' + def group_icon(group, options = {}) + img_path = group_icon_url(group, options) + image_tag img_path, options + end + + def group_icon_url(group, options = {}) if group.is_a?(String) group = Group.find_by_full_path(group) end - group.try(:avatar_url, use_asset_path: false) || ActionController::Base.helpers.image_path('no_group_avatar.png') + if group.avatar_url + if group.private? + options[:use_original_source] = true + group.avatar_url(use_asset_path: false) + else + group.avatar_url + end + else # No Avatar Icon + ActionController::Base.helpers.image_path('no_group_avatar.png') + end end def group_title(group, name = nil, url = nil) @@ -90,10 +110,9 @@ module GroupsHelper output = if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? if group.private? - puts "GROUP IS PRIVATE : " + group_icon(group) - image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15, use_original_source: true) + group_icon(group, class: "avatar-tile", width: 15, height: 15, use_original_source: true) else - image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15) + group_icon(group, class: "avatar-tile", width: 15, height: 15) end else "" diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index f8c0327e190..ddaa15e6a0d 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -16,7 +16,7 @@ module Avatarable [host, avatar.url].join else - avatar.url + [host, avatar.url].join end end end diff --git a/app/serializers/group_entity.rb b/app/serializers/group_entity.rb index 7c872a3e986..6d8466da902 100644 --- a/app/serializers/group_entity.rb +++ b/app/serializers/group_entity.rb @@ -45,6 +45,6 @@ class GroupEntity < Grape::Entity end expose :avatar_url do |group| - group_icon(group) + group_icon_url(group) end end diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index e3a77dfdf10..47cc2d4d27e 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -20,7 +20,7 @@ = visibility_level_icon(group.visibility_level, fw: false) .avatar-container.s40 - = image_tag group_icon(group), class: "avatar s40 hidden-xs" + = group_icon(group, class: "avatar s40 hidden-xs") .title = link_to [:admin, group], class: 'group-name' do = group.full_name diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 3e02f7b1e16..2545cecc721 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -16,7 +16,7 @@ %ul.well-list %li .avatar-container.s60 - = image_tag group_icon(@group), class: "avatar s60" + = group_icon(@group, class: "avatar s60") %li %span.light Name: %strong= @group.name diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index 181c7bee702..a0760c2073b 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -1,7 +1,7 @@ .group-home-panel.text-center %div{ class: container_class } .avatar-container.s70.group-avatar - = image_tag group_icon(@group), class: "avatar s70 avatar-tile" + = group_icon(@group, class: "avatar s70 avatar-tile") %h1.group-title = @group.name %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 15606dd30fd..16038ef2f79 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -10,7 +10,7 @@ .form-group .col-sm-offset-2.col-sm-10 .avatar-container.s160 - = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' + = group_icon(@group, alt: '', class: 'avatar group-avatar s160') %p.light - if @group.avatar? You can change your group avatar here diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 8cba495f7e4..0bf318b0b66 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -6,7 +6,7 @@ .context-header = link_to group_path(@group), title: @group.name do .avatar-container.s40.group-avatar - = image_tag group_icon(@group), class: "avatar s40 avatar-tile" + = group_icon(@group, class: "avatar s40 avatar-tile") .sidebar-context-title = @group.name %ul.sidebar-top-level-items diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index b361ec86ced..63f62eb476e 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -28,7 +28,7 @@ .avatar-container.s40 = link_to group do - = image_tag group_icon(group), class: "avatar s40 hidden-xs" + = group_icon(group, class: "avatar s40 hidden-xs") .title = link_to group_name, group, class: 'group-name' diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index bcdad3c153a..5868c52566d 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -4,7 +4,7 @@ - dom_id = "group_member_#{group_link.id}" %li.member.group_member{ id: dom_id } %span.list-item-name - = image_tag group_icon(group), class: "avatar s40", alt: '' + = group_icon(group, class: "avatar s40", alt: '') %strong = link_to group.full_name, group_path(group) .cgray diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index eff6c80d144..55799e10a46 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -2,4 +2,4 @@ - groups.each do |group| = link_to group, class: 'profile-groups-avatars inline', title: group.name do .avatar-container.s40 - = image_tag group_icon(group), class: 'avatar group-avatar s40' + = group_icon(group, class: 'avatar group-avatar s40') diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 76e5964ccf7..40c26b6e1d5 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe GroupsHelper do include ApplicationHelper + describe 'group_icon' do avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') @@ -10,14 +11,56 @@ describe GroupsHelper do group = create(:group) group.avatar = fixture_file_upload(avatar_file_path) group.save! - expect(group_icon(group.path).to_s) + + avatar_url = "/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + + expect(group_icon(group).to_s) + .to eq "" + + allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) + avatar_url = "#{gitlab_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + + expect(group_icon(group).to_s) + .to eq "" + end + end + + + + describe 'group_icon_url' do + avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') + + it 'returns an url for the avatar' do + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) + .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + end + + it 'returns an CDN url for the avatar' do + allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) + .to match("#{gitlab_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + end + + it 'returns an based url for the avatar if private' do + allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.private = true + group.save! + expect(group_icon_url(group.path).to_s) .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") end it 'gives default avatar_icon when no avatar is present' do group = create(:group) group.save! - expect(group_icon(group.path)).to match_asset_path('group_avatar.png') + expect(group_icon_url(group.path)).to match_asset_path('group_avatar.png') end end From 1b752968757873434404a2bf82ea360ce829ec87 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Mon, 2 Oct 2017 15:44:58 +0200 Subject: [PATCH 008/104] Fixed Linting errors + tests --- app/helpers/lazy_image_tag_helper.rb | 2 +- app/models/concerns/avatarable.rb | 6 ++---- spec/helpers/application_helper_spec.rb | 6 ++++-- spec/helpers/groups_helper_spec.rb | 13 ++++++------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb index aea1fcc7e91..60df16b3373 100644 --- a/app/helpers/lazy_image_tag_helper.rb +++ b/app/helpers/lazy_image_tag_helper.rb @@ -9,7 +9,7 @@ module LazyImageTagHelper unless options.delete(:lazy) == false options[:data] ||= {} - unless options.delete(:use_original_source) == true + unless options.delete(:use_original_source) options[:data][:src] = path_to_image(source) else options[:data][:src] = source diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index ddaa15e6a0d..6ed57dea858 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -13,10 +13,8 @@ module Avatarable # If asset_host is set then it is expected that assets are handled by a standalone host. # That means we do not want to get GitLab's relative_url_root option anymore. host = asset_host.present? ? asset_host : gitlab_host - - [host, avatar.url].join - else - [host, avatar.url].join end + + [host, avatar.url].join end end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 10bc5f2ecd2..6dafd73d337 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -57,6 +57,8 @@ describe ApplicationHelper do end describe 'project_icon' do + let(:asset_host) { 'http://assets' } + it 'returns an url for the avatar' do project = create(:project, avatar: File.open(uploaded_image_temp_path)) avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" @@ -64,8 +66,8 @@ describe ApplicationHelper do expect(helper.project_icon(project.full_path).to_s) .to eq "" - allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) - avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + avatar_url = "#{asset_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "" diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 40c26b6e1d5..55de03c8ed2 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe GroupsHelper do include ApplicationHelper + let(:asset_host) { 'http://assets' } describe 'group_icon' do avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') @@ -17,16 +18,14 @@ describe GroupsHelper do expect(group_icon(group).to_s) .to eq "" - allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) - avatar_url = "#{gitlab_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + avatar_url = "#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" expect(group_icon(group).to_s) .to eq "" end end - - describe 'group_icon_url' do avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') @@ -39,16 +38,16 @@ describe GroupsHelper do end it 'returns an CDN url for the avatar' do - allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) group = create(:group) group.avatar = fixture_file_upload(avatar_file_path) group.save! expect(group_icon_url(group.path).to_s) - .to match("#{gitlab_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + .to match("#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") end it 'returns an based url for the avatar if private' do - allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) group = create(:group) group.avatar = fixture_file_upload(avatar_file_path) group.private = true From 6168e33e9e2f327f078e07b71c466dcc9fa9813a Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 3 Oct 2017 12:21:02 +0200 Subject: [PATCH 009/104] Fixed Test --- app/helpers/lazy_image_tag_helper.rb | 6 +++--- spec/helpers/groups_helper_spec.rb | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb index 60df16b3373..4def806f1c0 100644 --- a/app/helpers/lazy_image_tag_helper.rb +++ b/app/helpers/lazy_image_tag_helper.rb @@ -9,10 +9,10 @@ module LazyImageTagHelper unless options.delete(:lazy) == false options[:data] ||= {} - unless options.delete(:use_original_source) - options[:data][:src] = path_to_image(source) - else + if options.delete(:use_original_source) options[:data][:src] = source + else + options[:data][:src] = path_to_image(source) end options[:class] ||= "" options[:class] << " lazy" diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 55de03c8ed2..4fe50d120d7 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -48,9 +48,8 @@ describe GroupsHelper do it 'returns an based url for the avatar if private' do allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) - group = create(:group) + group = create(:group, :private) group.avatar = fixture_file_upload(avatar_file_path) - group.private = true group.save! expect(group_icon_url(group.path).to_s) .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") From 27577e8e2b7bf18c0dbc402e5efad4905c5a154c Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Wed, 4 Oct 2017 13:58:32 +0200 Subject: [PATCH 010/104] Fixed Tests --- app/helpers/lazy_image_tag_helper.rb | 11 ++++++----- spec/helpers/application_helper_spec.rb | 2 +- spec/helpers/groups_helper_spec.rb | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb index 4def806f1c0..2b5e9a9ef57 100644 --- a/app/helpers/lazy_image_tag_helper.rb +++ b/app/helpers/lazy_image_tag_helper.rb @@ -9,11 +9,12 @@ module LazyImageTagHelper unless options.delete(:lazy) == false options[:data] ||= {} - if options.delete(:use_original_source) - options[:data][:src] = source - else - options[:data][:src] = path_to_image(source) - end + options[:data][:src] = if options.delete(:use_original_source) + source + else + path_to_image(source) + end + options[:class] ||= "" options[:class] << " lazy" diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 6dafd73d337..87ae1fa5660 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -60,7 +60,7 @@ describe ApplicationHelper do let(:asset_host) { 'http://assets' } it 'returns an url for the avatar' do - project = create(:project, avatar: File.open(uploaded_image_temp_path)) + project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 4fe50d120d7..97f0ed4904e 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -15,13 +15,13 @@ describe GroupsHelper do avatar_url = "/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" - expect(group_icon(group).to_s) + expect(helper.group_icon(group).to_s) .to eq "" allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) avatar_url = "#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" - expect(group_icon(group).to_s) + expect(helper.group_icon(group).to_s) .to eq "" end end From 09a733bac33195ef2693b17f1d3277dd26dc7777 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Wed, 4 Oct 2017 09:12:01 -0400 Subject: [PATCH 011/104] fix the spec so it fails before applying the fix --- spec/lib/gitlab/import_export/project.light.json | 12 ++++++++++++ .../import_export/project_tree_restorer_spec.rb | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json index 2d8f3d4a566..749fd868a81 100644 --- a/spec/lib/gitlab/import_export/project.light.json +++ b/spec/lib/gitlab/import_export/project.light.json @@ -63,6 +63,18 @@ "last_edited_at": null, "last_edited_by_id": null, "group_milestone_id": null, + "milestone": { + "id": 1, + "title": "test milestone", + "project_id": 8, + "description": "test milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "group_id": null + }, "label_links": [ { "id": 11, diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index efe11ca794a..eaaa9791d04 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -24,7 +24,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'JSON' do it 'restores models based on JSON' do - expect(@restored_project_json).to be true + expect(@restored_project_json).to be_truthy end it 'restore correct project features' do @@ -203,7 +203,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do restored_project_json - expect(shared.errors.first).to be_nil + expect(shared.errors).to be_empty end end end @@ -212,7 +212,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do it 'restores project json correctly' do create(:ci_build, token: 'abcd') - expect(restored_project_json).to be true + expect(restored_project_json).to be_truthy end end @@ -233,8 +233,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end it 'correctly restores project' do - expect(restored_project_json).to be_truthy expect(shared.errors).to be_empty + expect(restored_project_json).to be_truthy end it 'has labels' do From d2620faff92f235d5659ccdac0825cf18c7e2389 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Thu, 5 Oct 2017 16:29:45 +0200 Subject: [PATCH 012/104] Updates based on MR comments --- app/helpers/application_helper.rb | 15 +++++++++------ app/helpers/lazy_image_tag_helper.rb | 12 +++++++----- app/models/concerns/avatarable.rb | 8 +++----- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cfdb95e2498..264f21a1466 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -41,12 +41,15 @@ module ApplicationHelper end if project.avatar_url - if project.private? - options[:use_original_source] = true - image_tag project.avatar_url(use_asset_path: false), options - else - image_tag project.avatar_url, options - end + #if project.private? + # options[:use_original_source] = true + # image_tag project.avatar_url(use_asset_path: false), options + #else + # image_tag project.avatar_url, options + #end + + image_tag project.avatar_url(use_asset_path: project.public?), options + else # generated icon project_identicon(project, options) end diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb index 2b5e9a9ef57..c150c2927a9 100644 --- a/app/helpers/lazy_image_tag_helper.rb +++ b/app/helpers/lazy_image_tag_helper.rb @@ -9,11 +9,13 @@ module LazyImageTagHelper unless options.delete(:lazy) == false options[:data] ||= {} - options[:data][:src] = if options.delete(:use_original_source) - source - else - path_to_image(source) - end + #options[:data][:src] = if options.delete(:use_original_source) + # source + # else + # path_to_image(source) + # end + + options[:data][:src] = path_to_image(source) options[:class] ||= "" options[:class] << " lazy" diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index 6ed57dea858..dee7e47e2aa 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -9,11 +9,9 @@ module Avatarable asset_host = ActionController::Base.asset_host gitlab_host = only_path ? gitlab_config.relative_url_root : gitlab_config.url - if use_asset_path - # If asset_host is set then it is expected that assets are handled by a standalone host. - # That means we do not want to get GitLab's relative_url_root option anymore. - host = asset_host.present? ? asset_host : gitlab_host - end + # If asset_host is set then it is expected that assets are handled by a standalone host. + # That means we do not want to get GitLab's relative_url_root option anymore. + host = (asset_host.present? && use_asset_path) ? asset_host : gitlab_host [host, avatar.url].join end From d0593e1621c416fe1900803c787d8cba87a734cf Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 5 Oct 2017 17:24:51 +0200 Subject: [PATCH 013/104] Add docs for GKE integration --- doc/user/permissions.md | 1 + doc/user/project/clusters/index.md | 95 ++++++++++++++++++++++++++++++ doc/user/project/index.md | 2 + 3 files changed, 98 insertions(+) create mode 100644 doc/user/project/clusters/index.md diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 44ee994a26b..c6b306b3bfe 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -74,6 +74,7 @@ The following table depicts the various user permission levels in a project. | Force push to protected branches [^4] | | | | | | | Remove protected branches [^4] | | | | | | | Remove pages | | | | | ✓ | +| Manage clusters | | | | ✓ | ✓ | ## Project features permissions diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md new file mode 100644 index 00000000000..26dcc63db90 --- /dev/null +++ b/doc/user/project/clusters/index.md @@ -0,0 +1,95 @@ +# Connecting GitLab with GKE + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in 10.1. + +CAUTION: **Warning:** +The Cluster integration is currently in **Beta**. + +Connect your project to Google Container Engine (GKE) in a few steps +without the need to provide any credentials for your Kubernetes cluster. + +With a cluster associated to your project, you can use Review Apps, deploy your +applications, run your pipelines, and much more in an easy way. + +NOTE: **Note:** +The Cluster integration will eventually supersede the +[Kubernetes integration](../integrations/kubernetes.md). For the moment, +you can create only one cluster. + +## Prerequisites + +In order to be able to manage your GKE cluster through GitLab, the following +prerequisites must be met: + +- The [Google authentication integration](../../../integration/google.md) must + be enabled in GitLab at the instance level. If that's not the case, ask your + administrator to enable it. +- Your Google account must be associated with your GitLab one. If you haven't + done this already, you will be asked in the **Cluster** page. +- Your associated Google account must have the right privileges to manage + clusters on GKE. +- You must have Master [permissions] in order to be able to access the **Cluster** + page. + +If all of the above requirements are met, you can proceed to add a new cluster. + +TIP: **Tip:** +Check your existing connected accounts by navigating to your account's settings +under the **Account > Social sign-in** section. + +## Adding a cluster + +NOTE: **Note:** +You need Master [permissions] and above to add a cluster. + +To add a new cluster: + +1. Navigate to your project's **CI/CD > Cluster** page. +1. Connect your Google account if you haven't done already by clicking the + "Sign-in with Google" button. +1. Fill in the requested values: + - **Cluster name** (required) - The name you wish to give the cluster. + - **GCP project ID** (required) - The ID of the project you created in your GCP + console that will host the Kubernetes cluster. This must **not** be confused + with the project name. Learn more about [Google Cloud Platform projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects). + - **Zone** - The zone under which the cluster will be created. Read more about + [the available zones](https://cloud.google.com/compute/docs/regions-zones/). + - **Number of nodes** - The number of nodes you wish the cluster to have. + - **Machine type** - The machine type of the Virtual Machine instance that + the cluster will be based on. Read more about [the available machine types](https://cloud.google.com/compute/docs/machine-types). + - **Project namespace** - The unique namespace for this project. By default you + don't have to fill it in; by leaving it blank, GitLab will create one for you. +1. Click the **Create cluster** button. + +After a few moments your cluster should be created. If something goes wrong, +you will be notified. + +Now, you can proceed to [enable the Cluster integration](#enabling-or-disabling-the-cluster-integration). + +## Enabling or disabling the Cluster integration + +After you have successfully added your cluster information, you can enable the +Cluster integration: + +1. Click the "Enabled/Disabled" switch +1. Hit **Save** for the changes to take effect + +You can now start using your Kubernetes cluster for your deployments. + +To disable the Cluster integration, follow the same procedure. + +## Removing a cluster + +NOTE: **Note:** +You need Master [permissions] and above to remove a cluster integration. + +NOTE: **Note:** +When you remove a cluster, you only remove its relation to GitLab, not the +cluster itself. To remove the cluster, you can do so by visiting the GKE +dashboard or using `kubectl`. + +To remove the Cluster integration from your project, simply click on the +**Remove integration** button. You will then be able to follow the procedure +and [add a cluster](#adding-a-cluster) again. + +[permissions]: ../../permissions.md diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 03bbc46bd8c..5feb4a61714 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -63,6 +63,8 @@ common actions on issues or merge requests browse, and download job artifacts - [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job), timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more + - [GKE cluster integration](cluster/index.md): Connecting your GitLab project + with Google Container Engine - [GitLab Pages](pages/index.md): Build, test, and deploy your static website with GitLab Pages From c786c8dcb334fbacf5b6e4fbf2da1910f94d80c4 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Thu, 5 Oct 2017 17:49:11 +0200 Subject: [PATCH 014/104] Another Change for cleanup --- app/helpers/lazy_image_tag_helper.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb index c150c2927a9..603b9438e35 100644 --- a/app/helpers/lazy_image_tag_helper.rb +++ b/app/helpers/lazy_image_tag_helper.rb @@ -9,12 +9,6 @@ module LazyImageTagHelper unless options.delete(:lazy) == false options[:data] ||= {} - #options[:data][:src] = if options.delete(:use_original_source) - # source - # else - # path_to_image(source) - # end - options[:data][:src] = path_to_image(source) options[:class] ||= "" From a8d9dbc1a64fd84bebf59a358e0f1aaa2c1cac98 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Thu, 5 Oct 2017 12:03:30 -0400 Subject: [PATCH 015/104] fix the project import when an issue has a group milestone --- lib/gitlab/import_export/relation_factory.rb | 11 +- .../gitlab/import_export/project.light.json | 39 +----- .../project_tree_restorer_spec.rb | 132 +++++++++++------- 3 files changed, 101 insertions(+), 81 deletions(-) diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 380b336395d..b936873f1d7 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -35,7 +35,7 @@ module Gitlab def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:) @relation_name = OVERRIDES[relation_sym] || relation_sym - @relation_hash = relation_hash.except('noteable_id').merge('project_id' => project.id) + @relation_hash = relation_hash.except('noteable_id') @members_mapper = members_mapper @user = user @project = project @@ -248,7 +248,14 @@ module Gitlab end def find_or_create_object! - finder_attributes = @relation_name == :group_label ? %w[title group_id] : %w[title project_id] + # TODO: Trying to find how I can correctly use the correct id depending on the object's type + finder_attributes = if @relation_type == :group_label + %w[title group_id] + elsif parsed_relation_hash['project_id'] + %w[title project_id] + else + %w[title group_id] + end finder_hash = parsed_relation_hash.slice(*finder_attributes) if label? diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json index 749fd868a81..6c7e97a8338 100644 --- a/spec/lib/gitlab/import_export/project.light.json +++ b/spec/lib/gitlab/import_export/project.light.json @@ -5,9 +5,9 @@ "milestones": [ { "id": 1, - "title": "test milestone", + "title": "Project milestone", "project_id": 8, - "description": "test milestone", + "description": "Project-level milestone", "due_date": null, "created_at": "2016-06-14T15:02:04.415Z", "updated_at": "2016-06-14T15:02:04.415Z", @@ -65,9 +65,9 @@ "group_milestone_id": null, "milestone": { "id": 1, - "title": "test milestone", + "title": "Project milestone", "project_id": 8, - "description": "test milestone", + "description": "Project-level milestone", "due_date": null, "created_at": "2016-06-14T15:02:04.415Z", "updated_at": "2016-06-14T15:02:04.415Z", @@ -76,27 +76,6 @@ "group_id": null }, "label_links": [ - { - "id": 11, - "label_id": 6, - "target_id": 1, - "target_type": "Issue", - "created_at": "2017-08-15T18:37:40.795Z", - "updated_at": "2017-08-15T18:37:40.795Z", - "label": { - "id": 6, - "title": "group label", - "color": "#A8D695", - "project_id": null, - "created_at": "2017-08-15T18:37:19.698Z", - "updated_at": "2017-08-15T18:37:19.698Z", - "template": false, - "description": "", - "group_id": 5, - "type": "GroupLabel", - "priorities": [] - } - }, { "id": 11, "label_id": 2, @@ -113,7 +92,7 @@ "updated_at": "2017-08-15T18:37:19.698Z", "template": false, "description": "", - "group_id": 5, + "group_id": null, "type": "ProjectLabel", "priorities": [] } @@ -121,10 +100,6 @@ ] } ], - "snippets": [ - - ], - "hooks": [ - - ] + "snippets": [], + "hooks": [] } diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index eaaa9791d04..4301eee17dc 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -182,6 +182,53 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end end + shared_examples 'restores project successfully' do + it 'correctly restores project' do + expect(shared.errors).to be_empty + expect(restored_project_json).to be_truthy + end + end + + shared_examples 'restores project correctly' do |**results| + it 'has labels' do + expect(project.labels.size).to eq(results.fetch(:labels, 0)) + end + + it 'has label priorities' do + expect(project.labels.first.priorities).not_to be_empty + end + + it 'has milestones' do + expect(project.milestones.size).to eq(results.fetch(:milestones, 0)) + end + + it 'has issues' do + expect(project.issues.size).to eq(results.fetch(:issues, 0)) + end + + it 'has issue with group label and project label' do + labels = project.issues.first.labels + + expect(labels.where(type: "ProjectLabel").count).to eq(results.fetch(:first_issue_labels, 0)) + end + end + + shared_examples 'restores group correctly' do |**results| + it 'has group label' do + expect(project.group.labels.size).to eq(results.fetch(:labels, 0)) + end + + it 'has group milestone' do + expect(project.group.milestones.size).to eq(results.fetch(:milestones, 0)) + end + + it 'has issue with group label' do + labels = project.issues.first.labels + + expect(labels.where(type: "GroupLabel").count).to eq(results.fetch(:first_issue_labels, 0)) + end + end + context 'Light JSON' do let(:user) { create(:user) } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } @@ -190,33 +237,45 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do let(:restored_project_json) { project_tree_restorer.restore } before do - project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json") - allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') end - context 'project.json file access check' do - it 'does not read a symlink' do - Dir.mktmpdir do |tmpdir| - setup_symlink(tmpdir, 'project.json') - allow(shared).to receive(:export_path).and_call_original + context 'with a simple project' do + before do + project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json") - restored_project_json + restored_project_json + end - expect(shared.errors).to be_empty + it_behaves_like 'restores project correctly', + issues: 1, + labels: 1, + milestones: 1, + first_issue_labels: 1 + + context 'project.json file access check' do + it 'does not read a symlink' do + Dir.mktmpdir do |tmpdir| + setup_symlink(tmpdir, 'project.json') + allow(shared).to receive(:export_path).and_call_original + + restored_project_json + + expect(shared.errors).to be_empty + end end end - end - context 'when there is an existing build with build token' do - it 'restores project json correctly' do - create(:ci_build, token: 'abcd') + context 'when there is an existing build with build token' do + before do + create(:ci_build, token: 'abcd') + end - expect(restored_project_json).to be_truthy + it_behaves_like 'restores project successfully' end end - context 'with group' do + context 'with a project that has a group' do let!(:project) do create(:project, :builds_disabled, @@ -227,43 +286,22 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end before do - project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json") + project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.group.json") restored_project_json end - it 'correctly restores project' do - expect(shared.errors).to be_empty - expect(restored_project_json).to be_truthy - end + it_behaves_like 'restores project successfully' + it_behaves_like 'restores project correctly', + issues: 2, + labels: 1, + milestones: 1, + first_issue_labels: 1 - it 'has labels' do - expect(project.labels.count).to eq(2) - end - - it 'creates group label' do - expect(project.group.labels.count).to eq(1) - end - - it 'has label priorities' do - expect(project.labels.first.priorities).not_to be_empty - end - - it 'has milestones' do - expect(project.milestones.count).to eq(1) - end - - it 'has issue' do - expect(project.issues.count).to eq(1) - expect(project.issues.first.labels.count).to eq(2) - end - - it 'has issue with group label and project label' do - labels = project.issues.first.labels - - expect(labels.where(type: "GroupLabel").count).to eq(1) - expect(labels.where(type: "ProjectLabel").count).to eq(1) - end + it_behaves_like 'restores group correctly', + labels: 1, + milestones: 1, + first_issue_labels: 1 end end end From 48d339184c6fa89eb233cff9be71d56460bf5495 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 5 Oct 2017 18:15:12 +0200 Subject: [PATCH 016/104] Refactor the Google integration page --- doc/integration/google.md | 131 ++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/doc/integration/google.md b/doc/integration/google.md index d5b523e6dc0..0eb90f32b0a 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -1,83 +1,91 @@ # Google OAuth2 OmniAuth Provider -To enable the Google OAuth2 OmniAuth provider you must register your application with Google. Google will generate a client ID and secret key for you to use. +To enable the Google OAuth2 OmniAuth provider you must register your application +with Google. Google will generate a client ID and secret key for you to use. -1. Sign in to the [Google Developers Console](https://console.developers.google.com/) with the Google account you want to use to register GitLab. +## Enabling Google OAuth -1. Select "Create Project". - -1. Provide the project information - - Project name: 'GitLab' works just fine here. - - Project ID: Must be unique to all Google Developer registered applications. Google provides a randomly generated Project ID by default. You can use the randomly generated ID or choose a new one. -1. Refresh the page. You should now see your new project in the list. Click on the project. - -1. Select the "Google APIs" tab in the Overview. - -1. Select and enable the following Google APIs - listed under "Popular APIs" - - Enable `Contacts API` - - Enable `Google+ API` - -1. Select "Credentials" in the submenu. - -1. Select "Create New Client ID". +In Google's side: +1. Navigate to the [cloud resource manager](https://console.cloud.google.com/cloud-resource-manager) page +1. Select "Create Project" +1. Provide the project information: + - **Project name** - "GitLab" works just fine here. + - **Project ID** - Must be unique to all Google Developer registered applications. + Google provides a randomly generated Project ID by default. You can use + the randomly generated ID or choose a new one. +1. Refresh the page and you should see your new project in the list +1. Go to the [Google API Console](https://console.developers.google.com/apis/dashboard) +1. Select the previously created project form the upper left corner +1. Click on the project and select "Google Cloud APIs" in the overview +1. Select "Credentials" in the left menu +1. Select "Create New Client ID" 1. Fill in the required information - - Application type: "Web Application" - - Authorized JavaScript origins: This isn't really used by GitLab but go ahead and put 'https://gitlab.example.com' here. - - Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback' -1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](img/google_app.png) + - **Application type** - Choose "Web Application" + - **Authorized JavaScript origins** -This isn't really used by GitLab but go + ahead and put `https://gitlab.example.com` + - **Authorized redirect URIs** - Enter your domain name followed by the + callback URIs: -1. On your GitLab server, open the configuration file. + ``` + https://gitlab.example.com/users/auth/google_oauth2/callback + https://gitlab.example.com/google_api/auth/callback + ``` - For omnibus package: +1. Under the heading "Client ID for web application" you should see a Client ID + and Client secret. Note them down or keep this page open as you will need them + later. + +On your GitLab server: + +1. Open the configuration file. + + For Omnibus GitLab: ```sh - sudo editor /etc/gitlab/gitlab.rb + sudo editor /etc/gitlab/gitlab.rb ``` For installations from source: ```sh - cd /home/git/gitlab - - sudo -u git -H editor config/gitlab.yml + cd /home/git/gitlab + sudo -u git -H editor config/gitlab.yml ``` -1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. +1. Add the provider configuration: -1. Add the provider configuration: - - For omnibus package: + For Omnibus GitLab: ```ruby - gitlab_rails['omniauth_providers'] = [ - { - "name" => "google_oauth2", - "app_id" => "YOUR_APP_ID", - "app_secret" => "YOUR_APP_SECRET", - "args" => { "access_type" => "offline", "approval_prompt" => '' } - } - ] + gitlab_rails['omniauth_providers'] = [ + { + "name" => "google_oauth2", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", + "args" => { "access_type" => "offline", "approval_prompt" => '' } + } + ] ``` For installations from source: ``` - - { name: 'google_oauth2', app_id: 'YOUR_APP_ID', - app_secret: 'YOUR_APP_SECRET', - args: { access_type: 'offline', approval_prompt: '' } } + - { name: 'google_oauth2', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + args: { access_type: 'offline', approval_prompt: '' } } ``` -1. Change 'YOUR_APP_ID' to the client ID from the Google Developer page from step 10. - -1. Change 'YOUR_APP_SECRET' to the client secret from the Google Developer page from step 10. - -1. Make sure that you configure GitLab to use an FQDN as Google will not accept raw IP addresses. +1. Change `YOUR_APP_ID` to the client ID from the Google Developer page +1. Similarly, change `YOUR_APP_SECRET` to the client secret +1. Make sure that you configure GitLab to use an FQDN as Google will not accept + raw IP addresses. For Omnibus packages: ```ruby - external_url 'https://gitlab.example.com' + external_url 'https://gitlab.example.com' ``` For installations from source: @@ -88,21 +96,32 @@ To enable the Google OAuth2 OmniAuth provider you must register your application ``` 1. Save the configuration file. - 1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you installed GitLab via Omnibus or from source respectively. -On the sign in page there should now be a Google icon below the regular sign in form. Click the icon to begin the authentication process. Google will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in. +On the sign in page there should now be a Google icon below the regular sign in +form. Click the icon to begin the authentication process. Google will ask the +user to sign in and authorize the GitLab application. If everything goes well +the user will be returned to GitLab and will be signed in. ## Further Configuration -This further configuration is not required for Google authentication to function but it is strongly recommended. Taking these steps will increase usability for users by providing a little more recognition and branding. +This further configuration is not required for Google authentication to function +but it is strongly recommended. Taking these steps will increase usability for +users by providing a little more recognition and branding. -At this point, when users first try to authenticate to your GitLab installation with Google they will see a generic application name on the prompt screen. The prompt informs the user that "Project Default Service Account" would like to access their account. "Project Default Service Account" isn't very recognizable and may confuse or cause users to be concerned. This is easily changeable. +At this point, when users first try to authenticate to your GitLab installation +with Google they will see a generic application name on the prompt screen. The +prompt informs the user that "Project Default Service Account" would like to +access their account. "Project Default Service Account" isn't very recognizable +and may confuse or cause users to be concerned. This is easily changeable: -1. Select 'Consent screen' in the left menu. (See steps 1, 4 and 5 above for instructions on how to get here if you closed your window). -1. Scroll down until you find "Product Name". Change the product name to something more descriptive. -1. Add any additional information as you wish - homepage, logo, privacy policy, etc. None of this is required, but it may help your users. +1. Select 'Consent screen' in the left menu. (See steps 1, 4 and 5 above for + instructions on how to get here if you closed your window). +1. Scroll down until you find "Product Name". Change the product name to + something more descriptive. +1. Add any additional information as you wish - homepage, logo, privacy policy, + etc. None of this is required, but it may help your users. [reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart GitLab]: ../administration/restart_gitlab.md#installations-from-source From 002854b64585359c510d04771f949ed372eeb370 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 5 Oct 2017 18:16:21 +0200 Subject: [PATCH 017/104] Simplify overview --- doc/user/project/clusters/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 26dcc63db90..ffc80116819 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -5,8 +5,7 @@ CAUTION: **Warning:** The Cluster integration is currently in **Beta**. -Connect your project to Google Container Engine (GKE) in a few steps -without the need to provide any credentials for your Kubernetes cluster. +Connect your project to Google Container Engine (GKE) in a few steps. With a cluster associated to your project, you can use Review Apps, deploy your applications, run your pipelines, and much more in an easy way. From 3d152cd2058a928ec26efe90240b66e5e62ed677 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Thu, 5 Oct 2017 23:17:52 +0200 Subject: [PATCH 018/104] Fixed Linting Error --- app/helpers/application_helper.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 264f21a1466..466e1170181 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -41,15 +41,7 @@ module ApplicationHelper end if project.avatar_url - #if project.private? - # options[:use_original_source] = true - # image_tag project.avatar_url(use_asset_path: false), options - #else - # image_tag project.avatar_url, options - #end - image_tag project.avatar_url(use_asset_path: project.public?), options - else # generated icon project_identicon(project, options) end From 3da881b709e97556cdbaaf2a5d5e9fdd53f5d9f2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 6 Oct 2017 07:58:32 +0200 Subject: [PATCH 019/104] Remove Google sign-in requirement --- doc/user/project/clusters/index.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index ffc80116819..e5006e6723c 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -23,8 +23,6 @@ prerequisites must be met: - The [Google authentication integration](../../../integration/google.md) must be enabled in GitLab at the instance level. If that's not the case, ask your administrator to enable it. -- Your Google account must be associated with your GitLab one. If you haven't - done this already, you will be asked in the **Cluster** page. - Your associated Google account must have the right privileges to manage clusters on GKE. - You must have Master [permissions] in order to be able to access the **Cluster** @@ -32,10 +30,6 @@ prerequisites must be met: If all of the above requirements are met, you can proceed to add a new cluster. -TIP: **Tip:** -Check your existing connected accounts by navigating to your account's settings -under the **Account > Social sign-in** section. - ## Adding a cluster NOTE: **Note:** From 53e5b305c316506c952c244341804e66d38d1c27 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 6 Oct 2017 08:01:30 +0200 Subject: [PATCH 020/104] Fix broken link --- doc/user/project/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 5feb4a61714..97d0d529886 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -63,7 +63,7 @@ common actions on issues or merge requests browse, and download job artifacts - [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job), timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more - - [GKE cluster integration](cluster/index.md): Connecting your GitLab project + - [GKE cluster integration](clusters/index.md): Connecting your GitLab project with Google Container Engine - [GitLab Pages](pages/index.md): Build, test, and deploy your static website with GitLab Pages From 9f228449a53e1cc5661aba2645a49ecbbf4d5794 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Fri, 6 Oct 2017 08:14:52 +0200 Subject: [PATCH 021/104] Removed 2 uncommented lines --- app/helpers/groups_helper.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 60ac4c63e62..b366e627e01 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -11,8 +11,6 @@ module GroupsHelper can?(current_user, :change_share_with_group_lock, group) end - # = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') - # = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' def group_icon(group, options = {}) img_path = group_icon_url(group, options) image_tag img_path, options From a0b8275da5ba493369bd10e9ae9e5d17d009ac4a Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Thu, 5 Oct 2017 16:41:06 -0400 Subject: [PATCH 022/104] adding the new spec file --- .../gitlab/import_export/project.group.json | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 spec/lib/gitlab/import_export/project.group.json diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json new file mode 100644 index 00000000000..82a1fbd2fc5 --- /dev/null +++ b/spec/lib/gitlab/import_export/project.group.json @@ -0,0 +1,188 @@ +{ + "description": "Nisi et repellendus ut enim quo accusamus vel magnam.", + "visibility_level": 10, + "archived": false, + "milestones": [ + { + "id": 1, + "title": "Project milestone", + "project_id": 8, + "description": "Project-level milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "group_id": null + } + ], + "labels": [ + { + "id": 2, + "title": "project label", + "color": "#428bca", + "project_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "type": "ProjectLabel", + "priorities": [ + { + "id": 1, + "project_id": 5, + "label_id": 1, + "priority": 1, + "created_at": "2016-10-18T09:35:43.338Z", + "updated_at": "2016-10-18T09:35:43.338Z" + } + ] + } + ], + "issues": [ + { + "id": 1, + "title": "Fugiat est minima quae maxime non similique.", + "assignee_id": null, + "project_id": 8, + "author_id": 1, + "created_at": "2017-07-07T18:13:01.138Z", + "updated_at": "2017-08-15T18:37:40.807Z", + "branch_name": null, + "description": "Quam totam fuga numquam in eveniet.", + "state": "opened", + "iid": 1, + "updated_by_id": 1, + "confidential": false, + "deleted_at": null, + "due_date": null, + "moved_to_id": null, + "lock_version": null, + "time_estimate": 0, + "closed_at": null, + "last_edited_at": null, + "last_edited_by_id": null, + "group_milestone_id": null, + "milestone": { + "id": 1, + "title": "Project milestone", + "project_id": 8, + "description": "Project-level milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "group_id": null + }, + "label_links": [ + { + "id": 11, + "label_id": 6, + "target_id": 1, + "target_type": "Issue", + "created_at": "2017-08-15T18:37:40.795Z", + "updated_at": "2017-08-15T18:37:40.795Z", + "label": { + "id": 6, + "title": "group label", + "color": "#A8D695", + "project_id": null, + "created_at": "2017-08-15T18:37:19.698Z", + "updated_at": "2017-08-15T18:37:19.698Z", + "template": false, + "description": "", + "group_id": 5, + "type": "GroupLabel", + "priorities": [] + } + }, + { + "id": 11, + "label_id": 2, + "target_id": 1, + "target_type": "Issue", + "created_at": "2017-08-15T18:37:40.795Z", + "updated_at": "2017-08-15T18:37:40.795Z", + "label": { + "id": 6, + "title": "project label", + "color": "#A8D695", + "project_id": null, + "created_at": "2017-08-15T18:37:19.698Z", + "updated_at": "2017-08-15T18:37:19.698Z", + "template": false, + "description": "", + "group_id": 5, + "type": "ProjectLabel", + "priorities": [] + } + } + ] + }, + { + "id": 2, + "title": "Fugiat est minima quae maxime non similique.", + "assignee_id": null, + "project_id": 8, + "author_id": 1, + "created_at": "2017-07-07T18:13:01.138Z", + "updated_at": "2017-08-15T18:37:40.807Z", + "branch_name": null, + "description": "Quam totam fuga numquam in eveniet.", + "state": "opened", + "iid": 2, + "updated_by_id": 1, + "confidential": false, + "deleted_at": null, + "due_date": null, + "moved_to_id": null, + "lock_version": null, + "time_estimate": 0, + "closed_at": null, + "last_edited_at": null, + "last_edited_by_id": null, + "group_milestone_id": null, + "milestone": { + "id": 2, + "title": "A group milestone", + "description": "Group-level milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "group_id": 100 + }, + "label_links": [ + { + "id": 11, + "label_id": 2, + "target_id": 1, + "target_type": "Issue", + "created_at": "2017-08-15T18:37:40.795Z", + "updated_at": "2017-08-15T18:37:40.795Z", + "label": { + "id": 2, + "title": "project label", + "color": "#A8D695", + "project_id": null, + "created_at": "2017-08-15T18:37:19.698Z", + "updated_at": "2017-08-15T18:37:19.698Z", + "template": false, + "description": "", + "group_id": 5, + "type": "ProjectLabel", + "priorities": [] + } + } + ] + } + ], + "snippets": [ + + ], + "hooks": [ + + ] +} From 509d9f0120a54716103d410178a7344779d94e52 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 6 Oct 2017 14:41:43 +0200 Subject: [PATCH 023/104] Refine Google integration steps --- doc/integration/google.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/integration/google.md b/doc/integration/google.md index 0eb90f32b0a..52de5dc61be 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -8,7 +8,7 @@ with Google. Google will generate a client ID and secret key for you to use. In Google's side: 1. Navigate to the [cloud resource manager](https://console.cloud.google.com/cloud-resource-manager) page -1. Select "Create Project" +1. Select **Create Project** 1. Provide the project information: - **Project name** - "GitLab" works just fine here. - **Project ID** - Must be unique to all Google Developer registered applications. @@ -17,24 +17,25 @@ In Google's side: 1. Refresh the page and you should see your new project in the list 1. Go to the [Google API Console](https://console.developers.google.com/apis/dashboard) 1. Select the previously created project form the upper left corner -1. Click on the project and select "Google Cloud APIs" in the overview -1. Select "Credentials" in the left menu -1. Select "Create New Client ID" +1. Select **Credentials** from the sidebar +1. Select **OAuth consent screen** and fill the form with the required information +1. In the **Credentials** tab, select **Create credentials > OAuth client ID** 1. Fill in the required information - **Application type** - Choose "Web Application" + - **Name** - Use the default one or provide your own - **Authorized JavaScript origins** -This isn't really used by GitLab but go ahead and put `https://gitlab.example.com` - **Authorized redirect URIs** - Enter your domain name followed by the - callback URIs: + callback URIs one at a time: ``` https://gitlab.example.com/users/auth/google_oauth2/callback https://gitlab.example.com/google_api/auth/callback ``` -1. Under the heading "Client ID for web application" you should see a Client ID - and Client secret. Note them down or keep this page open as you will need them - later. +1. You should now be able to see a Client ID and Client secret. Note them down + or keep this page open as you will need them later. +1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Google Cloud APIs > Container Engine API > Enable** On your GitLab server: From de025ad2dbd613dc41867db5cc60fa770de41253 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Fri, 6 Oct 2017 09:11:29 -0400 Subject: [PATCH 024/104] fixing group label import --- lib/gitlab/import_export/relation_factory.rb | 44 ++++++++++++------- .../gitlab/import_export/project.light.json | 4 +- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index b936873f1d7..352d5032af4 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -56,22 +56,21 @@ module Gitlab private def setup_models - if @relation_name == :notes - set_note_author - - # attachment is deprecated and note uploads are handled by Markdown uploader - @relation_hash['attachment'] = nil + case @relation_name + when :merge_request_diff then setup_st_diff_commits + when :merge_request_diff_files then setup_diff + when :notes then setup_note + when :project_label, :project_labels then setup_label + when :milestone, :milestones then setup_milestone + else + @relation_hash['project_id'] = @project.id end update_user_references update_project_references - handle_group_label if group_label? reset_tokens! remove_encrypted_attributes! - - set_st_diff_commits if @relation_name == :merge_request_diff - set_diff if @relation_name == :merge_request_diff_files end def update_user_references @@ -82,6 +81,12 @@ module Gitlab end end + def setup_note + set_note_author + # attachment is deprecated and note uploads are handled by Markdown uploader + @relation_hash['attachment'] = nil + end + # Sets the author for a note. If the user importing the project # has admin access, an actual mapping with new project members # will be used. Otherwise, a note stating the original author name @@ -134,11 +139,9 @@ module Gitlab @relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id'] end - def group_label? - @relation_hash['type'] == 'GroupLabel' - end + def setup_label + return unless @relation_hash['type'] == 'GroupLabel' - def handle_group_label # If there's no group, move the label to a project label if @relation_hash['group_id'] @relation_hash['project_id'] = nil @@ -148,6 +151,14 @@ module Gitlab end end + def setup_milestone + if @relation_hash['group_id'] + @relation_hash['group_id'] = @project.group.id + else + @relation_hash['project_id'] = @project.id + end + end + def reset_tokens! return unless Gitlab::ImportExport.reset_tokens? && TOKEN_RESET_MODELS.include?(@relation_name.to_s) @@ -196,14 +207,14 @@ module Gitlab relation_class: relation_class) end - def set_st_diff_commits + def setup_st_diff_commits @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs') HashUtil.deep_symbolize_array!(@relation_hash['st_diffs']) HashUtil.deep_symbolize_array_with_date!(@relation_hash['st_commits']) end - def set_diff + def setup_diff @relation_hash['diff'] = @relation_hash.delete('utf8_diff') end @@ -248,8 +259,7 @@ module Gitlab end def find_or_create_object! - # TODO: Trying to find how I can correctly use the correct id depending on the object's type - finder_attributes = if @relation_type == :group_label + finder_attributes = if @relation_name == :group_label %w[title group_id] elsif parsed_relation_hash['project_id'] %w[title project_id] diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json index 6c7e97a8338..02450478a77 100644 --- a/spec/lib/gitlab/import_export/project.light.json +++ b/spec/lib/gitlab/import_export/project.light.json @@ -19,7 +19,7 @@ "labels": [ { "id": 2, - "title": "test2", + "title": "A project label", "color": "#428bca", "project_id": 8, "created_at": "2016-07-22T08:55:44.161Z", @@ -85,7 +85,7 @@ "updated_at": "2017-08-15T18:37:40.795Z", "label": { "id": 6, - "title": "project label", + "title": "Another project label", "color": "#A8D695", "project_id": null, "created_at": "2017-08-15T18:37:19.698Z", From 5f41cddf80b5bcf7b409e200b21cd26761ad9afd Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Mon, 9 Oct 2017 10:45:23 +0200 Subject: [PATCH 025/104] Based on MR simplified the logic --- app/helpers/application_helper.rb | 2 +- app/helpers/groups_helper.rb | 16 ++-------------- app/models/concerns/avatarable.rb | 4 ++-- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 466e1170181..8d02d5de5c3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -41,7 +41,7 @@ module ApplicationHelper end if project.avatar_url - image_tag project.avatar_url(use_asset_path: project.public?), options + image_tag project.avatar_url, options else # generated icon project_identicon(project, options) end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index b366e627e01..655b9d02c1e 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,7 +1,4 @@ -require 'uri' - module GroupsHelper - include Gitlab::CurrentSettings def can_change_group_visibility_level?(group) can?(current_user, :change_visibility_level, group) @@ -22,12 +19,7 @@ module GroupsHelper end if group.avatar_url - if group.private? - options[:use_original_source] = true - group.avatar_url(use_asset_path: false) - else - group.avatar_url - end + group.avatar_url else # No Avatar Icon ActionController::Base.helpers.image_path('no_group_avatar.png') end @@ -107,11 +99,7 @@ module GroupsHelper link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do output = if (group.try(:avatar_url) || show_avatar) && !Rails.env.test? - if group.private? - group_icon(group, class: "avatar-tile", width: 15, height: 15, use_original_source: true) - else - group_icon(group, class: "avatar-tile", width: 15, height: 15) - end + group_icon(group, class: "avatar-tile", width: 15, height: 15) else "" end diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index dee7e47e2aa..2ec70203710 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -1,7 +1,7 @@ module Avatarable extend ActiveSupport::Concern - def avatar_path(only_path: true, use_asset_path: true) + def avatar_path(only_path: true) return unless self[:avatar].present? # If only_path is true then use the relative path of avatar. @@ -11,7 +11,7 @@ module Avatarable # If asset_host is set then it is expected that assets are handled by a standalone host. # That means we do not want to get GitLab's relative_url_root option anymore. - host = (asset_host.present? && use_asset_path) ? asset_host : gitlab_host + host = (asset_host.present? && (!respond_to?(:public?) || public?)) ? asset_host : gitlab_host [host, avatar.url].join end From 7048834004ebbd7a0a3cd9d10cd8f0aec5e676d9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 9 Oct 2017 11:13:31 +0200 Subject: [PATCH 026/104] Add link to Google billing account --- doc/user/project/clusters/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index e5006e6723c..27e2f928ce0 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -24,7 +24,9 @@ prerequisites must be met: be enabled in GitLab at the instance level. If that's not the case, ask your administrator to enable it. - Your associated Google account must have the right privileges to manage - clusters on GKE. + clusters on GKE. That would mean that a + [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account) + must be set up. - You must have Master [permissions] in order to be able to access the **Cluster** page. From 30e0262813aff896189e1c2327a6b951ce4d34ad Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 9 Oct 2017 10:42:20 +0000 Subject: [PATCH 027/104] Make naming imports more clear --- doc/development/fe_guide/style_guide_js.md | 30 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 77ae6d2a0ea..10f4c5a0902 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -88,16 +88,31 @@ followed by any global declarations, then a blank newline prior to any imports o 1. Use ES module syntax to import modules ```javascript // bad - require('foo'); + const SomeClass = require('some_class'); // good - import Foo from 'foo'; + import SomeClass from 'some_class'; // bad - module.exports = Foo; + module.exports = SomeClass; // good - export default Foo; + export default SomeClass; + ``` + + Import statements are following usual naming guidelines, for example object literals use camel case: + + ```javascript + // some_object file + export default { + key: 'value', + }; + + // bad + import ObjectLiteral from 'some_object'; + + // good + import objectLiteral from 'some_object'; ``` 1. Relative paths: when importing a module in the same directory, a child @@ -285,6 +300,13 @@ A forEach will cause side effects, it will be mutating the array being iterated. 1. **Extensions**: Use `.vue` extension for Vue components. 1. **Reference Naming**: Use camelCase for their instances: ```javascript + // bad + import CardBoard from 'cardBoard' + + components: { + CardBoard: + }; + // good import cardBoard from 'cardBoard' From 618dd9e4b265cef75f415bffc383247f65915e33 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Mon, 2 Oct 2017 20:51:20 +0200 Subject: [PATCH 028/104] Migrate Workhorse Send{Diff,Patch} to Gitaly --- GITALY_SERVER_VERSION | 2 +- lib/gitlab/workhorse.rb | 46 ++++++++++++++++----- spec/lib/gitlab/workhorse_spec.rb | 66 +++++++++++++++++++++++++------ 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 8298bb08b2d..a8ab6c9666a 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.43.0 +0.44.0 diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index f200c694562..58d5b0da1c4 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -103,11 +103,16 @@ module Gitlab end def send_git_diff(repository, diff_refs) - params = { - 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => diff_refs.base_sha, - 'ShaTo' => diff_refs.head_sha - } + params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_diff) + { + 'GitalyServer' => gitaly_server_hash(repository), + 'RawDiffRequest' => Gitaly::RawDiffRequest.new( + gitaly_diff_or_patch_hash(repository, diff_refs) + ).to_json + } + else + workhorse_diff_or_patch_hash(repository, diff_refs) + end [ SEND_DATA_HEADER, @@ -116,11 +121,16 @@ module Gitlab end def send_git_patch(repository, diff_refs) - params = { - 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => diff_refs.base_sha, - 'ShaTo' => diff_refs.head_sha - } + params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_send_git_patch) + { + 'GitalyServer' => gitaly_server_hash(repository), + 'RawPatchRequest' => Gitaly::RawPatchRequest.new( + gitaly_diff_or_patch_hash(repository, diff_refs) + ).to_json + } + else + workhorse_diff_or_patch_hash(repository, diff_refs) + end [ SEND_DATA_HEADER, @@ -216,6 +226,22 @@ module Gitlab token: Gitlab::GitalyClient.token(repository.project.repository_storage) } end + + def workhorse_diff_or_patch_hash(repository, diff_refs) + { + 'RepoPath' => repository.path_to_repo, + 'ShaFrom' => diff_refs.base_sha, + 'ShaTo' => diff_refs.head_sha + } + end + + def gitaly_diff_or_patch_hash(repository, diff_refs) + { + repository: repository.gitaly_repository, + left_commit_id: diff_refs.base_sha, + right_commit_id: diff_refs.head_sha + } + end end end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 4dffe2bd82f..ebc0202df9f 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -66,12 +66,34 @@ describe Gitlab::Workhorse do let(:diff_refs) { double(base_sha: "base", head_sha: "head") } subject { described_class.send_git_patch(repository, diff_refs) } - it 'sets the header correctly' do - key, command, params = decode_workhorse_header(subject) + context 'when Gitaly workhorse_send_git_patch feature is enabled' do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) - expect(key).to eq("Gitlab-Workhorse-Send-Data") - expect(command).to eq("git-format-patch") - expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-format-patch") + expect(params).to eq({ + 'GitalyServer' => { + address: Gitlab::GitalyClient.address(project.repository_storage), + token: Gitlab::GitalyClient.token(project.repository_storage) + }, + 'RawPatchRequest' => Gitaly::RawPatchRequest.new( + repository: repository.gitaly_repository, + left_commit_id: 'base', + right_commit_id: 'head' + ).to_json + }.deep_stringify_keys) + end + end + + context 'when Gitaly workhorse_send_git_patch feature is disabled', skip_gitaly_mock: true do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-format-patch") + expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + end end end @@ -115,14 +137,36 @@ describe Gitlab::Workhorse do describe '.send_git_diff' do let(:diff_refs) { double(base_sha: "base", head_sha: "head") } - subject { described_class.send_git_patch(repository, diff_refs) } + subject { described_class.send_git_diff(repository, diff_refs) } - it 'sets the header correctly' do - key, command, params = decode_workhorse_header(subject) + context 'when Gitaly workhorse_send_git_diff feature is enabled' do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) - expect(key).to eq("Gitlab-Workhorse-Send-Data") - expect(command).to eq("git-format-patch") - expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-diff") + expect(params).to eq({ + 'GitalyServer' => { + address: Gitlab::GitalyClient.address(project.repository_storage), + token: Gitlab::GitalyClient.token(project.repository_storage) + }, + 'RawDiffRequest' => Gitaly::RawDiffRequest.new( + repository: repository.gitaly_repository, + left_commit_id: 'base', + right_commit_id: 'head' + ).to_json + }.deep_stringify_keys) + end + end + + context 'when Gitaly workhorse_send_git_diff feature is disabled', skip_gitaly_mock: true do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-diff") + expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + end end end From 59c6ddcdefb813487d552499ebcb5504b5fde548 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Mon, 9 Oct 2017 17:05:51 +0200 Subject: [PATCH 029/104] Fix for Lint Error + Upload Test --- app/helpers/groups_helper.rb | 1 - spec/models/project_spec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 655b9d02c1e..f5ef9c53a7a 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,5 +1,4 @@ module GroupsHelper - def can_change_group_visibility_level?(group) can?(current_user, :change_visibility_level, group) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 176bb568cbe..fef89999b85 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -844,7 +844,7 @@ describe Project do let(:project) { create(:project) } context 'when avatar file is uploaded' do - let(:project) { create(:project, :with_avatar) } + let(:project) { create(:project, :public, :with_avatar) } let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } From 1731934398aedfec89ad006818b55641dc1f789c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Mon, 9 Oct 2017 18:25:24 -0300 Subject: [PATCH 030/104] Add `Gitlab::Git::Repository#fetch` command --- lib/gitlab/git/repository.rb | 6 ++++++ spec/lib/gitlab/git/repository_spec.rb | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 0f059bef808..c49065c38bc 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1082,6 +1082,12 @@ module Gitlab @has_visible_content = has_local_branches? end + def fetch(remote = 'origin') + args = %W(#{Gitlab.config.git.bin_path} fetch #{remote}) + + popen(args, @path).last.zero? + end + def gitaly_repository Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository) end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 1ee4acfd193..ca3b6cc2d73 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1489,6 +1489,21 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + describe '#fetch' do + let(:git_path) { Gitlab.config.git.bin_path } + let(:remote_name) { 'my_remote' } + + subject { repository.fetch(remote_name) } + + it 'fetches the remote and returns true if the command was successful' do + expect(repository).to receive(:popen) + .with(%W(#{git_path} fetch #{remote_name}), repository.path) + .and_return(['', 0]) + + expect(subject).to be(true) + end + end + def create_remote_branch(repository, remote_name, branch_name, source_branch_name) source_branch = repository.branches.find { |branch| branch.name == source_branch_name } rugged = repository.rugged From 4a13fcf80365060b06893b71d93a64e5a1836b9a Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 9 Oct 2017 22:03:21 +0000 Subject: [PATCH 031/104] fix incorrect description for advanced settings section of project settings --- app/views/projects/edit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 8ae4fd94146..893e536e289 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -97,7 +97,7 @@ %button.btn.js-settings-toggle = expanded ? 'Collapse' : 'Expand' %p - Perform advanced options such as housekeeping, exporting, archiving, renaming, transferring, or removing your project. + Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project. .settings-content.no-animate{ class: ('expanded' if expanded) } .sub-section %h4 Housekeeping From 41b430b2cfa6d16e644d14164234f890ea17c744 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 9 Oct 2017 23:21:56 +0100 Subject: [PATCH 032/104] Remove u2f from globalnamespace --- app/assets/javascripts/dispatcher.js | 7 +- app/assets/javascripts/main.js | 6 - app/assets/javascripts/two_factor_auth.js | 3 +- app/assets/javascripts/u2f/authenticate.js | 188 ++++++++++----------- app/assets/javascripts/u2f/error.js | 43 +++-- app/assets/javascripts/u2f/register.js | 155 ++++++++--------- app/assets/javascripts/u2f/util.js | 15 +- spec/javascripts/u2f/authenticate_spec.js | 113 ++++++------- spec/javascripts/u2f/mock_u2f_device.js | 53 +++--- spec/javascripts/u2f/register_spec.js | 120 ++++++------- 10 files changed, 325 insertions(+), 378 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 1edd460f380..ad4bdeba01e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -78,6 +78,7 @@ import initChangesDropdown from './init_changes_dropdown'; import AbuseReports from './abuse_reports'; import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import AjaxLoadingSpinner from './ajax_loading_spinner'; +import U2FAuthenticate from './u2f/authenticate'; (function() { var Dispatcher; @@ -535,14 +536,16 @@ import AjaxLoadingSpinner from './ajax_loading_spinner'; case 'sessions': case 'omniauth_callbacks': if (!gon.u2f) break; - gl.u2fAuthenticate = new gl.U2FAuthenticate( + const u2fAuthenticate = new U2FAuthenticate( $('#js-authenticate-u2f'), '#js-login-u2f-form', gon.u2f, document.querySelector('#js-login-2fa-device'), document.querySelector('.js-2fa-form'), ); - gl.u2fAuthenticate.start(); + u2fAuthenticate.start(); + // needed in rspec + gl.u2fAuthenticate = u2fAuthenticate; case 'admin': new Admin(); switch (path[1]) { diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 853546b617b..7c703f7be9d 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -47,12 +47,6 @@ import './lib/utils/url_utility'; // behaviors import './behaviors/'; -// u2f -import './u2f/authenticate'; -import './u2f/error'; -import './u2f/register'; -import './u2f/util'; - // everything else import './activities'; import './admin'; diff --git a/app/assets/javascripts/two_factor_auth.js b/app/assets/javascripts/two_factor_auth.js index d26f61562a5..e3414d9afff 100644 --- a/app/assets/javascripts/two_factor_auth.js +++ b/app/assets/javascripts/two_factor_auth.js @@ -1,4 +1,5 @@ -/* global U2FRegister */ +import U2FRegister from './u2f/register'; + document.addEventListener('DOMContentLoaded', () => { const twoFactorNode = document.querySelector('.js-two-factor-auth'); const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true'; diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index 8821b22477f..a3cc04e35fe 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -1,118 +1,108 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, prefer-arrow-callback, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, max-len */ +/* eslint-disable func-names, wrap-iife */ /* global u2f */ -/* global U2FError */ -/* global U2FUtil */ - import _ from 'underscore'; +import isU2FSupported from './util'; +import U2FError from './error'; // Authenticate U2F (universal 2nd factor) devices for users to authenticate with. // // State Flow #1: setup -> in_progress -> authenticated -> POST to server // State Flow #2: setup -> in_progress -> error -> setup -(function() { - const global = window.gl || (window.gl = {}); - - global.U2FAuthenticate = (function() { - function U2FAuthenticate(container, form, u2fParams, fallbackButton, fallbackUI) { - this.container = container; - this.renderNotSupported = this.renderNotSupported.bind(this); - this.renderAuthenticated = this.renderAuthenticated.bind(this); - this.renderError = this.renderError.bind(this); - this.renderInProgress = this.renderInProgress.bind(this); - this.renderTemplate = this.renderTemplate.bind(this); - this.authenticate = this.authenticate.bind(this); - this.start = this.start.bind(this); - this.appId = u2fParams.app_id; - this.challenge = u2fParams.challenge; - this.form = form; - this.fallbackButton = fallbackButton; - this.fallbackUI = fallbackUI; - if (this.fallbackButton) this.fallbackButton.addEventListener('click', this.switchToFallbackUI.bind(this)); - this.signRequests = u2fParams.sign_requests.map(function(request) { - // The U2F Javascript API v1.1 requires a single challenge, with - // _no challenges per-request_. The U2F Javascript API v1.0 requires a - // challenge per-request, which is done by copying the single challenge - // into every request. - // - // In either case, we don't need the per-request challenges that the server - // has generated, so we can remove them. - // - // Note: The server library fixes this behaviour in (unreleased) version 1.0.0. - // This can be removed once we upgrade. - // https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4 - return _(request).omit('challenge'); - }); +export default class U2FAuthenticate { + constructor(container, form, u2fParams, fallbackButton, fallbackUI) { + this.container = container; + this.renderNotSupported = this.renderNotSupported.bind(this); + this.renderAuthenticated = this.renderAuthenticated.bind(this); + this.renderError = this.renderError.bind(this); + this.renderInProgress = this.renderInProgress.bind(this); + this.renderTemplate = this.renderTemplate.bind(this); + this.authenticate = this.authenticate.bind(this); + this.start = this.start.bind(this); + this.appId = u2fParams.app_id; + this.challenge = u2fParams.challenge; + this.form = form; + this.fallbackButton = fallbackButton; + this.fallbackUI = fallbackUI; + if (this.fallbackButton) { + this.fallbackButton.addEventListener('click', this.switchToFallbackUI.bind(this)); } - U2FAuthenticate.prototype.start = function() { - if (U2FUtil.isU2FSupported()) { - return this.renderInProgress(); - } else { - return this.renderNotSupported(); - } - }; + // The U2F Javascript API v1.1 requires a single challenge, with + // _no challenges per-request_. The U2F Javascript API v1.0 requires a + // challenge per-request, which is done by copying the single challenge + // into every request. + // + // In either case, we don't need the per-request challenges that the server + // has generated, so we can remove them. + // + // Note: The server library fixes this behaviour in (unreleased) version 1.0.0. + // This can be removed once we upgrade. + // https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4 + this.signRequests = u2fParams.sign_requests.map(request => _(request).omit('challenge')); - U2FAuthenticate.prototype.authenticate = function() { - return u2f.sign(this.appId, this.challenge, this.signRequests, (function(_this) { - return function(response) { - var error; - if (response.errorCode) { - error = new U2FError(response.errorCode, 'authenticate'); - return _this.renderError(error); - } else { - return _this.renderAuthenticated(JSON.stringify(response)); - } - }; - })(this), 10); + this.templates = { + notSupported: '#js-authenticate-u2f-not-supported', + setup: '#js-authenticate-u2f-setup', + inProgress: '#js-authenticate-u2f-in-progress', + error: '#js-authenticate-u2f-error', + authenticated: '#js-authenticate-u2f-authenticated', }; + } - // Rendering # - U2FAuthenticate.prototype.templates = { - "notSupported": "#js-authenticate-u2f-not-supported", - "setup": '#js-authenticate-u2f-setup', - "inProgress": '#js-authenticate-u2f-in-progress', - "error": '#js-authenticate-u2f-error', - "authenticated": '#js-authenticate-u2f-authenticated' - }; + start() { + if (isU2FSupported()) { + return this.renderInProgress(); + } + return this.renderNotSupported(); + } - U2FAuthenticate.prototype.renderTemplate = function(name, params) { - var template, templateString; - templateString = $(this.templates[name]).html(); - template = _.template(templateString); - return this.container.html(template(params)); - }; + authenticate() { + return u2f.sign(this.appId, this.challenge, this.signRequests, (function (_this) { + return function (response) { + if (response.errorCode) { + const error = new U2FError(response.errorCode, 'authenticate'); + return _this.renderError(error); + } + return _this.renderAuthenticated(JSON.stringify(response)); + }; + })(this), 10); + } - U2FAuthenticate.prototype.renderInProgress = function() { - this.renderTemplate('inProgress'); - return this.authenticate(); - }; + renderTemplate(name, params) { + const templateString = $(this.templates[name]).html(); + const template = _.template(templateString); + return this.container.html(template(params)); + } - U2FAuthenticate.prototype.renderError = function(error) { - this.renderTemplate('error', { - error_message: error.message(), - error_code: error.errorCode - }); - return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress); - }; + renderInProgress() { + this.renderTemplate('inProgress'); + return this.authenticate(); + } - U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) { - this.renderTemplate('authenticated'); - const container = this.container[0]; - container.querySelector('#js-device-response').value = deviceResponse; - container.querySelector(this.form).submit(); - this.fallbackButton.classList.add('hidden'); - }; + renderError(error) { + this.renderTemplate('error', { + error_message: error.message(), + error_code: error.errorCode, + }); + return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress); + } - U2FAuthenticate.prototype.renderNotSupported = function() { - return this.renderTemplate('notSupported'); - }; + renderAuthenticated(deviceResponse) { + this.renderTemplate('authenticated'); + const container = this.container[0]; + container.querySelector('#js-device-response').value = deviceResponse; + container.querySelector(this.form).submit(); + this.fallbackButton.classList.add('hidden'); + } - U2FAuthenticate.prototype.switchToFallbackUI = function() { - this.fallbackButton.classList.add('hidden'); - this.container[0].classList.add('hidden'); - this.fallbackUI.classList.remove('hidden'); - }; + renderNotSupported() { + return this.renderTemplate('notSupported'); + } - return U2FAuthenticate; - })(); -})(); + switchToFallbackUI() { + this.fallbackButton.classList.add('hidden'); + this.container[0].classList.add('hidden'); + this.fallbackUI.classList.remove('hidden'); + } + +} diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js index 3119b3480c3..1a98564ff55 100644 --- a/app/assets/javascripts/u2f/error.js +++ b/app/assets/javascripts/u2f/error.js @@ -1,25 +1,22 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-console, quotes, prefer-template, max-len */ -/* global u2f */ +export default class U2FError { + constructor(errorCode, u2fFlowType) { + this.errorCode = errorCode; + this.message = this.message.bind(this); + this.httpsDisabled = window.location.protocol !== 'https:'; + this.u2fFlowType = u2fFlowType; + } -(function() { - this.U2FError = (function() { - function U2FError(errorCode, u2fFlowType) { - this.errorCode = errorCode; - this.message = this.message.bind(this); - this.httpsDisabled = window.location.protocol !== 'https:'; - this.u2fFlowType = u2fFlowType; - } - - U2FError.prototype.message = function() { - if (this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) { - return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.'; - } else if (this.errorCode === u2f.ErrorCodes.DEVICE_INELIGIBLE) { - if (this.u2fFlowType === 'authenticate') return 'This device has not been registered with us.'; - if (this.u2fFlowType === 'register') return 'This device has already been registered with us.'; + message() { + if (this.errorCode === window.u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) { + return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.'; + } else if (this.errorCode === window.u2f.ErrorCodes.DEVICE_INELIGIBLE) { + if (this.u2fFlowType === 'authenticate') { + return 'This device has not been registered with us.'; } - return "There was a problem communicating with your device."; - }; - - return U2FError; - })(); -}).call(window); + if (this.u2fFlowType === 'register') { + return 'This device has already been registered with us.'; + } + } + return 'There was a problem communicating with your device.'; + } +} diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 3a2534d553b..cc3f02e75f6 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -1,98 +1,89 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, max-len */ +/* eslint-disable func-names, wrap-iife */ /* global u2f */ -/* global U2FError */ -/* global U2FUtil */ import _ from 'underscore'; +import isU2FSupported from './util'; +import U2FError from './error'; // Register U2F (universal 2nd factor) devices for users to authenticate with. // // State Flow #1: setup -> in_progress -> registered -> POST to server // State Flow #2: setup -> in_progress -> error -> setup -(function() { - this.U2FRegister = (function() { - function U2FRegister(container, u2fParams) { - this.container = container; - this.renderNotSupported = this.renderNotSupported.bind(this); - this.renderRegistered = this.renderRegistered.bind(this); - this.renderError = this.renderError.bind(this); - this.renderInProgress = this.renderInProgress.bind(this); - this.renderSetup = this.renderSetup.bind(this); - this.renderTemplate = this.renderTemplate.bind(this); - this.register = this.register.bind(this); - this.start = this.start.bind(this); - this.appId = u2fParams.app_id; - this.registerRequests = u2fParams.register_requests; - this.signRequests = u2fParams.sign_requests; +export default class U2FRegister { + constructor(container, u2fParams) { + this.container = container; + this.renderNotSupported = this.renderNotSupported.bind(this); + this.renderRegistered = this.renderRegistered.bind(this); + this.renderError = this.renderError.bind(this); + this.renderInProgress = this.renderInProgress.bind(this); + this.renderSetup = this.renderSetup.bind(this); + this.renderTemplate = this.renderTemplate.bind(this); + this.register = this.register.bind(this); + this.start = this.start.bind(this); + this.appId = u2fParams.app_id; + this.registerRequests = u2fParams.register_requests; + this.signRequests = u2fParams.sign_requests; + + this.templates = { + notSupported: '#js-register-u2f-not-supported', + setup: '#js-register-u2f-setup', + inProgress: '#js-register-u2f-in-progress', + error: '#js-register-u2f-error', + registered: '#js-register-u2f-registered', + }; + } + + start() { + if (isU2FSupported()) { + return this.renderSetup(); } + return this.renderNotSupported(); + } - U2FRegister.prototype.start = function() { - if (U2FUtil.isU2FSupported()) { - return this.renderSetup(); - } else { - return this.renderNotSupported(); - } - }; + register() { + return u2f.register(this.appId, this.registerRequests, this.signRequests, (function (_this) { + return function (response) { + if (response.errorCode) { + const error = new U2FError(response.errorCode, 'register'); + return _this.renderError(error); + } + return _this.renderRegistered(JSON.stringify(response)); + }; + })(this), 10); + } - U2FRegister.prototype.register = function() { - return u2f.register(this.appId, this.registerRequests, this.signRequests, (function(_this) { - return function(response) { - var error; - if (response.errorCode) { - error = new U2FError(response.errorCode, 'register'); - return _this.renderError(error); - } else { - return _this.renderRegistered(JSON.stringify(response)); - } - }; - })(this), 10); - }; + renderTemplate(name, params) { + const templateString = $(this.templates[name]).html(); + const template = _.template(templateString); + return this.container.html(template(params)); + } - // Rendering # - U2FRegister.prototype.templates = { - "notSupported": "#js-register-u2f-not-supported", - "setup": '#js-register-u2f-setup', - "inProgress": '#js-register-u2f-in-progress', - "error": '#js-register-u2f-error', - "registered": '#js-register-u2f-registered' - }; + renderSetup() { + this.renderTemplate('setup'); + return this.container.find('#js-setup-u2f-device').on('click', this.renderInProgress); + } - U2FRegister.prototype.renderTemplate = function(name, params) { - var template, templateString; - templateString = $(this.templates[name]).html(); - template = _.template(templateString); - return this.container.html(template(params)); - }; + renderInProgress() { + this.renderTemplate('inProgress'); + return this.register(); + } - U2FRegister.prototype.renderSetup = function() { - this.renderTemplate('setup'); - return this.container.find('#js-setup-u2f-device').on('click', this.renderInProgress); - }; + renderError(error) { + this.renderTemplate('error', { + error_message: error.message(), + error_code: error.errorCode, + }); + return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); + } - U2FRegister.prototype.renderInProgress = function() { - this.renderTemplate('inProgress'); - return this.register(); - }; + renderRegistered(deviceResponse) { + this.renderTemplate('registered'); + // Prefer to do this instead of interpolating using Underscore templates + // because of JSON escaping issues. + return this.container.find('#js-device-response').val(deviceResponse); + } - U2FRegister.prototype.renderError = function(error) { - this.renderTemplate('error', { - error_message: error.message(), - error_code: error.errorCode - }); - return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); - }; - - U2FRegister.prototype.renderRegistered = function(deviceResponse) { - this.renderTemplate('registered'); - // Prefer to do this instead of interpolating using Underscore templates - // because of JSON escaping issues. - return this.container.find("#js-device-response").val(deviceResponse); - }; - - U2FRegister.prototype.renderNotSupported = function() { - return this.renderTemplate('notSupported'); - }; - - return U2FRegister; - })(); -}).call(window); + renderNotSupported() { + return this.renderTemplate('notSupported'); + } +} diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js index 813d363db00..9771ff935c2 100644 --- a/app/assets/javascripts/u2f/util.js +++ b/app/assets/javascripts/u2f/util.js @@ -1,12 +1,3 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife */ -(function() { - this.U2FUtil = (function() { - function U2FUtil() {} - - U2FUtil.isU2FSupported = function() { - return window.u2f; - }; - - return U2FUtil; - })(); -}).call(window); +export default function isU2FSupported() { + return window.u2f; +} diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index a160c86308d..29b15f3a782 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -1,72 +1,63 @@ -/* eslint-disable space-before-function-paren, new-parens, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, max-len */ -/* global MockU2FDevice */ -/* global U2FAuthenticate */ - -import '~/u2f/authenticate'; -import '~/u2f/util'; -import '~/u2f/error'; +import U2FAuthenticate from '~/u2f/authenticate'; import 'vendor/u2f'; -import './mock_u2f_device'; +import MockU2FDevice from './mock_u2f_device'; -(function() { - describe('U2FAuthenticate', function() { - preloadFixtures('u2f/authenticate.html.raw'); +describe('U2FAuthenticate', () => { + preloadFixtures('u2f/authenticate.html.raw'); - beforeEach(function() { - loadFixtures('u2f/authenticate.html.raw'); - this.u2fDevice = new MockU2FDevice; - this.container = $("#js-authenticate-u2f"); - this.component = new window.gl.U2FAuthenticate( - this.container, - '#js-login-u2f-form', - { - sign_requests: [] - }, - document.querySelector('#js-login-2fa-device'), - document.querySelector('.js-2fa-form') - ); + beforeEach(() => { + loadFixtures('u2f/authenticate.html.raw'); + this.u2fDevice = new MockU2FDevice(); + this.container = $('#js-authenticate-u2f'); + this.component = new U2FAuthenticate( + this.container, + '#js-login-u2f-form', + { + sign_requests: [], + }, + document.querySelector('#js-login-2fa-device'), + document.querySelector('.js-2fa-form'), + ); - // bypass automatic form submission within renderAuthenticated - spyOn(this.component, 'renderAuthenticated').and.returnValue(true); + // bypass automatic form submission within renderAuthenticated + spyOn(this.component, 'renderAuthenticated').and.returnValue(true); - return this.component.start(); + return this.component.start(); + }); + + it('allows authenticating via a U2F device', () => { + const inProgressMessage = this.container.find('p'); + expect(inProgressMessage.text()).toContain('Trying to communicate with your device'); + this.u2fDevice.respondToAuthenticateRequest({ + deviceData: 'this is data from the device', }); - it('allows authenticating via a U2F device', function() { - var inProgressMessage; - inProgressMessage = this.container.find("p"); - expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); + expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); + }); + + return describe('errors', () => { + it('displays an error message', () => { + const setupButton = this.container.find('#js-login-u2f-device'); + setupButton.trigger('click'); this.u2fDevice.respondToAuthenticateRequest({ - deviceData: "this is data from the device" + errorCode: 'error!', + }); + const errorMessage = this.container.find('p'); + return expect(errorMessage.text()).toContain('There was a problem communicating with your device'); + }); + return it('allows retrying authentication after an error', () => { + let setupButton = this.container.find('#js-login-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToAuthenticateRequest({ + errorCode: 'error!', + }); + const retryButton = this.container.find('#js-u2f-try-again'); + retryButton.trigger('click'); + setupButton = this.container.find('#js-login-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToAuthenticateRequest({ + deviceData: 'this is data from the device', }); expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); }); - return describe("errors", function() { - it("displays an error message", function() { - var errorMessage, setupButton; - setupButton = this.container.find("#js-login-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToAuthenticateRequest({ - errorCode: "error!" - }); - errorMessage = this.container.find("p"); - return expect(errorMessage.text()).toContain("There was a problem communicating with your device"); - }); - return it("allows retrying authentication after an error", function() { - var retryButton, setupButton; - setupButton = this.container.find("#js-login-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToAuthenticateRequest({ - errorCode: "error!" - }); - retryButton = this.container.find("#js-u2f-try-again"); - retryButton.trigger('click'); - setupButton = this.container.find("#js-login-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToAuthenticateRequest({ - deviceData: "this is data from the device" - }); - expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); - }); - }); }); -}).call(window); +}); diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 4eb8ad3d9e4..5a1ace2b4d6 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,31 +1,28 @@ -/* eslint-disable space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, max-len */ +/* eslint-disable prefer-rest-params, wrap-iife, +no-unused-expressions, no-return-assign, no-param-reassign*/ -(function() { - this.MockU2FDevice = (function() { - function MockU2FDevice() { - this.respondToAuthenticateRequest = this.respondToAuthenticateRequest.bind(this); - this.respondToRegisterRequest = this.respondToRegisterRequest.bind(this); - window.u2f || (window.u2f = {}); - window.u2f.register = (function(_this) { - return function(appId, registerRequests, signRequests, callback) { - return _this.registerCallback = callback; - }; - })(this); - window.u2f.sign = (function(_this) { - return function(appId, challenges, signRequests, callback) { - return _this.authenticateCallback = callback; - }; - })(this); - } +export default class MockU2FDevice { + constructor() { + this.respondToAuthenticateRequest = this.respondToAuthenticateRequest.bind(this); + this.respondToRegisterRequest = this.respondToRegisterRequest.bind(this); + window.u2f || (window.u2f = {}); + window.u2f.register = (function (_this) { + return function (appId, registerRequests, signRequests, callback) { + return _this.registerCallback = callback; + }; + })(this); + window.u2f.sign = (function (_this) { + return function (appId, challenges, signRequests, callback) { + return _this.authenticateCallback = callback; + }; + })(this); + } - MockU2FDevice.prototype.respondToRegisterRequest = function(params) { - return this.registerCallback(params); - }; + respondToRegisterRequest(params) { + return this.registerCallback(params); + } - MockU2FDevice.prototype.respondToAuthenticateRequest = function(params) { - return this.authenticateCallback(params); - }; - - return MockU2FDevice; - })(); -}).call(window); + respondToAuthenticateRequest(params) { + return this.authenticateCallback(params); + } +} diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index a445c80f2af..b0051f11362 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -1,77 +1,69 @@ -/* eslint-disable space-before-function-paren, new-parens, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, max-len */ -/* global MockU2FDevice */ -/* global U2FRegister */ - -import '~/u2f/register'; -import '~/u2f/util'; -import '~/u2f/error'; +import U2FRegister from '~/u2f/register'; import 'vendor/u2f'; -import './mock_u2f_device'; +import MockU2FDevice from './mock_u2f_device'; -(function() { - describe('U2FRegister', function() { - preloadFixtures('u2f/register.html.raw'); +describe('U2FRegister', () => { + preloadFixtures('u2f/register.html.raw'); - beforeEach(function() { - loadFixtures('u2f/register.html.raw'); - this.u2fDevice = new MockU2FDevice; - this.container = $("#js-register-u2f"); - this.component = new U2FRegister(this.container, $("#js-register-u2f-templates"), {}, "token"); - return this.component.start(); + beforeEach(() => { + loadFixtures('u2f/register.html.raw'); + this.u2fDevice = new MockU2FDevice(); + this.container = $('#js-register-u2f'); + this.component = new U2FRegister(this.container, $('#js-register-u2f-templates'), {}, 'token'); + return this.component.start(); + }); + + it('allows registering a U2F device', () => { + const setupButton = this.container.find('#js-setup-u2f-device'); + expect(setupButton.text()).toBe('Setup new U2F device'); + setupButton.trigger('click'); + const inProgressMessage = this.container.children('p'); + expect(inProgressMessage.text()).toContain('Trying to communicate with your device'); + this.u2fDevice.respondToRegisterRequest({ + deviceData: 'this is data from the device', }); - it('allows registering a U2F device', function() { - var deviceResponse, inProgressMessage, registeredMessage, setupButton; - setupButton = this.container.find("#js-setup-u2f-device"); - expect(setupButton.text()).toBe('Setup new U2F device'); + const registeredMessage = this.container.find('p'); + const deviceResponse = this.container.find('#js-device-response'); + expect(registeredMessage.text()).toContain('Your device was successfully set up!'); + return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}'); + }); + + return describe('errors', () => { + it('doesn\'t allow the same device to be registered twice (for the same user', () => { + const setupButton = this.container.find('#js-setup-u2f-device'); setupButton.trigger('click'); - inProgressMessage = this.container.children("p"); - expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); this.u2fDevice.respondToRegisterRequest({ - deviceData: "this is data from the device" + errorCode: 4, }); - registeredMessage = this.container.find('p'); - deviceResponse = this.container.find('#js-device-response'); - expect(registeredMessage.text()).toContain("Your device was successfully set up!"); - return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}'); + const errorMessage = this.container.find('p'); + return expect(errorMessage.text()).toContain('already been registered with us'); }); - return describe("errors", function() { - it("doesn't allow the same device to be registered twice (for the same user", function() { - var errorMessage, setupButton; - setupButton = this.container.find("#js-setup-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToRegisterRequest({ - errorCode: 4 - }); - errorMessage = this.container.find("p"); - return expect(errorMessage.text()).toContain("already been registered with us"); + + it('displays an error message for other errors', () => { + const setupButton = this.container.find('#js-setup-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToRegisterRequest({ + errorCode: 'error!', }); - it("displays an error message for other errors", function() { - var errorMessage, setupButton; - setupButton = this.container.find("#js-setup-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToRegisterRequest({ - errorCode: "error!" - }); - errorMessage = this.container.find("p"); - return expect(errorMessage.text()).toContain("There was a problem communicating with your device"); + const errorMessage = this.container.find('p'); + return expect(errorMessage.text()).toContain('There was a problem communicating with your device'); + }); + + return it('allows retrying registration after an error', () => { + let setupButton = this.container.find('#js-setup-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToRegisterRequest({ + errorCode: 'error!', }); - return it("allows retrying registration after an error", function() { - var registeredMessage, retryButton, setupButton; - setupButton = this.container.find("#js-setup-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToRegisterRequest({ - errorCode: "error!" - }); - retryButton = this.container.find("#U2FTryAgain"); - retryButton.trigger('click'); - setupButton = this.container.find("#js-setup-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToRegisterRequest({ - deviceData: "this is data from the device" - }); - registeredMessage = this.container.find("p"); - return expect(registeredMessage.text()).toContain("Your device was successfully set up!"); + const retryButton = this.container.find('#U2FTryAgain'); + retryButton.trigger('click'); + setupButton = this.container.find('#js-setup-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToRegisterRequest({ + deviceData: 'this is data from the device', }); + const registeredMessage = this.container.find('p'); + return expect(registeredMessage.text()).toContain('Your device was successfully set up!'); }); }); -}).call(window); +}); From a4c501756f8710b9d4b7a3501b54be90e1c7fd1f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 9 Oct 2017 23:26:03 +0100 Subject: [PATCH 033/104] Remove Api from main.js --- app/assets/javascripts/main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 853546b617b..974121412bd 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -56,7 +56,6 @@ import './u2f/util'; // everything else import './activities'; import './admin'; -import './api'; import './aside'; import './autosave'; import loadAwardsHandler from './awards_handler'; From 67f4408d741b62e61f1fd767b4724727c489b42c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 10 Oct 2017 07:47:42 +0000 Subject: [PATCH 034/104] Fix bad type checking to prevent 0 count badge to be shown --- app/assets/javascripts/header.js | 19 +++-- .../javascripts/lib/utils/text_utility.js | 14 +++- changelogs/unreleased/34841-todos.yml | 5 ++ spec/javascripts/header_spec.js | 77 +++++++++---------- .../lib/utils/text_utility_spec.js | 10 +-- 5 files changed, 71 insertions(+), 54 deletions(-) create mode 100644 changelogs/unreleased/34841-todos.yml diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index dc170c60456..ea2e2205077 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -1,7 +1,16 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var */ +import { highCountTrim } from '~/lib/utils/text_utility'; -$(document).on('todo:toggle', function(e, count) { - var $todoPendingCount = $('.todos-count'); - $todoPendingCount.text(gl.text.highCountTrim(count)); - $todoPendingCount.toggleClass('hidden', count === 0); +/** + * Updates todo counter when todos are toggled. + * When count is 0, we hide the badge. + * + * @param {jQuery.Event} e + * @param {String} count + */ +$(document).on('todo:toggle', (e, count) => { + const parsedCount = parseInt(count, 10); + const $todoPendingCount = $('.todos-count'); + + $todoPendingCount.text(highCountTrim(parsedCount)); + $todoPendingCount.toggleClass('hidden', parsedCount === 0); }); diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 021f936a4fa..f776829f69c 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */ +/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */ import 'vendor/latinise'; @@ -13,9 +13,17 @@ if ((base = w.gl).text == null) { gl.text.addDelimiter = function(text) { return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text; }; -gl.text.highCountTrim = function(count) { + +/** + * Returns '99+' for numbers bigger than 99. + * + * @param {Number} count + * @return {Number|String} + */ +export function highCountTrim(count) { return count > 99 ? '99+' : count; -}; +} + gl.text.randomString = function() { return Math.random().toString(36).substring(7); }; diff --git a/changelogs/unreleased/34841-todos.yml b/changelogs/unreleased/34841-todos.yml new file mode 100644 index 00000000000..37180eefbfc --- /dev/null +++ b/changelogs/unreleased/34841-todos.yml @@ -0,0 +1,5 @@ +--- +title: Fix bad type checking to prevent 0 count badge to be shown +merge_request: +author: +type: fixed diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 0e01934d3a3..4751eb868a4 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -1,53 +1,48 @@ -/* eslint-disable space-before-function-paren, no-var */ - import '~/header'; -import '~/lib/utils/text_utility'; -(function() { - describe('Header', function() { - var todosPendingCount = '.todos-count'; - var fixtureTemplate = 'issues/open-issue.html.raw'; +describe('Header', function () { + const todosPendingCount = '.todos-count'; + const fixtureTemplate = 'issues/open-issue.html.raw'; - function isTodosCountHidden() { - return $(todosPendingCount).hasClass('hidden'); - } + function isTodosCountHidden() { + return $(todosPendingCount).hasClass('hidden'); + } - function triggerToggle(newCount) { - $(document).trigger('todo:toggle', newCount); - } + function triggerToggle(newCount) { + $(document).trigger('todo:toggle', newCount); + } - preloadFixtures(fixtureTemplate); - beforeEach(function() { - loadFixtures(fixtureTemplate); + preloadFixtures(fixtureTemplate); + beforeEach(() => { + loadFixtures(fixtureTemplate); + }); + + it('should update todos-count after receiving the todo:toggle event', () => { + triggerToggle('5'); + expect($(todosPendingCount).text()).toEqual('5'); + }); + + it('should hide todos-count when it is 0', () => { + triggerToggle('0'); + expect(isTodosCountHidden()).toEqual(true); + }); + + it('should show todos-count when it is more than 0', () => { + triggerToggle('10'); + expect(isTodosCountHidden()).toEqual(false); + }); + + describe('when todos-count is 1000', () => { + beforeEach(() => { + triggerToggle('1000'); }); - it('should update todos-count after receiving the todo:toggle event', function() { - triggerToggle(5); - expect($(todosPendingCount).text()).toEqual('5'); - }); - - it('should hide todos-count when it is 0', function() { - triggerToggle(0); - expect(isTodosCountHidden()).toEqual(true); - }); - - it('should show todos-count when it is more than 0', function() { - triggerToggle(10); + it('should show todos-count', () => { expect(isTodosCountHidden()).toEqual(false); }); - describe('when todos-count is 1000', function() { - beforeEach(function() { - triggerToggle(1000); - }); - - it('should show todos-count', function() { - expect(isTodosCountHidden()).toEqual(false); - }); - - it('should show 99+ for todos-count', function() { - expect($(todosPendingCount).text()).toEqual('99+'); - }); + it('should show 99+ for todos-count', () => { + expect($(todosPendingCount).text()).toEqual('99+'); }); }); -}).call(window); +}); diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index f1a975ba962..829b3ef5735 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -1,4 +1,4 @@ -import '~/lib/utils/text_utility'; +import { highCountTrim } from '~/lib/utils/text_utility'; describe('text_utility', () => { describe('gl.text.getTextWidth', () => { @@ -35,14 +35,14 @@ describe('text_utility', () => { }); }); - describe('gl.text.highCountTrim', () => { + describe('highCountTrim', () => { it('returns 99+ for count >= 100', () => { - expect(gl.text.highCountTrim(105)).toBe('99+'); - expect(gl.text.highCountTrim(100)).toBe('99+'); + expect(highCountTrim(105)).toBe('99+'); + expect(highCountTrim(100)).toBe('99+'); }); it('returns exact number for count < 100', () => { - expect(gl.text.highCountTrim(45)).toBe(45); + expect(highCountTrim(45)).toBe(45); }); }); From 75c6953fc76422b138335680b227e7303d20c136 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 10 Oct 2017 10:59:07 +0200 Subject: [PATCH 035/104] Changed Group Icon URL Back to try option --- app/helpers/groups_helper.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index f5ef9c53a7a..676c1d1988b 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -17,11 +17,7 @@ module GroupsHelper group = Group.find_by_full_path(group) end - if group.avatar_url - group.avatar_url - else # No Avatar Icon - ActionController::Base.helpers.image_path('no_group_avatar.png') - end + group.try(:avatar_url) || ActionController::Base.helpers.image_path('no_group_avatar.png') end def group_title(group, name = nil, url = nil) From 5b32a4aaffc54c7ea9aca9e2745fcbcdbeae7a22 Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Tue, 10 Oct 2017 10:09:49 +0100 Subject: [PATCH 036/104] Backports EE 38771 changes to CE. --- app/workers/concerns/project_start_import.rb | 9 ++++++++ app/workers/repository_fork_worker.rb | 3 ++- app/workers/repository_import_worker.rb | 3 ++- spec/workers/repository_fork_worker_spec.rb | 22 +++++++++++++++++++ spec/workers/repository_import_worker_spec.rb | 17 ++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 app/workers/concerns/project_start_import.rb diff --git a/app/workers/concerns/project_start_import.rb b/app/workers/concerns/project_start_import.rb new file mode 100644 index 00000000000..0704ebbb0fd --- /dev/null +++ b/app/workers/concerns/project_start_import.rb @@ -0,0 +1,9 @@ +module ProjectStartImport + def start(project) + if project.import_started? && project.import_jid == self.jid + return true + end + + project.import_start + end +end diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index cde5b45ad41..264706e3e23 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -4,6 +4,7 @@ class RepositoryForkWorker include Sidekiq::Worker include Gitlab::ShellAdapter include DedicatedSidekiqQueue + include ProjectStartImport sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION @@ -37,7 +38,7 @@ class RepositoryForkWorker private def start_fork(project) - return true if project.import_start + return true if start(project) Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.") false diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 00a021abbdc..d7c0043d3b6 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -4,6 +4,7 @@ class RepositoryImportWorker include Sidekiq::Worker include DedicatedSidekiqQueue include ExceptionBacktrace + include ProjectStartImport sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION @@ -34,7 +35,7 @@ class RepositoryImportWorker private def start_import(project) - return true if project.import_start + return true if start(project) Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.") false diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index d9e9409840f..e881ec37ae5 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -12,6 +12,28 @@ describe RepositoryForkWorker do end describe "#perform" do + describe 'when a worker was reset without cleanup' do + let(:jid) { '12345678' } + let(:started_project) { create(:project, :repository, :import_started) } + + it 'creates a new repository from a fork' do + allow(subject).to receive(:jid).and_return(jid) + + expect(shell).to receive(:fork_repository).with( + '/test/path', + project.full_path, + project.repository_storage_path, + fork_project.namespace.full_path + ).and_return(true) + + subject.perform( + project.id, + '/test/path', + project.full_path, + fork_project.namespace.full_path) + end + end + it "creates a new repository from a fork" do expect(shell).to receive(:fork_repository).with( '/test/path', diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 100dfc32bbe..5cff5108477 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -6,6 +6,23 @@ describe RepositoryImportWorker do subject { described_class.new } describe '#perform' do + context 'when worker was reset without cleanup' do + let(:jid) { '12345678' } + let(:started_project) { create(:project, :import_started, import_jid: jid) } + + it 'imports the project successfully' do + allow(subject).to receive(:jid).and_return(jid) + + expect_any_instance_of(Projects::ImportService).to receive(:execute) + .and_return({ status: :ok }) + + expect_any_instance_of(Repository).to receive(:expire_emptiness_caches) + expect_any_instance_of(Project).to receive(:import_finish) + + subject.perform(project.id) + end + end + context 'when the import was successful' do it 'imports a project' do expect_any_instance_of(Projects::ImportService).to receive(:execute) From fa2af5e0f5e290eff32f62c7ea9f935a6ad33967 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 2 Oct 2017 13:32:53 +0100 Subject: [PATCH 037/104] Flash is now a ES6 module Reduced the technical debt around our JS flash function by making it a module that is imported rather than relying on the global function. The global function still exists mainly for technical debt with how some requests are being completed, but new JS should import the module directly. Also reduces some tech debt in the file by removing the need for jQuery. Instead Flash is now 100% vanilla JS. --- app/assets/javascripts/awards_handler.js | 2 +- .../javascripts/blob/balsamiq_viewer.js | 5 +- .../blob/file_template_mediator.js | 3 +- app/assets/javascripts/blob/viewer/index.js | 2 +- .../javascripts/boards/boards_bundle.js | 2 +- .../boards/components/board_sidebar.js | 2 +- .../boards/components/modal/footer.js | 2 +- .../boards/components/sidebar/remove_issue.js | 2 +- .../create_merge_request_dropdown.js | 2 +- .../cycle_analytics/cycle_analytics_bundle.js | 1 + .../deploy_keys/components/app.vue | 2 +- .../diff_notes/components/resolve_btn.js | 2 +- .../diff_notes/services/resolve.js | 2 +- .../environments/components/environment.vue | 2 +- .../folder/environments_folder_view.vue | 2 +- .../filtered_search/dropdown_emoji.js | 7 +- .../filtered_search/dropdown_non_user.js | 7 +- .../filtered_search/dropdown_user.js | 5 +- .../filtered_search_manager.js | 3 +- .../filtered_search_visual_tokens.js | 2 +- app/assets/javascripts/flash.js | 127 +++++++++--------- app/assets/javascripts/groups/index.js | 3 +- .../integrations/integration_settings_form.js | 4 +- .../issuable_bulk_update_actions.js | 2 +- app/assets/javascripts/issue.js | 4 +- .../javascripts/issue_show/components/app.vue | 2 +- .../javascripts/jobs/job_details_mediator.js | 2 +- app/assets/javascripts/label_manager.js | 3 +- app/assets/javascripts/main.js | 11 +- .../components/diff_file_editor.js | 2 +- .../merge_conflicts/merge_conflicts_bundle.js | 2 +- app/assets/javascripts/merge_request_tabs.js | 3 +- app/assets/javascripts/milestone.js | 3 +- .../mini_pipeline_graph_dropdown.js | 2 +- .../monitoring/components/dashboard.vue | 2 +- app/assets/javascripts/notes.js | 4 +- .../notes/components/issue_comment_form.vue | 7 +- .../notes/components/issue_discussion.vue | 4 +- .../notes/components/issue_note.vue | 5 +- .../components/issue_note_awards_list.vue | 3 +- .../notes/components/issue_notes_app.vue | 2 +- .../javascripts/notes/stores/actions.js | 10 +- .../javascripts/notifications_dropdown.js | 2 +- .../pipelines/components/stage.vue | 2 +- .../javascripts/pipelines/mixins/pipelines.js | 3 +- .../pipelines/pipeline_details_bundle.js | 3 +- .../pipelines/pipeline_details_mediatior.js | 3 +- app/assets/javascripts/profile/profile.js | 2 +- .../protected_branch_edit.js | 5 +- .../protected_tags/protected_tag_edit.js | 5 +- .../repo/components/repo_commit_section.vue | 2 +- .../javascripts/repo/helpers/repo_helper.js | 3 +- .../javascripts/repo/services/repo_service.js | 6 +- app/assets/javascripts/search.js | 2 +- .../components/assignees/sidebar_assignees.js | 3 +- .../confidential_issue_sidebar.vue | 2 +- .../sidebar/lib/sidebar_move_issue.js | 2 +- .../javascripts/sidebar/sidebar_mediator.js | 3 +- app/assets/javascripts/star.js | 3 +- app/assets/javascripts/task_list.js | 3 +- .../components/mr_widget_deployment.js | 3 +- .../mr_widget_merge_when_pipeline_succeeds.js | 2 +- .../components/states/mr_widget_merged.js | 3 +- .../states/mr_widget_ready_to_merge.js | 2 +- .../components/states/mr_widget_wip.js | 2 +- .../mr_widget_options.js | 3 +- .../vue_shared/components/markdown/field.vue | 2 +- 67 files changed, 152 insertions(+), 183 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 4f01345ee3b..622764107ad 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,8 +1,8 @@ /* eslint-disable class-methods-use-this */ -/* global Flash */ import _ from 'underscore'; import Cookies from 'js-cookie'; import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils'; +import Flash from './flash'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; diff --git a/app/assets/javascripts/blob/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq_viewer.js index 8641a6fdae6..062577af385 100644 --- a/app/assets/javascripts/blob/balsamiq_viewer.js +++ b/app/assets/javascripts/blob/balsamiq_viewer.js @@ -1,9 +1,8 @@ -/* global Flash */ - +import Flash from '../flash'; import BalsamiqViewer from './balsamiq/balsamiq_viewer'; function onError() { - const flash = new window.Flash('Balsamiq file could not be loaded.'); + const flash = new Flash('Balsamiq file could not be loaded.'); return flash; } diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js index a20c6ca7a21..583e5faa506 100644 --- a/app/assets/javascripts/blob/file_template_mediator.js +++ b/app/assets/javascripts/blob/file_template_mediator.js @@ -1,6 +1,5 @@ /* eslint-disable class-methods-use-this */ -/* global Flash */ - +import Flash from '../flash'; import FileTemplateTypeSelector from './template_selectors/type_selector'; import BlobCiYamlSelector from './template_selectors/ci_yaml_selector'; import DockerfileSelector from './template_selectors/dockerfile_selector'; diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index e0b73f13d36..54132e8537b 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -1,4 +1,4 @@ -/* global Flash */ +import Flash from '../../flash'; import { handleLocationHash } from '../../lib/utils/common_utils'; export default class BlobViewer { diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 815248f38ee..ef4093b59e3 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -1,10 +1,10 @@ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ /* global BoardService */ -/* global Flash */ import _ from 'underscore'; import Vue from 'vue'; import VueResource from 'vue-resource'; +import Flash from '../flash'; import FilteredSearchBoards from './filtered_search_boards'; import eventHub from './eventhub'; import './models/issue'; diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 590b7be36e3..7f3afefc9cc 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -3,9 +3,9 @@ /* global MilestoneSelect */ /* global LabelsSelect */ /* global Sidebar */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../../flash'; import eventHub from '../../sidebar/event_hub'; import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import Assignees from '../../sidebar/components/assignees/assignees'; diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js index a656f0546c0..de9e44cef35 100644 --- a/app/assets/javascripts/boards/components/modal/footer.js +++ b/app/assets/javascripts/boards/components/modal/footer.js @@ -1,7 +1,7 @@ /* eslint-disable no-new */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../../../flash'; import './lists_dropdown'; const ModalStore = gl.issueBoards.ModalStore; diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index 1e623cf58b7..1ad97211934 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -1,7 +1,7 @@ /* eslint-disable no-new */ -/* global Flash */ import Vue from 'vue'; +import Flash from '../../../flash'; const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index ff2f2c81971..bf40eb3ee11 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -1,5 +1,5 @@ /* eslint-disable no-new */ -/* global Flash */ +import Flash from './flash'; import DropLab from './droplab/drop_lab'; import ISetter from './droplab/plugins/input_setter'; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index cdf5e3c0290..d184fcfeebd 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import Cookies from 'js-cookie'; +import Flash from '../flash'; import Translate from '../vue_shared/translate'; import banner from './components/banner.vue'; import stageCodeComponent from './components/stage_code_component.vue'; diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index a663e30dfd0..54e13b79a4f 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -1,5 +1,5 @@ ', 'alert'); + + expect( + el.querySelector('.flash-text').textContent.trim(), + ).toBe(''); + }); + }); + + describe('hideFlash', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + el.className = 'js-testing'; + }); + + it('sets transition style', () => { + hideFlash(el); + + expect( + el.style.transition, + ).toBe('opacity 0.3s'); + }); + + it('sets opacity style', () => { + hideFlash(el); + + expect( + el.style.opacity, + ).toBe('0'); + }); + + it('removes element after transitionend', () => { + document.body.appendChild(el); + + hideFlash(el); + el.dispatchEvent(new Event('transitionend')); + + expect( + document.querySelector('.js-testing'), + ).toBeNull(); + }); + + it('calls event listener callback once', () => { + spyOn(el, 'remove').and.callThrough(); + document.body.appendChild(el); + + hideFlash(el); + + el.dispatchEvent(new Event('transitionend')); + el.dispatchEvent(new Event('transitionend')); + + expect( + el.remove.calls.count(), + ).toBe(1); + }); + }); + + describe('createAction', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + }); + + it('creates link with href', () => { + el.innerHTML = createAction({ + href: 'testing', + title: 'test', + }); + + expect( + el.querySelector('.flash-action').href, + ).toContain('testing'); + }); + + it('uses hash as href when no href is present', () => { + el.innerHTML = createAction({ + title: 'test', + }); + + expect( + el.querySelector('.flash-action').href, + ).toContain('#'); + }); + + it('adds role when no href is present', () => { + el.innerHTML = createAction({ + title: 'test', + }); + + expect( + el.querySelector('.flash-action').getAttribute('role'), + ).toBe('button'); + }); + + it('escapes the title text', () => { + el.innerHTML = createAction({ + title: '', + }); + + expect( + el.querySelector('.flash-action').textContent.trim(), + ).toBe(''); + }); + }); + + describe('createFlash', () => { + describe('no flash-container', () => { + it('does not add to the DOM', () => { + const el = flash('test'); + const flashEl = flash('testing'); + + expect( + flashEl, + ).toBeNull(); + expect( + document.querySelector('.flash-alert'), + ).toBeNull(); + }); + }); + + describe('with flash-container', () => { + beforeEach(() => { + document.body.innerHTML += ` +
+
+
+ `; + }); + + afterEach(() => { + document.querySelector('.js-content-wrapper').remove(); + }); + + it('adds flash element into container', () => { + flash('test'); + + expect( + document.querySelector('.flash-alert'), + ).not.toBeNull(); + }); + + it('adds flash into specified parent', () => { + flash( + 'test', + 'alert', + document.querySelector('.content-wrapper'), + ); + + expect( + document.querySelector('.content-wrapper .flash-alert'), + ).not.toBeNull(); + }); + + it('adds container classes when inside content-wrapper', () => { + flash('test'); + + expect( + document.querySelector('.flash-text').className, + ).toBe('flash-text container-fluid container-limited') + }); + + it('does not add container when outside of content-wrapper', () => { + document.querySelector('.content-wrapper').className = 'js-content-wrapper'; + flash('test'); + + expect( + document.querySelector('.flash-text').className, + ).toBe('flash-text') + }); + + it('removes element after clicking', () => { + flash('test', 'alert', document, null, false); + + document.querySelector('.flash-alert').click(); + + expect( + document.querySelector('.flash-alert'), + ).toBeNull(); + }); + + describe('with actionConfig', () => { + it('adds action link', () => { + flash( + 'test', + 'alert', + document, + { + title: 'test', + }, + ); + + expect( + document.querySelector('.flash-action'), + ).not.toBeNull(); + }); + + it('calls actionConfig clickHandler on click', () => { + const actionConfig = { + title: 'test', + clickHandler: jasmine.createSpy('actionConfig'), + }; + + flash( + 'test', + 'alert', + document, + actionConfig, + ); + + document.querySelector('.flash-action').click(); + + expect( + actionConfig.clickHandler, + ).toHaveBeenCalled(); + }); + }); + }); + }); +}); From 6c97107d37771522d1deec6007a791332270ba6f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 4 Oct 2017 14:44:43 +0100 Subject: [PATCH 040/104] fixed eslint --- app/assets/javascripts/flash.js | 16 ++++++++-------- spec/javascripts/flash_spec.js | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index d745483c93f..8ac2b96a22d 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -44,14 +44,14 @@ const createFlashEl = (message, type) => ` * along with ability to provide actionConfig which can be used to show * additional action or link on banner next to message * - * @param {String} message Flash message text - * @param {String} type Type of Flash, it can be `notice` or `alert` (default) - * @param {Object} parent Reference to parent element under which Flash needs to appear - * @param {Object} actonConfig Map of config to show action on banner - * @param {String} href URL to which action config should point to (default: '#') - * @param {String} title Title of action - * @param {Function} clickHandler Method to call when action is clicked on - * @param {Boolean} fadeTransition Boolean to determine whether to fade the alert out + * @param {String} message Flash message text + * @param {String} type Type of Flash, it can be `notice` or `alert` (default) + * @param {Object} parent Reference to parent element under which Flash needs to appear + * @param {Object} actonConfig Map of config to show action on banner + * @param {String} href URL to which action config should point to (default: '#') + * @param {String} title Title of action + * @param {Function} clickHandler Method to call when action is clicked on + * @param {Boolean} fadeTransition Boolean to determine whether to fade the alert out */ const createFlash = function createFlash( message, diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js index 1feb31f5c4f..66cc76ee626 100644 --- a/spec/javascripts/flash_spec.js +++ b/spec/javascripts/flash_spec.js @@ -135,7 +135,6 @@ describe('Flash', () => { describe('createFlash', () => { describe('no flash-container', () => { it('does not add to the DOM', () => { - const el = flash('test'); const flashEl = flash('testing'); expect( @@ -185,7 +184,7 @@ describe('Flash', () => { expect( document.querySelector('.flash-text').className, - ).toBe('flash-text container-fluid container-limited') + ).toBe('flash-text container-fluid container-limited'); }); it('does not add container when outside of content-wrapper', () => { @@ -194,7 +193,7 @@ describe('Flash', () => { expect( document.querySelector('.flash-text').className, - ).toBe('flash-text') + ).toBe('flash-text'); }); it('removes element after clicking', () => { From cbfc97b112849299b0aaf3b5155e278d3db17c91 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 4 Oct 2017 15:55:01 +0100 Subject: [PATCH 041/104] karma spec fixes --- app/assets/javascripts/issue_show/components/app.vue | 5 ++--- app/assets/javascripts/repo/services/repo_service.js | 5 ++--- app/assets/javascripts/sidebar/lib/sidebar_move_issue.js | 6 ++---- .../components/states/mr_widget_wip.js | 5 ++--- .../integrations/integration_settings_form_spec.js | 6 +++--- spec/javascripts/notes_spec.js | 4 ++-- 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index 0e63c723e2f..eecb56cb185 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -1,6 +1,5 @@ '); }); + + it('adds container classes when inside content wrapper', () => { + el.innerHTML = createFlashEl('testing', 'alert', true); + + expect( + el.querySelector('.flash-text').classList.contains('container-fluid'), + ).toBeTruthy(); + expect( + el.querySelector('.flash-text').classList.contains('container-limited'), + ).toBeTruthy(); + }); }); describe('hideFlash', () => { @@ -57,6 +68,17 @@ describe('Flash', () => { ).toBe('0'); }); + it('does not set styles when fadeTransition is false', () => { + hideFlash(el, false); + + expect( + el.style.opacity, + ).toBe(''); + expect( + el.style.transition, + ).toBe(''); + }); + it('removes element after transitionend', () => { document.body.appendChild(el); @@ -192,7 +214,7 @@ describe('Flash', () => { flash('test'); expect( - document.querySelector('.flash-text').className, + document.querySelector('.flash-text').className.trim(), ).toBe('flash-text'); }); From b40bac3a3c251110ddba3f90d6d1e4419a65afe3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 5 Oct 2017 14:42:29 +0100 Subject: [PATCH 043/104] removed global eslint for remaining files --- .../javascripts/cycle_analytics/cycle_analytics_bundle.js | 2 -- .../javascripts/issue_show/components/fields/description.vue | 1 - app/assets/javascripts/jobs/job_details_bundle.js | 2 -- .../javascripts/pipelines/components/pipelines_actions.vue | 2 -- app/assets/javascripts/repo/stores/repo_store.js | 1 - 5 files changed, 8 deletions(-) diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index d184fcfeebd..49bb6c52180 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -1,5 +1,3 @@ -/* global Flash */ - import Vue from 'vue'; import Cookies from 'js-cookie'; import Flash from '../flash'; diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue index dc902eefc5f..0aa1b2c2e31 100644 --- a/app/assets/javascripts/issue_show/components/fields/description.vue +++ b/app/assets/javascripts/issue_show/components/fields/description.vue @@ -1,5 +1,4 @@ " } it { is_expected.to render_template('new') } - it { project.repository.branch_names.include?('feature/test') } + it { project.repository.branch_exists?('feature/test') } end end diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 62b23121c5a..9f67216705d 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -118,7 +118,7 @@ feature 'issuable templates', :js do context 'user creates a merge request from a forked project using templates' do let(:template_content) { 'this is a test "feature-proposal" template' } let(:fork_user) { create(:user) } - let(:forked_project) { fork_project(project, fork_user) } + let(:forked_project) { fork_project(project, fork_user, repository: true) } let(:merge_request) { create(:merge_request, :with_diffs, source_project: forked_project, target_project: project) } background do diff --git a/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb b/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb index 8970cf54457..3aac93eaf7c 100644 --- a/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb +++ b/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb @@ -6,7 +6,7 @@ describe 'User views an open merge request' do end context 'when a merge request does not have repository' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } before do visit(merge_request_path(merge_request)) diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 485b0b287ad..2dc3c5e3927 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Task Lists' do include Warden::Test::Helpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb index 4aeb593da44..87832b3dca1 100644 --- a/spec/serializers/merge_request_entity_spec.rb +++ b/spec/serializers/merge_request_entity_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe MergeRequestEntity do - let(:project) { create :project } + let(:project) { create :project, :repository } let(:resource) { create(:merge_request, source_project: project, target_project: project) } let(:user) { create(:user) } From eff96b8c775c19b320f97b5d6359822f2b7929f1 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 10 Oct 2017 15:36:32 +0000 Subject: [PATCH 055/104] Fix unable to expand text diff discussion comments --- app/views/discussions/_diff_discussion.html.haml | 2 +- app/views/discussions/_diff_with_notes.html.haml | 2 +- app/views/discussions/_notes.html.haml | 4 ++-- spec/features/merge_requests/diffs_spec.rb | 8 ++------ 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml index 52279d0a870..4b6c4581eb3 100644 --- a/app/views/discussions/_diff_discussion.html.haml +++ b/app/views/discussions/_diff_discussion.html.haml @@ -7,4 +7,4 @@ %td.notes_line{ colspan: 2 } %td.notes_content .content{ class: ('hide' unless expanded) } - = render partial: "discussions/notes", collection: discussions, as: :discussion + = render partial: "discussions/notes", collection: discussions, as: :discussion, locals: { disable_collapse_class: true } diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 636d06cab53..f9bfc01f213 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -24,4 +24,4 @@ = render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false } .note-container - = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse: true } + = render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse_class: true } diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 9efcfef690f..1cc227428e9 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -1,5 +1,5 @@ -- disable_collapse = local_assigns.fetch(:disable_collapse, false) -- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse +- disable_collapse_class = local_assigns.fetch(:disable_collapse_class, false) +- collapsed_class = 'collapsed' if discussion.resolved? && !disable_collapse_class - badge_counter = discussion_counter + 1 if local_assigns[:discussion_counter] - show_toggle = local_assigns.fetch(:show_toggle, true) - show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb index 80fb7335989..2adca58620f 100644 --- a/spec/features/merge_requests/diffs_spec.rb +++ b/spec/features/merge_requests/diffs_spec.rb @@ -44,12 +44,8 @@ feature 'Diffs URL', :js do visit "#{diffs_project_merge_request_path(project, merge_request)}#{fragment}" end - it 'shows collapsed note' do - wait_for_requests - - expect(page).to have_selector('.discussion-notes.collapsed') do |note_container| - expect(note_container).to have_selector(fragment, visible: false) - end + it 'shows expanded note' do + expect(page).to have_selector(fragment, visible: true) end end end From ad80db9799a65814be836aaa4b219b87d93aa433 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 10 Oct 2017 12:57:56 +0100 Subject: [PATCH 056/104] Remove not selector increasing specificity of rules --- app/assets/stylesheets/framework/dropdowns.scss | 2 +- app/assets/stylesheets/framework/header.scss | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 5b950ae0ba0..9dcf332eee2 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -749,7 +749,7 @@ margin-bottom: $dropdown-vertical-offset; } - li:not(.dropdown-bold-header) { + li { display: block; padding: 0 1px; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 22945e935ef..d79444fad79 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -161,9 +161,10 @@ } } - .dropdown-bold-header { + li.dropdown-bold-header { color: $gl-text-color-secondary; font-size: 12px; + padding: 0 16px; } .navbar-collapse { From 6fb19f77e6f31c049101624b001f8f36414e0443 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 10 Oct 2017 18:22:34 +0100 Subject: [PATCH 057/104] Use ES6 modules in labels and labels manager --- app/assets/javascripts/dispatcher.js | 5 +- app/assets/javascripts/label_manager.js | 232 ++++++++++++------------ app/assets/javascripts/labels.js | 75 ++++---- app/assets/javascripts/main.js | 2 - 4 files changed, 150 insertions(+), 164 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index d43eae79730..5ebc92110dd 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -29,7 +29,8 @@ import CILintEditor from './ci_lint_editor'; /* global ProjectNew */ /* global ProjectShow */ /* global ProjectImport */ -/* global Labels */ +import Labels from './labels'; +import LabelManager from './label_manager'; /* global Shortcuts */ /* global ShortcutsFindFile */ /* global Sidebar */ @@ -459,7 +460,7 @@ import U2FAuthenticate from './u2f/authenticate'; case 'groups:labels:index': case 'projects:labels:index': if ($('.prioritized-labels').length) { - new gl.LabelManager(); + new LabelManager(); } $('.label-subscription').each((i, el) => { const $el = $(el); diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index a8f613c6cf2..c929dc98c10 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -3,123 +3,119 @@ import Flash from './flash'; -((global) => { - class LabelManager { - constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { - this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); - this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); - this.otherLabels = otherLabels || $('.js-other-labels'); - this.errorMessage = 'Unable to update label prioritization at this time'; - this.emptyState = document.querySelector('#js-priority-labels-empty-state'); - this.sortable = Sortable.create(this.prioritizedLabels.get(0), { - filter: '.empty-message', - forceFallback: true, - fallbackClass: 'is-dragging', - dataIdAttr: 'data-id', - onUpdate: this.onPrioritySortUpdate.bind(this), - }); - this.bindEvents(); - } - - bindEvents() { - this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick); - return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); - } - - onTogglePriorityClick(e) { - e.preventDefault(); - const _this = e.data; - const $btn = $(e.currentTarget); - const $label = $(`#${$btn.data('domId')}`); - const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; - const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); - $tooltip.tooltip('destroy'); - _this.toggleLabelPriority($label, action); - _this.toggleEmptyState($label, $btn, action); - } - - onButtonActionClick(e) { - e.stopPropagation(); - $(e.currentTarget).tooltip('hide'); - } - - toggleEmptyState($label, $btn, action) { - this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li')); - } - - toggleLabelPriority($label, action, persistState) { - if (persistState == null) { - persistState = true; - } - let xhr; - const _this = this; - const url = $label.find('.js-toggle-priority').data('url'); - let $target = this.prioritizedLabels; - let $from = this.otherLabels; - if (action === 'remove') { - $target = this.otherLabels; - $from = this.prioritizedLabels; - } - $label.detach().appendTo($target); - if ($from.find('li').length) { - $from.find('.empty-message').removeClass('hidden'); - } - if ($target.find('> li:not(.empty-message)').length) { - $target.find('.empty-message').addClass('hidden'); - } - // Return if we are not persisting state - if (!persistState) { - return; - } - if (action === 'remove') { - xhr = $.ajax({ - url, - type: 'DELETE' - }); - // Restore empty message - if (!$from.find('li').length) { - $from.find('.empty-message').removeClass('hidden'); - } - } else { - xhr = this.savePrioritySort($label, action); - } - return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); - } - - onPrioritySortUpdate() { - const xhr = this.savePrioritySort(); - return xhr.fail(function() { - return new Flash(this.errorMessage, 'alert'); - }); - } - - savePrioritySort() { - return $.post({ - url: this.prioritizedLabels.data('url'), - data: { - label_ids: this.getSortedLabelsIds() - } - }); - } - - rollbackLabelPosition($label, originalAction) { - const action = originalAction === 'remove' ? 'add' : 'remove'; - this.toggleLabelPriority($label, action, false); - return new Flash(this.errorMessage, 'alert'); - } - - getSortedLabelsIds() { - const sortedIds = []; - this.prioritizedLabels.find('> li').each(function() { - const id = $(this).data('id'); - - if (id) { - sortedIds.push(id); - } - }); - return sortedIds; - } +export default class LabelManager { + constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { + this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); + this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); + this.otherLabels = otherLabels || $('.js-other-labels'); + this.errorMessage = 'Unable to update label prioritization at this time'; + this.emptyState = document.querySelector('#js-priority-labels-empty-state'); + this.sortable = Sortable.create(this.prioritizedLabels.get(0), { + filter: '.empty-message', + forceFallback: true, + fallbackClass: 'is-dragging', + dataIdAttr: 'data-id', + onUpdate: this.onPrioritySortUpdate.bind(this), + }); + this.bindEvents(); } - gl.LabelManager = LabelManager; -})(window.gl || (window.gl = {})); + bindEvents() { + this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick); + return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); + } + + onTogglePriorityClick(e) { + e.preventDefault(); + const _this = e.data; + const $btn = $(e.currentTarget); + const $label = $(`#${$btn.data('domId')}`); + const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; + const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); + $tooltip.tooltip('destroy'); + _this.toggleLabelPriority($label, action); + _this.toggleEmptyState($label, $btn, action); + } + + onButtonActionClick(e) { + e.stopPropagation(); + $(e.currentTarget).tooltip('hide'); + } + + toggleEmptyState($label, $btn, action) { + this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li')); + } + + toggleLabelPriority($label, action, persistState) { + if (persistState == null) { + persistState = true; + } + let xhr; + const _this = this; + const url = $label.find('.js-toggle-priority').data('url'); + let $target = this.prioritizedLabels; + let $from = this.otherLabels; + if (action === 'remove') { + $target = this.otherLabels; + $from = this.prioritizedLabels; + } + $label.detach().appendTo($target); + if ($from.find('li').length) { + $from.find('.empty-message').removeClass('hidden'); + } + if ($target.find('> li:not(.empty-message)').length) { + $target.find('.empty-message').addClass('hidden'); + } + // Return if we are not persisting state + if (!persistState) { + return; + } + if (action === 'remove') { + xhr = $.ajax({ + url, + type: 'DELETE' + }); + // Restore empty message + if (!$from.find('li').length) { + $from.find('.empty-message').removeClass('hidden'); + } + } else { + xhr = this.savePrioritySort($label, action); + } + return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); + } + + onPrioritySortUpdate() { + const xhr = this.savePrioritySort(); + return xhr.fail(function() { + return new Flash(this.errorMessage, 'alert'); + }); + } + + savePrioritySort() { + return $.post({ + url: this.prioritizedLabels.data('url'), + data: { + label_ids: this.getSortedLabelsIds() + } + }); + } + + rollbackLabelPosition($label, originalAction) { + const action = originalAction === 'remove' ? 'add' : 'remove'; + this.toggleLabelPriority($label, action, false); + return new Flash(this.errorMessage, 'alert'); + } + + getSortedLabelsIds() { + const sortedIds = []; + this.prioritizedLabels.find('> li').each(function() { + const id = $(this).data('id'); + + if (id) { + sortedIds.push(id); + } + }); + return sortedIds; + } +} diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js index 03dd61b4263..7aab13ed9c6 100644 --- a/app/assets/javascripts/labels.js +++ b/app/assets/javascripts/labels.js @@ -1,44 +1,35 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, max-len */ -(function() { - this.Labels = (function() { - function Labels() { - this.setSuggestedColor = this.setSuggestedColor.bind(this); - this.updateColorPreview = this.updateColorPreview.bind(this); - var form; - form = $('.label-form'); - this.cleanBinding(); - this.addBinding(); - this.updateColorPreview(); - } +export default class Labels { + constructor() { + this.setSuggestedColor = this.setSuggestedColor.bind(this); + this.updateColorPreview = this.updateColorPreview.bind(this); + this.cleanBinding(); + this.addBinding(); + this.updateColorPreview(); + } - Labels.prototype.addBinding = function() { - $(document).on('click', '.suggest-colors a', this.setSuggestedColor); - return $(document).on('input', 'input#label_color', this.updateColorPreview); - }; + addBinding() { + $(document).on('click', '.suggest-colors a', this.setSuggestedColor); + return $(document).on('input', 'input#label_color', this.updateColorPreview); + } + // eslint-disable-next-line class-methods-use-this + cleanBinding() { + $(document).off('click', '.suggest-colors a'); + return $(document).off('input', 'input#label_color'); + } + // eslint-disable-next-line class-methods-use-this + updateColorPreview() { + const previewColor = $('input#label_color').val(); + return $('div.label-color-preview').css('background-color', previewColor); + // Updates the the preview color with the hex-color input + } - Labels.prototype.cleanBinding = function() { - $(document).off('click', '.suggest-colors a'); - return $(document).off('input', 'input#label_color'); - }; - - Labels.prototype.updateColorPreview = function() { - var previewColor; - previewColor = $('input#label_color').val(); - return $('div.label-color-preview').css('background-color', previewColor); - // Updates the the preview color with the hex-color input - }; - - // Updates the preview color with a click on a suggested color - Labels.prototype.setSuggestedColor = function(e) { - var color; - color = $(e.currentTarget).data('color'); - $('input#label_color').val(color); - this.updateColorPreview(); - // Notify the form, that color has changed - $('.label-form').trigger('keyup'); - return e.preventDefault(); - }; - - return Labels; - })(); -}).call(window); + // Updates the preview color with a click on a suggested color + setSuggestedColor(e) { + const color = $(e.currentTarget).data('color'); + $('input#label_color').val(color); + this.updateColorPreview(); + // Notify the form, that color has changed + $('.label-form').trigger('keyup'); + return e.preventDefault(); + } +} diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 50aa445e9e7..09cf5a2a3cb 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -79,8 +79,6 @@ import './issuable_context'; import './issuable_form'; import './issue'; import './issue_status_select'; -import './label_manager'; -import './labels'; import './labels_select'; import './layout_nav'; import LazyLoader from './lazy_loader'; From 06e7eeb1c22bf380ad8f3d35581ed63132adf5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Sun, 8 Oct 2017 20:44:18 -0300 Subject: [PATCH 058/104] Use Gitaly's RepositoryService.HasLocalBranches RPC --- lib/gitlab/git/repository.rb | 2 +- lib/gitlab/gitaly_client/ref_service.rb | 8 -------- lib/gitlab/gitaly_client/repository_service.rb | 7 +++++++ .../gitlab/gitaly_client/repository_service_spec.rb | 11 +++++++++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 0f059bef808..fa13920a3f3 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -193,7 +193,7 @@ module Gitlab def has_local_branches? gitaly_migrate(:has_local_branches) do |is_enabled| if is_enabled - gitaly_ref_client.has_local_branches? + gitaly_repository_client.has_local_branches? else has_local_branches_rugged? end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index 8214b7d63fa..b0c73395cb1 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -57,14 +57,6 @@ module Gitlab branch_names.count end - # TODO implement a more efficient RPC for this https://gitlab.com/gitlab-org/gitaly/issues/616 - def has_local_branches? - request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo) - response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request).first - - response&.names.present? - end - def local_branches(sort_by: nil) request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo) request.sort_by = sort_by_param(sort_by) if sort_by diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index fdf912214e0..cef692d3c2a 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -58,6 +58,13 @@ module Gitlab request = Gitaly::CreateRepositoryRequest.new(repository: @gitaly_repo) GitalyClient.call(@storage, :repository_service, :create_repository, request) end + + def has_local_branches? + request = Gitaly::HasLocalBranchesRequest.new(repository: @gitaly_repo) + response = GitalyClient.call(@storage, :repository_service, :has_local_branches, request) + + response.value + end end end end diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index fd5f984601e..cbc7ce1c1b0 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -73,4 +73,15 @@ describe Gitlab::GitalyClient::RepositoryService do client.apply_gitattributes(revision) end end + + describe '#has_local_branches?' do + it 'sends a has_local_branches message' do + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:has_local_branches) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(double(value: true)) + + expect(client.has_local_branches?).to be(true) + end + end end From 501d2df40ff0da4f37dc9bbedde900ae820c2d11 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 10 Oct 2017 12:40:33 -0500 Subject: [PATCH 059/104] Add explicit test to test resolved discussion toggle content --- .../merge_requests/diff_notes_resolve_spec.rb | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index 637e6036384..475c8586f45 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -88,14 +88,24 @@ feature 'Diff notes resolve', :js do end end - it 'hides resolved discussion' do - page.within '.diff-content' do - click_button 'Resolve discussion' + describe 'resolved discussion' do + before do + page.within '.diff-content' do + click_button 'Resolve discussion' + end + + visit_merge_request end - visit_merge_request + it 'hides when resolve discussion is clicked' do + expect(page).to have_selector('.discussion-body', visible: false) + end - expect(page).to have_selector('.discussion-body', visible: false) + it 'shows resolved discussion when toggled' do + find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click + + expect(page.find(".timeline-content #note_#{note.noteable_id}")).to be_visible + end end it 'allows user to resolve from reply form without a comment' do From 02b2386bf921b8b8902d35e9ea6b957d9cd254e8 Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Tue, 10 Oct 2017 17:47:48 +0000 Subject: [PATCH 060/104] Locked issues / mrs docs --- doc/user/discussions/index.md | 38 ++++++++++++++--------------------- doc/user/permissions.md | 2 +- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index ab46befb91c..23506261f12 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -153,42 +153,34 @@ comments in greater detail. ![Discussion comment](img/discussion_comment.png) -## Locking discussions +## Lock discussions > [Introduced][ce-14531] in GitLab 10.1. -There might be some cases where a discussion is better off if it's locked down. -For example: +For large projects with many contributors, it may be useful to stop discussions +in issues or merge requests in these scenarios: -- Discussions that are several years old and the issue/merge request is closed, - but people continue to try to resurrect the discussion. -- Discussions where someone or a group of people are trolling, are abusive, or - in-general are causing the discussion to be unproductive. +- The project maintainer has already resolved the discussion and it is not helpful +for continued feedback. The project maintainer has already directed new conversation +to newer issues or merge requests. +- The people participating in the discussion are trolling, abusive, or otherwise +being unproductive. -In locked discussions, only team members can write new comments and edit the old -ones. +In these cases, a user with Master permissions or higher in the project can lock (and unlock) +an issue or a merge request, using the "Lock" section in the sidebar: -To lock or unlock a discussion, you need to have at least Master [permissions]: - -1. Find the "Lock" section in the sidebar and click **Edit** -1. In the dialog that will appear, you can choose to turn on or turn off the - discussion lock -1. Optionally, leave a comment to explain your reasoning behind that action - -| Turn off discussion lock | Turn on discussion lock | +| Unlock | Lock | | :-----------: | :----------: | | ![Turn off discussion lock](img/turn_off_lock.png) | ![Turn on discussion lock](img/turn_on_lock.png) | -Every change is indicated by a system note in the issue's or merge request's -comments. +System notes indicate locking and unlocking. ![Discussion lock system notes](img/discussion_lock_system_notes.png) -Once an issue or merge request is locked, project members can see the indicator -in the comment area, whereas non project members can only see the information -that the discussion is locked. +In a locked issue or merge request, only team members can add new comments and +edit existing comments. Non-team members are restricted from adding or editing comments. -| Team member | Not a member | +| Team member | Non-team member | | :-----------: | :----------: | | ![Comment form member](img/lock_form_member.png) | ![Comment form non-member](img/lock_form_non_member.png) | diff --git a/doc/user/permissions.md b/doc/user/permissions.md index be72d42625a..9b096d26081 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -25,7 +25,7 @@ The following table depicts the various user permission levels in a project. | Create confidential issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ | | Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | -| Lock comments | | | | ✓ | ✓ | +| Lock discussions (issues and merge requests) | | | | ✓ | ✓ | | See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | From e5ed2e4f4e82dcfdec4847f7d96094393a9f0839 Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Tue, 10 Oct 2017 14:45:43 -0400 Subject: [PATCH 061/104] fix multiple notifications from being sent for multiple labels This also refactor the email_helper support spec to watch for multiple emails being sent. --- app/services/notification_service.rb | 2 +- spec/services/notification_service_spec.rb | 12 ++++++++++++ spec/support/email_helpers.rb | 14 +++++++------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index e2a80db06a6..b1695f348ee 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -397,7 +397,7 @@ class NotificationService end def relabeled_resource_email(target, labels, current_user, method) - recipients = labels.flat_map { |l| l.subscribers(target.project) } + recipients = labels.flat_map { |l| l.subscribers(target.project) }.uniq recipients = notifiable_users( recipients, :subscription, target: target, diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index f4b36eb7eeb..457acc86e50 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -743,6 +743,18 @@ describe NotificationService, :mailer do should_not_email(@u_participating) end + it "doesn't send multiple email when a user is subscribed to multiple given labels" do + subscriber_to_both = create(:user) do |user| + [label_1, label_2].each { |label| label.toggle_subscription(user, project) } + end + + notification.relabeled_issue(issue, [label_1, label_2], @u_disabled) + + should_email(subscriber_to_label_1) + should_email(subscriber_to_label_2) + should_email(subscriber_to_both) + end + context 'confidential issues' do let(:author) { create(:user) } let(:assignee) { create(:user) } diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb index 3e979f2f470..a9976972268 100644 --- a/spec/support/email_helpers.rb +++ b/spec/support/email_helpers.rb @@ -1,6 +1,6 @@ module EmailHelpers - def sent_to_user?(user, recipients = email_recipients) - recipients.include?(user.notification_email) + def sent_to_user(user, recipients: email_recipients) + recipients.count { |to| to == user.notification_email } end def reset_delivered_emails! @@ -10,17 +10,17 @@ module EmailHelpers def should_only_email(*users, kind: :to) recipients = email_recipients(kind: kind) - users.each { |user| should_email(user, recipients) } + users.each { |user| should_email(user, recipients: recipients) } expect(recipients.count).to eq(users.count) end - def should_email(user, recipients = email_recipients) - expect(sent_to_user?(user, recipients)).to be_truthy + def should_email(user, times: 1, recipients: email_recipients) + expect(sent_to_user(user, recipients: recipients)).to eq(times) end - def should_not_email(user, recipients = email_recipients) - expect(sent_to_user?(user, recipients)).to be_falsey + def should_not_email(user, recipients: email_recipients) + should_email(user, times: 0) end def should_not_email_anyone From 38f0f7e8ccdd86ffb2f149bb4ae3bcb377efbd0a Mon Sep 17 00:00:00 2001 From: "micael.bergeron" Date: Tue, 10 Oct 2017 15:02:03 -0400 Subject: [PATCH 062/104] [ci-skip] add changelog --- .../37691-subscription-fires-multiple-notifications.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/37691-subscription-fires-multiple-notifications.yml diff --git a/changelogs/unreleased/37691-subscription-fires-multiple-notifications.yml b/changelogs/unreleased/37691-subscription-fires-multiple-notifications.yml new file mode 100644 index 00000000000..c3c38b35fa7 --- /dev/null +++ b/changelogs/unreleased/37691-subscription-fires-multiple-notifications.yml @@ -0,0 +1,5 @@ +--- +title: Fixed duplicate notifications when added multiple labels on an issue +merge_request: 14798 +author: +type: fixed From 33faadc15fb525bb39da6aa9bedfed91bcbc8737 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 10 Oct 2017 20:18:32 +0000 Subject: [PATCH 063/104] Decreases z-index of select2 to a lower number than our navigation bar --- app/assets/stylesheets/framework/selects.scss | 1 + changelogs/unreleased/36160-select2-dropdown.yml | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 changelogs/unreleased/36160-select2-dropdown.yml diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 50f1445bc2e..58f6e62b06a 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -61,6 +61,7 @@ border: 1px solid $dropdown-border-color; min-width: 175px; color: $gl-text-color; + z-index: 999; } .select2-drop.select2-drop-above.select2-drop-active { diff --git a/changelogs/unreleased/36160-select2-dropdown.yml b/changelogs/unreleased/36160-select2-dropdown.yml new file mode 100644 index 00000000000..a836744fb41 --- /dev/null +++ b/changelogs/unreleased/36160-select2-dropdown.yml @@ -0,0 +1,5 @@ +--- +title: Decreases z-index of select2 to a lower number of our navigation bar +merge_request: +author: +type: fixed From fa68cb3c21c420eee9a801cdf4bdd520949d1782 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Tue, 10 Oct 2017 15:39:51 -0700 Subject: [PATCH 064/104] 37660 Change background color of nav sidebar to match other gl sidebars --- app/assets/stylesheets/framework/new-sidebar.scss | 2 +- changelogs/unreleased/37660-match-sidebar-colors.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/37660-match-sidebar-colors.yml diff --git a/app/assets/stylesheets/framework/new-sidebar.scss b/app/assets/stylesheets/framework/new-sidebar.scss index caf4c7a40b1..78972717932 100644 --- a/app/assets/stylesheets/framework/new-sidebar.scss +++ b/app/assets/stylesheets/framework/new-sidebar.scss @@ -90,7 +90,7 @@ $new-sidebar-collapsed-width: 50px; top: $header-height; bottom: 0; left: 0; - background-color: $gray-normal; + background-color: $gray-light; box-shadow: inset -2px 0 0 $border-color; transform: translate3d(0, 0, 0); diff --git a/changelogs/unreleased/37660-match-sidebar-colors.yml b/changelogs/unreleased/37660-match-sidebar-colors.yml new file mode 100644 index 00000000000..d5600f453e7 --- /dev/null +++ b/changelogs/unreleased/37660-match-sidebar-colors.yml @@ -0,0 +1,5 @@ +--- +title: Change background color of nav sidebar to match other gl sidebars +merge_request: +author: +type: changed From bd69b794350f44ff750798c476a9cd27861fedfa Mon Sep 17 00:00:00 2001 From: "Vitaliy @blackst0ne Klachkov" Date: Wed, 11 Oct 2017 15:26:04 +1100 Subject: [PATCH 065/104] Replace the 'features/explore/projects.feature' spinach test with an rspec analog --- .../replace_explore_projects-feature.yml | 5 + features/explore/projects.feature | 144 ---------------- features/steps/explore/projects.rb | 145 ---------------- features/steps/shared/paths.rb | 13 -- features/steps/shared/project.rb | 18 -- .../explore/user_explores_projects_spec.rb | 72 ++++++++ spec/features/projects/issues/list_spec.rb | 20 --- .../projects/issues/user_views_issues_spec.rb | 56 +++++++ .../user_views_open_merge_requests_spec.rb | 155 +++++++++++------- .../projects/user_views_details_spec.rb | 151 +++++++++++++++++ 10 files changed, 383 insertions(+), 396 deletions(-) create mode 100644 changelogs/unreleased/replace_explore_projects-feature.yml delete mode 100644 features/explore/projects.feature delete mode 100644 features/steps/explore/projects.rb create mode 100644 spec/features/explore/user_explores_projects_spec.rb delete mode 100644 spec/features/projects/issues/list_spec.rb create mode 100644 spec/features/projects/issues/user_views_issues_spec.rb create mode 100644 spec/features/projects/user_views_details_spec.rb diff --git a/changelogs/unreleased/replace_explore_projects-feature.yml b/changelogs/unreleased/replace_explore_projects-feature.yml new file mode 100644 index 00000000000..85ef045fb4b --- /dev/null +++ b/changelogs/unreleased/replace_explore_projects-feature.yml @@ -0,0 +1,5 @@ +--- +title: Replace the 'features/explore/projects.feature' spinach test with an rspec analog +merge_request: 14755 +author: Vitaliy @blackst0ne Klachkov +type: other diff --git a/features/explore/projects.feature b/features/explore/projects.feature deleted file mode 100644 index 4e0f4486ab7..00000000000 --- a/features/explore/projects.feature +++ /dev/null @@ -1,144 +0,0 @@ -@public -Feature: Explore Projects - Background: - Given public project "Community" - And internal project "Internal" - And private project "Enterprise" - - Scenario: I visit public area - Given archived project "Archive" - When I visit the public projects area - Then I should see project "Community" - And I should not see project "Internal" - And I should not see project "Enterprise" - And I should not see project "Archive" - - Scenario: I visit public project page - When I visit project "Community" page - Then I should see project "Community" home page - - Scenario: I visit internal project page - When I visit project "Internal" page - Then I should be redirected to sign in page - - Scenario: I visit private project page - When I visit project "Enterprise" page - Then I should be redirected to sign in page - - Scenario: I visit an empty public project page - Given public empty project "Empty Public Project" - When I visit empty project page - Then I should see empty public project details - And I should see empty public project details with http clone info - - Scenario: I visit an empty public project page as user with no ssh-keys - Given I sign in as a user - And I have no ssh keys - And public empty project "Empty Public Project" - When I visit empty project page - Then I should see empty public project details - And I should see empty public project details with http clone info - - Scenario: I visit an empty public project page as user with an ssh-key - Given I sign in as a user - And I have an ssh key - And public empty project "Empty Public Project" - When I visit empty project page - Then I should see empty public project details - And I should see empty public project details with ssh clone info - - Scenario: I visit public area as user - Given archived project "Archive" - And I sign in as a user - When I visit the public projects area - Then I should see project "Community" - And I should see project "Internal" - And I should not see project "Enterprise" - And I should not see project "Archive" - - Scenario: I visit internal project page as user - Given I sign in as a user - When I visit project "Internal" page - Then I should see project "Internal" home page - - Scenario: I visit public project page - When I visit project "Community" page - Then I should see project "Community" home page - And I should see an http link to the repository - - Scenario: I visit public project page as user with no ssh-keys - Given I sign in as a user - And I have no ssh keys - When I visit project "Community" page - Then I should see project "Community" home page - And I should see an http link to the repository - - Scenario: I visit public project page as user with an ssh-key - Given I sign in as a user - And I have an ssh key - When I visit project "Community" page - Then I should see project "Community" home page - And I should see an ssh link to the repository - - Scenario: I visit an empty public project page - Given public empty project "Empty Public Project" - When I visit empty project page - Then I should see empty public project details - - Scenario: I visit public project issues page as a non authorized user - Given I visit project "Community" page - Then I should not see command line instructions - And I visit "Community" issues page - Then I should see list of issues for "Community" project - - Scenario: I visit public project issues page as authorized user - Given I sign in as a user - Given I visit project "Community" page - And I visit "Community" issues page - Then I should see list of issues for "Community" project - - Scenario: I visit internal project issues page as authorized user - Given I sign in as a user - Given I visit project "Internal" page - And I visit "Internal" issues page - Then I should see list of issues for "Internal" project - - Scenario: I visit public project merge requests page as an authorized user - Given I sign in as a user - Given I visit project "Community" page - And I visit "Community" merge requests page - And project "Community" has "Bug fix" open merge request - Then I should see list of merge requests for "Community" project - - Scenario: I visit public project merge requests page as a non authorized user - Given I visit project "Community" page - And I visit "Community" merge requests page - And project "Community" has "Bug fix" open merge request - Then I should see list of merge requests for "Community" project - - Scenario: I visit internal project merge requests page as an authorized user - Given I sign in as a user - Given I visit project "Internal" page - And I visit "Internal" merge requests page - And project "Internal" has "Feature implemented" open merge request - Then I should see list of merge requests for "Internal" project - - Scenario: Trending page - Given archived project "Archive" - And project "Archive" has comments - And I sign in as a user - And project "Community" has comments - And trending projects are refreshed - When I visit the explore trending projects - Then I should see project "Community" - And I should not see project "Internal" - And I should not see project "Enterprise" - And I should not see project "Archive" - - Scenario: Most starred page - Given archived project "Archive" - And I sign in as a user - When I visit the explore starred projects - Then I should see project "Community" - And I should see project "Internal" - And I should not see project "Archive" diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb deleted file mode 100644 index 962e39dde9a..00000000000 --- a/features/steps/explore/projects.rb +++ /dev/null @@ -1,145 +0,0 @@ -class Spinach::Features::ExploreProjects < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - include SharedUser - - step 'I should see project "Empty Public Project"' do - expect(page).to have_content "Empty Public Project" - end - - step 'I should see public project details' do - expect(page).to have_content '32 branches' - expect(page).to have_content '16 tags' - end - - step 'I should see project readme' do - expect(page).to have_content 'README.md' - end - - step 'I should see empty public project details' do - expect(page).not_to have_content 'Git global setup' - end - - step 'I should see empty public project details with http clone info' do - project = Project.find_by(name: 'Empty Public Project') - page.all(:css, '.git-empty .clone').each do |element| - expect(element.text).to include(project.http_url_to_repo) - end - end - - step 'I should see empty public project details with ssh clone info' do - project = Project.find_by(name: 'Empty Public Project') - page.all(:css, '.git-empty .clone').each do |element| - expect(element.text).to include(project.url_to_repo) - end - end - - step 'I should see project "Community" home page' do - page.within '.breadcrumbs .breadcrumb-item-text' do - expect(page).to have_content 'Community' - end - end - - step 'I should see project "Internal" home page' do - page.within '.breadcrumbs .breadcrumb-item-text' do - expect(page).to have_content 'Internal' - end - end - - step 'I should see an http link to the repository' do - project = Project.find_by(name: 'Community') - expect(page).to have_field('project_clone', with: project.http_url_to_repo) - end - - step 'I should see an ssh link to the repository' do - project = Project.find_by(name: 'Community') - expect(page).to have_field('project_clone', with: project.url_to_repo) - end - - step 'I visit "Community" issues page' do - create(:issue, - title: "Bug", - project: public_project - ) - create(:issue, - title: "New feature", - project: public_project - ) - visit project_issues_path(public_project) - end - - step 'I should see list of issues for "Community" project' do - expect(page).to have_content "Bug" - expect(page).to have_content public_project.name - expect(page).to have_content "New feature" - end - - step 'I visit "Internal" issues page' do - create(:issue, - title: "Internal Bug", - project: internal_project - ) - create(:issue, - title: "New internal feature", - project: internal_project - ) - visit project_issues_path(internal_project) - end - - step 'I should see list of issues for "Internal" project' do - expect(page).to have_content "Internal Bug" - expect(page).to have_content internal_project.name - expect(page).to have_content "New internal feature" - end - - step 'I visit "Community" merge requests page' do - visit project_merge_requests_path(public_project) - end - - step 'project "Community" has "Bug fix" open merge request' do - create(:merge_request, - title: "Bug fix for public project", - source_project: public_project, - target_project: public_project - ) - end - - step 'I should see list of merge requests for "Community" project' do - expect(page).to have_content public_project.name - expect(page).to have_content public_merge_request.source_project.name - end - - step 'I visit "Internal" merge requests page' do - visit project_merge_requests_path(internal_project) - end - - step 'project "Internal" has "Feature implemented" open merge request' do - create(:merge_request, - title: "Feature implemented", - source_project: internal_project, - target_project: internal_project - ) - end - - step 'I should see list of merge requests for "Internal" project' do - expect(page).to have_content internal_project.name - expect(page).to have_content internal_merge_request.source_project.name - end - - def internal_project - @internal_project ||= Project.find_by!(name: 'Internal') - end - - def public_project - @public_project ||= Project.find_by!(name: 'Community') - end - - def internal_merge_request - @internal_merge_request ||= MergeRequest.find_by!(title: 'Feature implemented') - end - - def public_merge_request - @public_merge_request ||= MergeRequest.find_by!(title: 'Bug fix for public project') - end -end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index be69a96c3ee..dc0e3ac59a5 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -454,19 +454,6 @@ module SharedPaths # ---------------------------------------- # Public Projects # ---------------------------------------- - - step 'I visit the public projects area' do - visit explore_projects_path - end - - step 'I visit the explore trending projects' do - visit trending_explore_projects_path - end - - step 'I visit the explore starred projects' do - visit starred_explore_projects_path - end - step 'I visit the public groups area' do visit explore_groups_path end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 96cc0745e97..5e4edaf99a6 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -112,10 +112,6 @@ module SharedProject # Visibility of archived project # ---------------------------------------- - step 'archived project "Archive"' do - create(:project, :archived, :public, :repository, name: 'Archive') - end - step 'I should not see project "Archive"' do project = Project.find_by(name: "Archive") expect(page).not_to have_content project.name_with_namespace @@ -126,11 +122,6 @@ module SharedProject expect(page).to have_content project.name_with_namespace end - step 'project "Archive" has comments' do - project = Project.find_by(name: "Archive") - 2.times { create(:note_on_issue, project: project) } - end - # ---------------------------------------- # Visibility level # ---------------------------------------- @@ -209,15 +200,6 @@ module SharedProject create :project_empty_repo, :public, name: "Empty Public Project" end - step 'project "Community" has comments' do - project = Project.find_by(name: "Community") - 2.times { create(:note_on_issue, project: project) } - end - - step 'trending projects are refreshed' do - TrendingProject.refresh! - end - step 'project "Shop" has labels: "bug", "feature", "enhancement"' do project = Project.find_by(name: "Shop") create(:label, project: project, title: 'bug') diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb new file mode 100644 index 00000000000..6ac9497b024 --- /dev/null +++ b/spec/features/explore/user_explores_projects_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe 'User explores projects' do + set(:archived_project) { create(:project, :archived) } + set(:internal_project) { create(:project, :internal) } + set(:private_project) { create(:project, :private) } + set(:public_project) { create(:project, :public) } + + shared_examples_for 'shows public projects' do + it 'shows projects' do + expect(page).to have_content(public_project.title) + expect(page).not_to have_content(internal_project.title) + expect(page).not_to have_content(private_project.title) + expect(page).not_to have_content(archived_project.title) + end + end + + shared_examples_for 'shows public and internal projects' do + it 'shows projects' do + expect(page).to have_content(public_project.title) + expect(page).to have_content(internal_project.title) + expect(page).not_to have_content(private_project.title) + expect(page).not_to have_content(archived_project.title) + end + end + + context 'when not signed in' do + context 'when viewing public projects' do + before do + visit(explore_projects_path) + end + + include_examples 'shows public projects' + end + end + + context 'when signed in' do + set(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when viewing public projects' do + before do + visit(explore_projects_path) + end + + include_examples 'shows public and internal projects' + end + + context 'when viewing most starred projects' do + before do + visit(starred_explore_projects_path) + end + + include_examples 'shows public and internal projects' + end + + context 'when viewing trending projects' do + before do + [archived_project, public_project].each { |project| create(:note_on_issue, project: project) } + + TrendingProject.refresh! + + visit(trending_explore_projects_path) + end + + include_examples 'shows public projects' + end + end +end diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb deleted file mode 100644 index 9fc03f49f5b..00000000000 --- a/spec/features/projects/issues/list_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -feature 'Issues List' do - let(:user) { create(:user) } - let(:project) { create(:project) } - - background do - project.team << [user, :developer] - - sign_in(user) - end - - scenario 'user does not see create new list button' do - create(:issue, project: project) - - visit project_issues_path(project) - - expect(page).not_to have_selector('.js-new-board-list') - end -end diff --git a/spec/features/projects/issues/user_views_issues_spec.rb b/spec/features/projects/issues/user_views_issues_spec.rb new file mode 100644 index 00000000000..d35009b8974 --- /dev/null +++ b/spec/features/projects/issues/user_views_issues_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe 'User views issues' do + set(:user) { create(:user) } + + shared_examples_for 'shows issues' do + it 'shows issues' do + expect(page).to have_content(project.name) + .and have_content(issue1.title) + .and have_content(issue2.title) + .and have_no_selector('.js-new-board-list') + end + end + + context 'when project is public' do + set(:project) { create(:project_empty_repo, :public) } + set(:issue1) { create(:issue, project: project) } + set(:issue2) { create(:issue, project: project) } + + context 'when signed in' do + before do + project.add_developer(user) + sign_in(user) + + visit(project_issues_path(project)) + end + + include_examples 'shows issues' + end + + context 'when not signed in' do + before do + visit(project_issues_path(project)) + end + + include_examples 'shows issues' + end + end + + context 'when project is internal' do + set(:project) { create(:project_empty_repo, :internal) } + set(:issue1) { create(:issue, project: project) } + set(:issue2) { create(:issue, project: project) } + + context 'when signed in' do + before do + project.add_developer(user) + sign_in(user) + + visit(project_issues_path(project)) + end + + include_examples 'shows issues' + end + end +end diff --git a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb index 07b8c1ef479..bf95dbb7d09 100644 --- a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb +++ b/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb @@ -1,72 +1,115 @@ require 'spec_helper' describe 'User views open merge requests' do - let(:project) { create(:project, :public, :repository) } + set(:user) { create(:user) } - context "when the target branch is the project's default branch" do - let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let!(:closed_merge_request) { create(:closed_merge_request, source_project: project, target_project: project) } - - before do - visit(project_merge_requests_path(project)) - end - - it 'shows open merge requests' do - expect(page).to have_content(merge_request.title).and have_no_content(closed_merge_request.title) - end - - it 'does not show target branch name' do - expect(page).to have_content(merge_request.title) - expect(find('.issuable-info')).not_to have_content(project.default_branch) + shared_examples_for 'shows merge requests' do + it 'shows merge requests' do + expect(page).to have_content(project.name).and have_content(merge_request.source_project.name) end end - context "when the target branch is different from the project's default branch" do - let!(:merge_request) do - create(:merge_request, - source_project: project, - target_project: project, - source_branch: 'fix', - target_branch: 'feature_conflict') - end + context 'when project is public' do + set(:project) { create(:project, :public, :repository) } - before do - visit(project_merge_requests_path(project)) - end + context 'when not signed in' do + context "when the target branch is the project's default branch" do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let!(:closed_merge_request) { create(:closed_merge_request, source_project: project, target_project: project) } - it 'shows target branch name' do - expect(page).to have_content(merge_request.target_branch) - end - end + before do + visit(project_merge_requests_path(project)) + end - context 'when a merge request has pipelines' do - let!(:build) { create :ci_build, pipeline: pipeline } + include_examples 'shows merge requests' - let(:merge_request) do - create(:merge_request_with_diffs, - source_project: project, - target_project: project, - source_branch: 'merge-test') - end + it 'shows open merge requests' do + expect(page).to have_content(merge_request.title).and have_no_content(closed_merge_request.title) + end - let(:pipeline) do - create(:ci_pipeline, - project: project, - sha: merge_request.diff_head_sha, - ref: merge_request.source_branch, - head_pipeline_of: merge_request) - end - - before do - project.enable_ci - - visit(project_merge_requests_path(project)) - end - - it 'shows pipeline status' do - page.within('.mr-list') do - expect(page).to have_link('Pipeline: pending') + it 'does not show target branch name' do + expect(page).to have_content(merge_request.title) + expect(find('.issuable-info')).not_to have_content(project.default_branch) + end end + + context "when the target branch is different from the project's default branch" do + let!(:merge_request) do + create(:merge_request, + source_project: project, + target_project: project, + source_branch: 'fix', + target_branch: 'feature_conflict') + end + + before do + visit(project_merge_requests_path(project)) + end + + it 'shows target branch name' do + expect(page).to have_content(merge_request.target_branch) + end + end + + context 'when a merge request has pipelines' do + let!(:build) { create :ci_build, pipeline: pipeline } + + let(:merge_request) do + create(:merge_request_with_diffs, + source_project: project, + target_project: project, + source_branch: 'merge-test') + end + + let(:pipeline) do + create(:ci_pipeline, + project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + head_pipeline_of: merge_request) + end + + before do + project.enable_ci + + visit(project_merge_requests_path(project)) + end + + it 'shows pipeline status' do + page.within('.mr-list') do + expect(page).to have_link('Pipeline: pending') + end + end + end + end + + context 'when signed in' do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + + before do + project.add_developer(user) + sign_in(user) + + visit(project_merge_requests_path(project)) + end + + include_examples 'shows merge requests' + end + end + + context 'when project is internal' do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + set(:project) { create(:project, :internal, :repository) } + + context 'when signed in' do + before do + project.add_developer(user) + sign_in(user) + + visit(project_merge_requests_path(project)) + end + + include_examples 'shows merge requests' end end end diff --git a/spec/features/projects/user_views_details_spec.rb b/spec/features/projects/user_views_details_spec.rb new file mode 100644 index 00000000000..ffc063654cd --- /dev/null +++ b/spec/features/projects/user_views_details_spec.rb @@ -0,0 +1,151 @@ +require 'spec_helper' + +describe 'User views details' do + set(:user) { create(:user) } + + shared_examples_for 'redirects to the sign in page' do + it 'redirects to the sign in page' do + expect(current_path).to eq(new_user_session_path) + end + end + + shared_examples_for 'shows details of empty project' do + let(:user_has_ssh_key) { false } + + it 'shows details' do + expect(page).not_to have_content('Git global setup') + + page.all(:css, '.git-empty .clone').each do |element| + expect(element.text).to include(project.http_url_to_repo) + end + + expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key + end + end + + shared_examples_for 'shows details of non empty project' do + let(:user_has_ssh_key) { false } + + it 'shows details' do + page.within('.breadcrumbs .breadcrumb-item-text') do + expect(page).to have_content(project.title) + end + + expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key + end + end + + context 'when project is public' do + context 'when project is empty' do + set(:project) { create(:project_empty_repo, :public) } + + context 'when not signed in' do + before do + visit(project_path(project)) + end + + include_examples 'shows details of empty project' + end + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when user does not have ssh keys' do + before do + visit(project_path(project)) + end + + include_examples 'shows details of empty project' + end + + context 'when user has ssh keys' do + before do + create(:personal_key, user: user) + + visit(project_path(project)) + end + + include_examples 'shows details of empty project' do + let(:user_has_ssh_key) { true } + end + end + end + end + + context 'when project is not empty' do + set(:project) { create(:project, :public, :repository) } + + before do + visit(project_path(project)) + end + + context 'when not signed in' do + before do + allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com') + end + + include_examples 'shows details of non empty project' + end + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when user does not have ssh keys' do + before do + visit(project_path(project)) + end + + include_examples 'shows details of non empty project' + end + + context 'when user has ssh keys' do + before do + create(:personal_key, user: user) + + visit(project_path(project)) + end + + include_examples 'shows details of non empty project' do + let(:user_has_ssh_key) { true } + end + end + end + end + end + + context 'when project is internal' do + set(:project) { create(:project, :internal, :repository) } + + context 'when not signed in' do + before do + visit(project_path(project)) + end + + include_examples 'redirects to the sign in page' + end + + context 'when signed in' do + before do + sign_in(user) + + visit(project_path(project)) + end + + include_examples 'shows details of non empty project' + end + end + + context 'when project is private' do + set(:project) { create(:project, :private) } + + before do + visit(project_path(project)) + end + + include_examples 'redirects to the sign in page' + end +end From c756b0872f89232c7afcad934841fbffff4fc1bc Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 11 Oct 2017 07:26:43 +0200 Subject: [PATCH 066/104] Fix Google API callback https://gitlab.com/gitlab-org/gitlab-ce/issues/38911#note_42966393 --- doc/integration/google.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/integration/google.md b/doc/integration/google.md index 52de5dc61be..0611cbb59dc 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -30,7 +30,7 @@ In Google's side: ``` https://gitlab.example.com/users/auth/google_oauth2/callback - https://gitlab.example.com/google_api/auth/callback + https://gitlab.exampl.com/-/google_api/auth/callback ``` 1. You should now be able to see a Client ID and Client secret. Note them down From e7a0c6b35ad26955504b860445ddfab569053686 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 11 Oct 2017 05:55:58 +0000 Subject: [PATCH 067/104] Update Kubernetes Helm chart docs --- doc/install/kubernetes/gitlab_chart.md | 6 ++++-- doc/install/kubernetes/gitlab_omnibus.md | 10 +++++----- doc/install/kubernetes/index.md | 14 +++++++------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md index ddfd47df099..fa564d83785 100644 --- a/doc/install/kubernetes/gitlab_chart.md +++ b/doc/install/kubernetes/gitlab_chart.md @@ -1,6 +1,6 @@ # GitLab Helm Chart > **Note**: -* This chart will be replaced by the [gitlab-omnibus](gitlab_omnibus.md) chart, once it supports [additional configuration options](https://gitlab.com/charts/charts.gitlab.io/issues/68). For more information on available charts, please see our [overview](index.md#chart-overview). +* This chart is deprecated, and is being replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). For more information on available charts, please see our [overview](index.md#chart-overview). * These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues). @@ -8,7 +8,9 @@ For more information on available GitLab Helm Charts, please see our [overview]( ## Introduction -The `gitlab` Helm chart deploys just GitLab into your Kubernetes cluster, and offers extensive configuration options. This chart requires advanced knowledge of Kubernetes to successfully use. For most deployments we **strongly recommended** the [gitlab-omnibus](gitlab_omnibus.md) chart, which will replace this chart once it supports [additional configuration options](https://gitlab.com/charts/charts.gitlab.io/issues/68). Due to the difficulty in supporting upgrades to the `omnibus-gitlab` chart, migrating will require exporting data out of this instance and importing it into the new deployment. +The `gitlab` Helm chart deploys just GitLab into your Kubernetes cluster, and offers extensive configuration options. This chart requires advanced knowledge of Kubernetes to successfully use. We **strongly recommend** the [gitlab-omnibus](gitlab_omnibus.md) chart. + +This chart is deprecated, and will be replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). Due to the difficulty in supporting upgrades, migrating will require exporting data out of this instance and importing it into the new deployment. This chart includes the following: diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md index 8c110a37380..6659c3cf7b2 100644 --- a/doc/install/kubernetes/gitlab_omnibus.md +++ b/doc/install/kubernetes/gitlab_omnibus.md @@ -1,7 +1,6 @@ # GitLab-Omnibus Helm Chart > **Note:** -* This Helm chart is in beta, while [additional features](https://gitlab.com/charts/charts.gitlab.io/issues/68) are being worked on. -* GitLab is working on a [cloud native set of Charts](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) which will eventually replace these. +* This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). * These charts have been tested on Google Container Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues). This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work. @@ -12,6 +11,8 @@ For more information on available GitLab Helm Charts, please see our [overview]( This chart provides an easy way to get started with GitLab, provisioning an installation with nearly all functionality enabled. SSL is automatically provisioned via [Let's Encrypt](https://letsencrypt.org/). +This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) once available. Due to the difficulty in supporting upgrades, migrating will require exporting data out of this instance and importing it into the new deployment. + The deployment includes: - A [GitLab Omnibus](https://docs.gitlab.com/omnibus/) Pod, including Mattermost, Container Registry, and Prometheus @@ -23,9 +24,8 @@ The deployment includes: ### Limitations -* This chart is suited for small to medium size deployments, because [High Availability](https://docs.gitlab.com/ee/administration/high_availability/) and [Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html) will not be supported. -* It is in beta. Additional features to support production deployments, like backups, are [in development](https://gitlab.com/charts/charts.gitlab.io/issues/68). Once completed, this chart will be generally available. -* A new generation of [cloud native charts](index.md#upcoming-cloud-native-helm-charts) is in development, and will eventually deprecate these. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment. We do not expect these to be production ready before the second half of 2018. +* This chart is in beta, and suited for small to medium size deployments. [High Availability](https://docs.gitlab.com/ee/administration/high_availability/) and [Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html) are not supported. +* A new generation [cloud native GitLab chart](index.md#cloud-native-gitlab-chart) is in development, and will deprecate this chart. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment. We plan to release the new chart in beta by the end of 2017. For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview). diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md index aed00ae9e2c..dd350820c18 100644 --- a/doc/install/kubernetes/index.md +++ b/doc/install/kubernetes/index.md @@ -9,11 +9,11 @@ should be deployed, upgraded, and configured. ## Chart Overview -* **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today. It is suited for small to medium deployments, and is in beta while support for backups and other features are added. +* **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small to medium deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart). * **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in development. Will support large deployments with horizontal scaling of individual GitLab components. * Other Charts * [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner. - * [Advanced GitLab Installation](gitlab_chart.md): Provides additional deployment options, but provides less functionality out-of-the-box. It's beta, no longer actively developed, and will be deprecated by [gitlab-omnibus](#gitlab-omnibus-chart-recommended) once it supports these options. + * [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box. * [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab chart. ## GitLab-Omnibus Chart (Recommended) @@ -21,13 +21,13 @@ should be deployed, upgraded, and configured. This chart is the best available way to operate GitLab on Kubernetes. It deploys and configures nearly all features of GitLab, including: a [Runner](https://docs.gitlab.com/runner/), [Container Registry](../../user/project/container_registry.html#gitlab-container-registry), [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/), [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego), and a [load balancer](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). It is based on our [GitLab Omnibus Docker Images](https://docs.gitlab.com/omnibus/docker/README.html). -Once the [cloud native charts](#upcoming-cloud-native-helm-charts) are ready for production use, this chart will be deprecated. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment. +Once the [cloud native GitLab chart](#cloud-native-gitlab-chart) is ready for production use, this chart will be deprecated. Due to the difficulty in supporting upgrades to the new architecture, migrating will require exporting data out of this instance and importing it into the new deployment. -Learn more about the [gitlab-omnibus chart.](gitlab_omnibus.md) +Learn more about the [gitlab-omnibus chart](gitlab_omnibus.md). ## Cloud Native GitLab Chart -GitLab is working towards building a [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). A key part of this effort is to isolate each service into its [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current charts](#official-gitlab-helm-charts-recommended). +GitLab is working towards building a [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). A key part of this effort is to isolate each service into its [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current chart](#gitlab-omnibus-chart-recommended). By offering individual containers and charts, we will be able to provide a number of benefits: * Easier horizontal scaling of each service, @@ -35,9 +35,9 @@ By offering individual containers and charts, we will be able to provide a numbe * Potential for rolling updates and canaries within a service, * and plenty more. -This is a large project and will be worked on over the span of multiple releases. For the most up-to-date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420). We do not expect these to be production ready before the second half of 2018. +This is a large project and will be worked on over the span of multiple releases. For the most up-to-date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420). We are planning to launch this chart in beta by the end of 2017. -Learn more about the [cloud native GitLab chart.](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) +Learn more about the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). ## Other Charts From 676840ff9c4e85e44b2f2c6e7c92f62f834e20aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 11 Oct 2017 05:59:34 +0000 Subject: [PATCH 068/104] Refactor the Development documentation, and divide the Testing documentation into multiple pages --- CONTRIBUTING.md | 8 +- PROCESS.md | 2 +- doc/development/README.md | 114 ++-- doc/development/fe_guide/index.md | 3 +- doc/development/fe_guide/testing.md | 255 +------- doc/development/testing.md | 567 +----------------- .../testing_guide/best_practices.md | 272 +++++++++ doc/development/testing_guide/ci.md | 52 ++ doc/development/testing_guide/flaky_tests.md | 74 +++ .../testing_guide/frontend_testing.md | 254 ++++++++ .../img/testing_triangle.png | Bin doc/development/testing_guide/index.md | 90 +++ .../testing_guide/testing_levels.md | 173 ++++++ .../testing_guide/testing_rake_tasks.md | 39 ++ 14 files changed, 1027 insertions(+), 876 deletions(-) create mode 100644 doc/development/testing_guide/best_practices.md create mode 100644 doc/development/testing_guide/ci.md create mode 100644 doc/development/testing_guide/flaky_tests.md create mode 100644 doc/development/testing_guide/frontend_testing.md rename doc/development/{fe_guide => testing_guide}/img/testing_triangle.png (100%) create mode 100644 doc/development/testing_guide/index.md create mode 100644 doc/development/testing_guide/testing_levels.md create mode 100644 doc/development/testing_guide/testing_rake_tasks.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b261bd5938..83e41a11e52 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -296,9 +296,9 @@ might be edited to make them small and simple. Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker. -For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should -be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may -need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself. +For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should +be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may +need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself. If you want to create something yourself, consider opening an issue first to discuss whether it is interesting to include this in GitLab. @@ -658,7 +658,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [license-finder-doc]: doc/development/licensing.md [GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues [polling-etag]: https://docs.gitlab.com/ce/development/polling.html -[testing]: doc/development/testing.md +[testing]: doc/development/testing_guide/index.md [^1]: Please note that specs other than JavaScript specs are considered backend code. diff --git a/PROCESS.md b/PROCESS.md index 5e65bb59246..06963243b25 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -1,4 +1,4 @@ -## GitLab Core Team & GitLab Inc. Contribution Process +## GitLab core team & GitLab Inc. contribution process --- diff --git a/doc/development/README.md b/doc/development/README.md index b648c7ce086..e2d0c6c2056 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -1,75 +1,89 @@ -# Development +# GitLab development guides -## Outside of docs +## Get started! -- [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) main contributing guide -- [PROCESS.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) contributing process -- [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/README.md) to install a development version +- Setup GitLab's development environment with [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/README.md) +- [GitLab contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) +- [Architecture](architecture.md) of GitLab +- [Rake tasks](rake_tasks.md) for development -## Styleguides +## Processes + +- [GitLab core team & GitLab Inc. contribution process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) +- [Generate a changelog entry with `bin/changelog`](changelog.md) +- [Code review guidelines](code_review.md) for reviewing code and having code reviewed. +- [Limit conflicts with EE when developing on CE](limit_ee_conflicts.md) + +## UX and frontend guides -- [API styleguide](api_styleguide.md) Use this styleguide if you are - contributing to the API. -- [Documentation styleguide](doc_styleguide.md) Use this styleguide if you are - contributing to documentation. -- [Writing documentation](writing_documentation.md) - - [Distinction between general documentation and technical articles](writing_documentation.md#distinction-between-general-documentation-and-technical-articles) -- [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations -- [Testing standards and style guidelines](testing.md) - [UX guide](ux_guide/index.md) for building GitLab with existing CSS styles and elements - [Frontend guidelines](fe_guide/index.md) -- [SQL guidelines](sql.md) for working with SQL queries + +## Backend guides + +- [Testing standards and style guidelines](testing_guide/index.md) +- [API styleguide](api_styleguide.md) Use this styleguide if you are + contributing to the API. - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers +- [Working with Gitaly](gitaly.md) +- [Manage feature flags](feature_flags.md) +- [View sent emails or preview mailers](emails.md) +- [Shell commands](shell_commands.md) in the GitLab codebase - [`Gemfile` guidelines](gemfile.md) +- [Sidekiq debugging](sidekiq_debugging.md) +- [Gotchas](gotchas.md) to avoid +- [Issue and merge requests state models](object_state_models.md) +- [How to dump production data to staging](db_dump.md) -## Process +## Performance guides -- [Generate a changelog entry with `bin/changelog`](changelog.md) -- [Limit conflicts with EE when developing on CE](limit_ee_conflicts.md) -- [Code review guidelines](code_review.md) for reviewing code and having code reviewed. +- [Instrumentation](instrumentation.md) +- [Performance guidelines](performance.md) - [Merge request performance guidelines](merge_request_performance_guidelines.md) for ensuring merge requests do not negatively impact GitLab performance -## Backend howtos +## Databases guides -- [Architecture](architecture.md) of GitLab -- [Gotchas](gotchas.md) to avoid -- [How to dump production data to staging](db_dump.md) -- [Instrumentation](instrumentation.md) -- [Performance guidelines](performance.md) -- [Rake tasks](rake_tasks.md) for development -- [Shell commands](shell_commands.md) in the GitLab codebase -- [Sidekiq debugging](sidekiq_debugging.md) -- [Object state models](object_state_models.md) -- [Building a package for testing purposes](build_test_package.md) -- [Manage feature flags](feature_flags.md) -- [View sent emails or preview mailers](emails.md) -- [Working with Gitaly](gitaly.md) +### Migrations -## Databases - -- [Merge Request Checklist](database_merge_request_checklist.md) - [What requires downtime?](what_requires_downtime.md) -- [Adding database indexes](adding_database_indexes.md) -- [Post Deployment Migrations](post_deployment_migrations.md) -- [Foreign Keys & Associations](foreign_keys.md) -- [Serializing Data](serializing_data.md) -- [Polymorphic Associations](polymorphic_associations.md) -- [Single Table Inheritance](single_table_inheritance.md) -- [Background Migrations](background_migrations.md) -- [Storing SHA1 Hashes As Binary](sha1_as_binary.md) -- [Iterating Tables In Batches](iterating_tables_in_batches.md) -- [Ordering Table Columns](ordering_table_columns.md) -- [Verifying Database Capabilities](verifying_database_capabilities.md) -- [Hash Indexes](hash_indexes.md) -- [Swapping Tables](swapping_tables.md) +- [SQL guidelines](sql.md) for working with SQL queries +- [Migrations style guide](migration_style_guide.md) for creating safe SQL migrations +- [Post deployment migrations](post_deployment_migrations.md) +- [Background migrations](background_migrations.md) +- [Swapping tables](swapping_tables.md) -## Internationalization (i18n) +### Best practices + +- [Merge Request checklist](database_merge_request_checklist.md) +- [Adding database indexes](adding_database_indexes.md) +- [Foreign keys & associations](foreign_keys.md) +- [Single table inheritance](single_table_inheritance.md) +- [Polymorphic associations](polymorphic_associations.md) +- [Serializing data](serializing_data.md) +- [Hash indexes](hash_indexes.md) +- [Storing SHA1 hashes as binary](sha1_as_binary.md) +- [Iterating tables in batches](iterating_tables_in_batches.md) +- [Ordering table columns](ordering_table_columns.md) +- [Verifying database capabilities](verifying_database_capabilities.md) + +## Documentation guides + +- [Documentation styleguide](doc_styleguide.md): Use this styleguide if you are + contributing to the documentation. +- [Writing documentation](writing_documentation.md) + - [Distinction between general documentation and technical articles](writing_documentation.md#distinction-between-general-documentation-and-technical-articles) + +## Internationalization (i18n) guides - [Introduction](i18n/index.md) - [Externalization](i18n/externalization.md) - [Translation](i18n/translation.md) +## Build guides + +- [Building a package for testing purposes](build_test_package.md) + ## Compliance - [Licensing](licensing.md) for ensuring license compliance diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 031b12a8e91..c0e1bfc12a1 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -54,7 +54,8 @@ or make changes to our frontend development guidelines. --- -## [Testing](testing.md) +## [Testing](../testing_guide/frontend_testing.md) + How we write frontend tests, run the GitLab test suite, and debug test related issues. diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md index 867c83f1e72..98e499b8c0f 100644 --- a/doc/development/fe_guide/testing.md +++ b/doc/development/fe_guide/testing.md @@ -1,254 +1 @@ -# Frontend Testing - -There are two types of test suites you'll encounter while developing frontend code -at GitLab. We use Karma and Jasmine for JavaScript unit and integration testing, and RSpec -feature tests with Capybara for e2e (end-to-end) integration testing. - -Unit and feature tests need to be written for all new features. -Most of the time, you should use rspec for your feature tests. -There are cases where the behaviour you are testing is not worth the time spent running the full application, -for example, if you are testing styling, animation, edge cases or small actions that don't involve the backend, -you should write an integration test using Jasmine. - -![Testing priority triangle](img/testing_triangle.png) - -_This diagram demonstrates the relative priority of each test type we use_ - -Regression tests should be written for bug fixes to prevent them from recurring in the future. - -See [the Testing Standards and Style Guidelines](../testing.md) -for more information on general testing practices at GitLab. - -## Karma test suite - -GitLab uses the [Karma][karma] test runner with [Jasmine][jasmine] as its test -framework for our JavaScript unit and integration tests. For integration tests, -we generate HTML files using RSpec (see `spec/javascripts/fixtures/*.rb` for examples). -Some fixtures are still HAML templates that are translated to HTML files using the same mechanism (see `static_fixtures.rb`). -Adding these static fixtures should be avoided as they are harder to keep up to date with real views. -The existing static fixtures will be migrated over time. -Please see [gitlab-org/gitlab-ce#24753](https://gitlab.com/gitlab-org/gitlab-ce/issues/24753) to track our progress. -Fixtures are served during testing by the [jasmine-jquery][jasmine-jquery] plugin. - -JavaScript tests live in `spec/javascripts/`, matching the folder structure -of `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js` -has a corresponding `spec/javascripts/behaviors/autosize_spec.js` file. - -Keep in mind that in a CI environment, these tests are run in a headless -browser and you will not have access to certain APIs, such as -[`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification), -which will have to be stubbed. - -### Best practice - -#### Naming unit tests - -When writing describe test blocks to test specific functions/methods, -please use the method name as the describe block name. - -```javascript -// Good -describe('methodName', () => { - it('passes', () => { - expect(true).toEqual(true); - }); -}); - -// Bad -describe('#methodName', () => { - it('passes', () => { - expect(true).toEqual(true); - }); -}); - -// Bad -describe('.methodName', () => { - it('passes', () => { - expect(true).toEqual(true); - }); -}); -``` -#### Testing Promises - -When testing Promises you should always make sure that the test is asynchronous and rejections are handled. -Your Promise chain should therefore end with a call of the `done` callback and `done.fail` in case an error occurred. - -```javascript -// Good -it('tests a promise', (done) => { - promise - .then((data) => { - expect(data).toBe(asExpected); - }) - .then(done) - .catch(done.fail); -}); - -// Good -it('tests a promise rejection', (done) => { - promise - .then(done.fail) - .catch((error) => { - expect(error).toBe(expectedError); - }) - .then(done) - .catch(done.fail); -}); - -// Bad (missing done callback) -it('tests a promise', () => { - promise - .then((data) => { - expect(data).toBe(asExpected); - }) -}); - -// Bad (missing catch) -it('tests a promise', (done) => { - promise - .then((data) => { - expect(data).toBe(asExpected); - }) - .then(done) -}); - -// Bad (use done.fail in asynchronous tests) -it('tests a promise', (done) => { - promise - .then((data) => { - expect(data).toBe(asExpected); - }) - .then(done) - .catch(fail) -}); - -// Bad (missing catch) -it('tests a promise rejection', (done) => { - promise - .catch((error) => { - expect(error).toBe(expectedError); - }) - .then(done) -}); -``` - -#### Stubbing - -For unit tests, you should stub methods that are unrelated to the current unit you are testing. -If you need to use a prototype method, instantiate an instance of the class and call it there instead of mocking the instance completely. - -For integration tests, you should stub methods that will effect the stability of the test if they -execute their original behaviour. i.e. Network requests. - -### Vue.js unit tests -See this [section][vue-test]. - -### Running frontend tests - -`rake karma` runs the frontend-only (JavaScript) tests. -It consists of two subtasks: - -- `rake karma:fixtures` (re-)generates fixtures -- `rake karma:tests` actually executes the tests - -As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`) -is sufficient (and saves you some time). - -### Live testing and focused testing - -While developing locally, it may be helpful to keep karma running so that you -can get instant feedback on as you write tests and modify code. To do this -you can start karma with `npm run karma-start`. It will compile the javascript -assets and run a server at `http://localhost:9876/` where it will automatically -run the tests on any browser which connects to it. You can enter that url on -multiple browsers at once to have it run the tests on each in parallel. - -While karma is running, any changes you make will instantly trigger a recompile -and retest of the entire test suite, so you can see instantly if you've broken -a test with your changes. You can use [jasmine focused][jasmine-focus] or -excluded tests (with `fdescribe` or `xdescribe`) to get karma to run only the -tests you want while you're working on a specific feature, but make sure to -remove these directives when you commit your code. - -## RSpec Feature Integration Tests - -Information on setting up and running RSpec integration tests with -[Capybara][capybara] can be found in the -[general testing guide](../testing.md). - -## Gotchas - -### Errors due to use of unsupported JavaScript features - -Similar errors will be thrown if you're using JavaScript features not yet -supported by the PhantomJS test runner which is used for both Karma and RSpec -tests. We polyfill some JavaScript objects for older browsers, but some -features are still unavailable: - -- Array.from -- Array.first -- Async functions -- Generators -- Array destructuring -- For..Of -- Symbol/Symbol.iterator -- Spread - -Until these are polyfilled appropriately, they should not be used. Please -update this list with additional unsupported features. - -### RSpec errors due to JavaScript - -By default RSpec unit tests will not run JavaScript in the headless browser -and will simply rely on inspecting the HTML generated by rails. - -If an integration test depends on JavaScript to run correctly, you need to make -sure the spec is configured to enable JavaScript when the tests are run. If you -don't do this you'll see vague error messages from the spec runner. - -To enable a JavaScript driver in an `rspec` test, add `:js` to the -individual spec or the context block containing multiple specs that need -JavaScript enabled: - -```ruby -# For one spec -it 'presents information about abuse report', :js do - # assertions... -end - -describe "Admin::AbuseReports", :js do - it 'presents information about abuse report' do - # assertions... - end - it 'shows buttons for adding to abuse report' do - # assertions... - end -end -``` - -### Spinach errors due to missing JavaScript - -> **Note:** Since we are discouraging the use of Spinach when writing new -> feature tests, you shouldn't ever need to use this. This information is kept -> available for legacy purposes only. - -In Spinach, the JavaScript driver is enabled differently. In the `*.feature` -file for the failing spec, add the `@javascript` flag above the Scenario: - -``` -@javascript -Scenario: Developer can approve merge request - Given I am a "Shop" developer - And I visit project "Shop" merge requests page - And merge request 'Bug NS-04' must be approved - And I click link "Bug NS-04" - When I click link "Approve" - Then I should see approved merge request "Bug NS-04" -``` - -[capybara]: http://teamcapybara.github.io/capybara/ -[jasmine]: https://jasmine.github.io/ -[jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html -[jasmine-jquery]: https://github.com/velesin/jasmine-jquery -[karma]: http://karma-runner.github.io/ -[vue-test]:https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components +This document was moved to [../testing_guide/frontend_testing.md](../testing_guide/frontend_testing.md). diff --git a/doc/development/testing.md b/doc/development/testing.md index 4d5b90de6fc..45b1519ece8 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -1,566 +1 @@ -# Testing Standards and Style Guidelines - -This guide outlines standards and best practices for automated testing of GitLab -CE and EE. - -It is meant to be an _extension_ of the [thoughtbot testing -styleguide](https://github.com/thoughtbot/guides/tree/master/style/testing). If -this guide defines a rule that contradicts the thoughtbot guide, this guide -takes precedence. Some guidelines may be repeated verbatim to stress their -importance. - -## Definitions - -### Unit tests - -Formal definition: https://en.wikipedia.org/wiki/Unit_testing - -These kind of tests ensure that a single unit of code (a method) works as -expected (given an input, it has a predictable output). These tests should be -isolated as much as possible. For example, model methods that don't do anything -with the database shouldn't need a DB record. Classes that don't need database -records should use stubs/doubles as much as possible. - -| Code path | Tests path | Testing engine | Notes | -| --------- | ---------- | -------------- | ----- | -| `app/finders/` | `spec/finders/` | RSpec | | -| `app/helpers/` | `spec/helpers/` | RSpec | | -| `app/db/{post_,}migrate/` | `spec/migrations/` | RSpec | More details at [`spec/migrations/README.md`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md). | -| `app/policies/` | `spec/policies/` | RSpec | | -| `app/presenters/` | `spec/presenters/` | RSpec | | -| `app/routing/` | `spec/routing/` | RSpec | | -| `app/serializers/` | `spec/serializers/` | RSpec | | -| `app/services/` | `spec/services/` | RSpec | | -| `app/tasks/` | `spec/tasks/` | RSpec | | -| `app/uploaders/` | `spec/uploaders/` | RSpec | | -| `app/views/` | `spec/views/` | RSpec | | -| `app/workers/` | `spec/workers/` | RSpec | | -| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [JavaScript](#javascript) section. | - -### Integration tests - -Formal definition: https://en.wikipedia.org/wiki/Integration_testing - -These kind of tests ensure that individual parts of the application work well together, without the overhead of the actual app environment (i.e. the browser). These tests should assert at the request/response level: status code, headers, body. They're useful to test permissions, redirections, what view is rendered etc. - -| Code path | Tests path | Testing engine | Notes | -| --------- | ---------- | -------------- | ----- | -| `app/controllers/` | `spec/controllers/` | RSpec | | -| `app/mailers/` | `spec/mailers/` | RSpec | | -| `lib/api/` | `spec/requests/api/` | RSpec | | -| `lib/ci/api/` | `spec/requests/ci/api/` | RSpec | | -| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [JavaScript](#javascript) section. | - -#### About controller tests - -In an ideal world, controllers should be thin. However, when this is not the -case, it's acceptable to write a system/feature test without JavaScript instead -of a controller test. The reason is that testing a fat controller usually -involves a lot of stubbing, things like: - -```ruby -controller.instance_variable_set(:@user, user) -``` - -and use methods which are deprecated in Rails 5 ([#23768]). - -[#23768]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23768 - -#### About Karma - -As you may have noticed, Karma is both in the Unit tests and the Integration -tests category. That's because Karma is a tool that provides an environment to -run JavaScript tests, so you can either run unit tests (e.g. test a single -JavaScript method), or integration tests (e.g. test a component that is composed -of multiple components). - -### System tests or Feature tests - -Formal definition: https://en.wikipedia.org/wiki/System_testing. - -These kind of tests ensure the application works as expected from a user point -of view (aka black-box testing). These tests should test a happy path for a -given page or set of pages, and a test case should be added for any regression -that couldn't have been caught at lower levels with better tests (i.e. if a -regression is found, regression tests should be added at the lowest-level -possible). - -| Tests path | Testing engine | Notes | -| ---------- | -------------- | ----- | -| `spec/features/` | [Capybara] + [RSpec] | If your spec has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. | -| `features/` | Spinach | Spinach tests are deprecated, [you shouldn't add new Spinach tests](#spinach-feature-tests). | - -[Capybara]: https://github.com/teamcapybara/capybara -[RSpec]: https://github.com/rspec/rspec-rails#feature-specs -[Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist -[RackTest]: https://github.com/teamcapybara/capybara#racktest - -#### Best practices - -- Create only the necessary records in the database -- Test a happy path and a less happy path but that's it -- Every other possible path should be tested with Unit or Integration tests -- Test what's displayed on the page, not the internals of ActiveRecord models. - For instance, if you want to verify that a record was created, add - expectations that its attributes are displayed on the page, not that - `Model.count` increased by one. -- It's ok to look for DOM elements but don't abuse it since it makes the tests - more brittle - -If we're confident that the low-level components work well (and we should be if -we have enough Unit & Integration tests), we shouldn't need to duplicate their -thorough testing at the System test level. - -It's very easy to add tests, but a lot harder to remove or improve tests, so one -should take care of not introducing too many (slow and duplicated) specs. - -The reasons why we should follow these best practices are as follows: - -- System tests are slow to run since they spin up the entire application stack - in a headless browser, and even slower when they integrate a JS driver -- When system tests run with a JavaScript driver, the tests are run in a - different thread than the application. This means it does not share a - database connection and your test will have to commit the transactions in - order for the running application to see the data (and vice-versa). In that - case we need to truncate the database after each spec instead of simply - rolling back a transaction (the faster strategy that's in use for other kind - of tests). This is slower than transactions, however, so we want to use - truncation only when necessary. - -### Black-box tests or End-to-end tests - -GitLab consists of [multiple pieces] such as [GitLab Shell], [GitLab Workhorse], -[Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces -are configured and packaged by [GitLab Omnibus]. - -[GitLab QA] is a tool that allows to test that all these pieces integrate well -together by building a Docker image for a given version of GitLab Rails and -running feature tests (i.e. using Capybara) against it. - -The actual test scenarios and steps are [part of GitLab Rails] so that they're -always in-sync with the codebase. - -[multiple pieces]: ./architecture.md#components -[GitLab Shell]: https://gitlab.com/gitlab-org/gitlab-shell -[GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse -[Gitaly]: https://gitlab.com/gitlab-org/gitaly -[GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages -[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner -[GitLab Omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab -[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa -[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa - -## Test for what should not be there - -This is particularly important for permission calls and might be called a -negative assertion: make sure only the bare minimum is returned and nothing else. - -See an issue about [leaking tokens] as an example of a vulnerability that is -captured by such a test. - -[leaking tokens]: https://gitlab.com/gitlab-org/gitlab-ce/issues/37948 - -## How to test at the correct level? - -As many things in life, deciding what to test at each level of testing is a -trade-off: - -- Unit tests are usually cheap, and you should consider them like the basement - of your house: you need them to be confident that your code is behaving - correctly. However if you run only unit tests without integration / system - tests, you might [miss] the [big] [picture]! -- Integration tests are a bit more expensive, but don't abuse them. A system test - is often better than an integration test that is stubbing a lot of internals. -- System tests are expensive (compared to unit tests), even more if they require - a JavaScript driver. Make sure to follow the guidelines in the [Speed](#test-speed) - section. - -Another way to see it is to think about the "cost of tests", this is well -explained [in this article][tests-cost] and the basic idea is that the cost of a -test includes: - -- The time it takes to write the test -- The time it takes to run the test every time the suite runs -- The time it takes to understand the test -- The time it takes to fix the test if it breaks and the underlying code is OK -- Maybe, the time it takes to change the code to make the code testable. - -[miss]: https://twitter.com/ThePracticalDev/status/850748070698651649 -[big]: https://twitter.com/timbray/status/822470746773409794 -[picture]: https://twitter.com/withzombies/status/829716565834752000 -[tests-cost]: https://medium.com/table-xi/high-cost-tests-and-high-value-tests-a86e27a54df#.2ulyh3a4e - -## Frontend testing - -Please consult the [dedicated "Frontend testing" guide](./fe_guide/testing.md). - -## RSpec - -### General Guidelines - -- Use a single, top-level `describe ClassName` block. -- Use `.method` to describe class methods and `#method` to describe instance - methods. -- Use `context` to test branching logic. -- Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). -- Try to match the ordering of tests to the ordering within the class. -- Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines - to separate phases. -- Use `Gitlab.config.gitlab.host` rather than hard coding `'localhost'` -- Don't assert against the absolute value of a sequence-generated attribute (see - [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). -- Don't supply the `:each` argument to hooks since it's the default. -- On `before` and `after` hooks, prefer it scoped to `:context` over `:all` - -[four-phase-test]: https://robots.thoughtbot.com/four-phase-test - -### Automatic retries and flaky tests detection - -On our CI, we use [rspec-retry] to automatically retry a failing example a few -times (see [`spec/spec_helper.rb`] for the precise retries count). - -We also use a home-made `RspecFlaky::Listener` listener which records flaky -examples in a JSON report file on `master` (`retrieve-tests-metadata` and `update-tests-metadata` jobs), and warns when a new flaky example -is detected in any other branch (`flaky-examples-check` job). In the future, the -`flaky-examples-check` job will not be allowed to fail. - -[rspec-retry]: https://github.com/NoRedInk/rspec-retry -[`spec/spec_helper.rb`]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/spec_helper.rb - -### `let` variables - -GitLab's RSpec suite has made extensive use of `let` variables to reduce -duplication. However, this sometimes [comes at the cost of clarity][lets-not], -so we need to set some guidelines for their use going forward: - -- `let` variables are preferable to instance variables. Local variables are - preferable to `let` variables. -- Use `let` to reduce duplication throughout an entire spec file. -- Don't use `let` to define variables used by a single test; define them as - local variables inside the test's `it` block. -- Don't define a `let` variable inside the top-level `describe` block that's - only used in a more deeply-nested `context` or `describe` block. Keep the - definition as close as possible to where it's used. -- Try to avoid overriding the definition of one `let` variable with another. -- Don't define a `let` variable that's only used by the definition of another. - Use a helper method instead. - -[lets-not]: https://robots.thoughtbot.com/lets-not - -#### `set` variables - -In some cases there is no need to recreate the same object for tests again for -each example. For example, a project is needed to test issues on the same -project, one project will do for the entire file. This can be achieved by using -`set` in the same way you would use `let`. - -`rspec-set` only works on ActiveRecord objects, and before new examples it -reloads or recreates the model, _only_ if needed. That is, when you changed -properties or destroyed the object. - -There is one gotcha; you can't reference a model defined in a `let` block in a -`set` block. - -### Time-sensitive tests - -[Timecop](https://github.com/travisjeffery/timecop) is available in our -Ruby-based tests for verifying things that are time-sensitive. Any test that -exercises or verifies something time-sensitive should make use of Timecop to -prevent transient test failures. - -Example: - -```ruby -it 'is overdue' do - issue = build(:issue, due_date: Date.tomorrow) - - Timecop.freeze(3.days.from_now) do - expect(issue).to be_overdue - end -end -``` - -### System / Feature tests - -- Feature specs should be named `ROLE_ACTION_spec.rb`, such as - `user_changes_password_spec.rb`. -- Use only one `feature` block per feature spec file. -- Use scenario titles that describe the success and failure paths. -- Avoid scenario titles that add no information, such as "successfully". -- Avoid scenario titles that repeat the feature title. - -### Table-based / Parameterized tests - -This style of testing is used to exercise one piece of code with a comprehensive -range of inputs. By specifying the test case once, alongside a table of inputs -and the expected output for each, your tests can be made easier to read and more -compact. - -We use the [rspec-parameterized](https://github.com/tomykaira/rspec-parameterized) -gem. A short example, using the table syntax and checking Ruby equality for a -range of inputs, might look like this: - -```ruby -describe "#==" do - using RSpec::Parameterized::TableSyntax - - let(:project1) { create(:project) } - let(:project2) { create(:project) } - where(:a, :b, :result) do - 1 | 1 | true - 1 | 2 | false - true | true | true - true | false | false - project1 | project1 | true - project2 | project2 | true - project 1 | project2 | false - end - - with_them do - it { expect(a == b).to eq(result) } - - it 'is isomorphic' do - expect(b == a).to eq(result) - end - end -end -``` - -### Matchers - -Custom matchers should be created to clarify the intent and/or hide the -complexity of RSpec expectations.They should be placed under -`spec/support/matchers/`. Matchers can be placed in subfolder if they apply to -a certain type of specs only (e.g. features, requests etc.) but shouldn't be if -they apply to multiple type of specs. - -#### have_gitlab_http_status - -Prefer `have_gitlab_http_status` over `have_http_status` because the former -could also show the response body whenever the status mismatched. This would -be very useful whenever some tests start breaking and we would love to know -why without editing the source and rerun the tests. - -This is especially useful whenever it's showing 500 internal server error. - -### Shared contexts - -All shared contexts should be be placed under `spec/support/shared_contexts/`. -Shared contexts can be placed in subfolder if they apply to a certain type of -specs only (e.g. features, requests etc.) but shouldn't be if they apply to -multiple type of specs. - -Each file should include only one context and have a descriptive name, e.g. -`spec/support/shared_contexts/controllers/githubish_import_controller_shared_context.rb`. - -### Shared examples - -All shared examples should be be placed under `spec/support/shared_examples/`. -Shared examples can be placed in subfolder if they apply to a certain type of -specs only (e.g. features, requests etc.) but shouldn't be if they apply to -multiple type of specs. - -Each file should include only one context and have a descriptive name, e.g. -`spec/support/shared_examples/controllers/githubish_import_controller_shared_example.rb`. - -### Helpers - -Helpers are usually modules that provide some methods to hide the complexity of -specific RSpec examples. You can define helpers in RSpec files if they're not -intended to be shared with other specs. Otherwise, they should be be placed -under `spec/support/helpers/`. Helpers can be placed in subfolder if they apply -to a certain type of specs only (e.g. features, requests etc.) but shouldn't be -if they apply to multiple type of specs. - -Helpers should follow the Rails naming / namespacing convention. For instance -`spec/support/helpers/cycle_analytics_helpers.rb` should define: - -```ruby -module Spec - module Support - module Helpers - module CycleAnalyticsHelpers - def create_commit_referencing_issue(issue, branch_name: random_git_name) - project.repository.add_branch(user, branch_name, 'master') - create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name) - end - end - end - end -end -``` - -Helpers should not change the RSpec config. For instance, the helpers module -described above should not include: - -```ruby -RSpec.configure do |config| - config.include Spec::Support::Helpers::CycleAnalyticsHelpers -end -``` - -### Factories - -GitLab uses [factory_girl] as a test fixture replacement. - -- Factory definitions live in `spec/factories/`, named using the pluralization - of their corresponding model (`User` factories are defined in `users.rb`). -- There should be only one top-level factory definition per file. -- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and - should) call `create(...)` instead of `FactoryGirl.create(...)`. -- Make use of [traits] to clean up definitions and usages. -- When defining a factory, don't define attributes that are not required for the - resulting record to pass validation. -- When instantiating from a factory, don't supply attributes that aren't - required by the test. -- Factories don't have to be limited to `ActiveRecord` objects. - [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d). - -[factory_girl]: https://github.com/thoughtbot/factory_girl -[traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits - -### Fixtures - -All fixtures should be be placed under `spec/fixtures/`. - -### Config - -RSpec config files are files that change the RSpec config (i.e. -`RSpec.configure do |config|` blocks). They should be placed under -`spec/support/config/`. - -Each file should be related to a specific domain, e.g. -`spec/support/config/capybara.rb`, `spec/support/config/carrierwave.rb`, etc. - -Helpers can be included in the `spec/support/config/rspec.rb` file. If a -helpers module applies only to a certain kind of specs, it should add modifiers -to the `config.include` call. For instance if -`spec/support/helpers/cycle_analytics_helpers.rb` applies to `:lib` and -`type: :model` specs only, you would write the following: - -```ruby -RSpec.configure do |config| - config.include Spec::Support::Helpers::CycleAnalyticsHelpers, :lib - config.include Spec::Support::Helpers::CycleAnalyticsHelpers, type: :model -end -``` - -## Testing Rake Tasks - -To make testing Rake tasks a little easier, there is a helper that can be included -in lieu of the standard Spec helper. Instead of `require 'spec_helper'`, use -`require 'rake_helper'`. The helper includes `spec_helper` for you, and configures -a few other things to make testing Rake tasks easier. - -At a minimum, requiring the Rake helper will redirect `stdout`, include the -runtime task helpers, and include the `RakeHelpers` Spec support module. - -The `RakeHelpers` module exposes a `run_rake_task()` method to make -executing tasks simple. See `spec/support/rake_helpers.rb` for all available -methods. - -Example: - -```ruby -require 'rake_helper' - -describe 'gitlab:shell rake tasks' do - before do - Rake.application.rake_require 'tasks/gitlab/shell' - - stub_warn_user_is_not_gitlab - end - - describe 'install task' do - it 'invokes create_hooks task' do - expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke) - - run_rake_task('gitlab:shell:install') - end - end -end -``` - -## Test speed - -GitLab has a massive test suite that, without [parallelization], can take hours -to run. It's important that we make an effort to write tests that are accurate -and effective _as well as_ fast. - -Here are some things to keep in mind regarding test performance: - -- `double` and `spy` are faster than `FactoryGirl.build(...)` -- `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`. -- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`, - `spy`, or `double` will do. Database persistence is slow! -- Don't mark a feature as requiring JavaScript (through `@javascript` in - Spinach or `:js` in RSpec) unless it's _actually_ required for the test - to be valid. Headless browser testing is slow! - -[parallelization]: #test-suite-parallelization-on-the-ci - -### Test suite parallelization on the CI - -Our current CI parallelization setup is as follows: - -1. The `retrieve-tests-metadata` job in the `prepare` stage ensures that we have - a `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file: - - The `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file is fetched - from S3, if it's not here we initialize the file with `{}`. -1. Each `rspec-pg x y`/`rspec-mysql x y` job is run with `knapsack rspec` and - should have an evenly distributed share of tests: - - It works because the jobs have access to the - `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` since the "artifacts - from all previous stages are passed by default". [^1] - - The jobs set their own report path to - `KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json`. - - If knapsack is doing its job, test files that are run should be listed under - `Report specs`, not under `Leftover specs`. -1. The `update-tests-metadata` job takes all the - `knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json` - files from the `rspec-pg x y`/`rspec-mysql x y`jobs and merge them all together - into a single `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file that - is then uploaded to S3. - -After that, the next pipeline will use the up-to-date -`knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file. The same strategy -is used for Spinach tests as well. - -### Monitoring - -The GitLab test suite is [monitored] for the `master` branch, and any branch -that includes `rspec-profile` in their name. - -A [public dashboard] is available for everyone to see. Feel free to look at the -slowest test files and try to improve them. - -[monitored]: ./performance.md#rspec-profiling -[public dashboard]: https://redash.gitlab.com/public/dashboards/l1WhHXaxrCWM5Ai9D7YDqHKehq6OU3bx5gssaiWe?org_slug=default - -## CI setup - -- On CE and EE, the test suite runs both PostgreSQL and MySQL. -- Rails logging to `log/test.log` is disabled by default in CI [for - performance reasons][logging]. To override this setting, provide the - `RAILS_ENABLE_TEST_LOG` environment variable. - -[logging]: https://jtway.co/speed-up-your-rails-test-suite-by-6-in-1-line-13fedb869ec4 - -## Spinach (feature) tests - -GitLab [moved from Cucumber to Spinach](https://github.com/gitlabhq/gitlabhq/pull/1426) -for its feature/integration tests in September 2012. - -As of March 2016, we are [trying to avoid adding new Spinach -tests](https://gitlab.com/gitlab-org/gitlab-ce/issues/14121) going forward, -opting for [RSpec feature](#features-integration) specs. - -Adding new Spinach scenarios is acceptable _only if_ the new scenario requires -no more than one new `step` definition. If more than that is required, the -test should be re-implemented using RSpec instead. - ---- - -[Return to Development documentation](README.md) - -[^1]: /ci/yaml/README.html#dependencies +This document was moved to [testing_guide/index.md](testing_guide/index.md). diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md new file mode 100644 index 00000000000..613423dbd9a --- /dev/null +++ b/doc/development/testing_guide/best_practices.md @@ -0,0 +1,272 @@ +# Testing best practices + +## Test speed + +GitLab has a massive test suite that, without [parallelization], can take hours +to run. It's important that we make an effort to write tests that are accurate +and effective _as well as_ fast. + +Here are some things to keep in mind regarding test performance: + +- `double` and `spy` are faster than `FactoryGirl.build(...)` +- `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`. +- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`, + `spy`, or `double` will do. Database persistence is slow! +- Don't mark a feature as requiring JavaScript (through `@javascript` in + Spinach or `:js` in RSpec) unless it's _actually_ required for the test + to be valid. Headless browser testing is slow! + +[parallelization]: ci.md#test-suite-parallelization-on-the-ci + +## RSpec + +### General guidelines + +- Use a single, top-level `describe ClassName` block. +- Use `.method` to describe class methods and `#method` to describe instance + methods. +- Use `context` to test branching logic. +- Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](../gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). +- Try to match the ordering of tests to the ordering within the class. +- Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines + to separate phases. +- Use `Gitlab.config.gitlab.host` rather than hard coding `'localhost'` +- Don't assert against the absolute value of a sequence-generated attribute (see + [Gotchas](../gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). +- Don't supply the `:each` argument to hooks since it's the default. +- On `before` and `after` hooks, prefer it scoped to `:context` over `:all` + +[four-phase-test]: https://robots.thoughtbot.com/four-phase-test + +### System / Feature tests + +NOTE: **Note:** Before writing a new system test, [please consider **not** +writing one](testing_levels.md#consider-not-writing-a-system-test)! + +- Feature specs should be named `ROLE_ACTION_spec.rb`, such as + `user_changes_password_spec.rb`. +- Use scenario titles that describe the success and failure paths. +- Avoid scenario titles that add no information, such as "successfully". +- Avoid scenario titles that repeat the feature title. +- Create only the necessary records in the database +- Test a happy path and a less happy path but that's it +- Every other possible path should be tested with Unit or Integration tests +- Test what's displayed on the page, not the internals of ActiveRecord models. + For instance, if you want to verify that a record was created, add + expectations that its attributes are displayed on the page, not that + `Model.count` increased by one. +- It's ok to look for DOM elements but don't abuse it since it makes the tests + more brittle + +### `let` variables + +GitLab's RSpec suite has made extensive use of `let` variables to reduce +duplication. However, this sometimes [comes at the cost of clarity][lets-not], +so we need to set some guidelines for their use going forward: + +- `let` variables are preferable to instance variables. Local variables are + preferable to `let` variables. +- Use `let` to reduce duplication throughout an entire spec file. +- Don't use `let` to define variables used by a single test; define them as + local variables inside the test's `it` block. +- Don't define a `let` variable inside the top-level `describe` block that's + only used in a more deeply-nested `context` or `describe` block. Keep the + definition as close as possible to where it's used. +- Try to avoid overriding the definition of one `let` variable with another. +- Don't define a `let` variable that's only used by the definition of another. + Use a helper method instead. + +[lets-not]: https://robots.thoughtbot.com/lets-not + +### `set` variables + +In some cases there is no need to recreate the same object for tests again for +each example. For example, a project is needed to test issues on the same +project, one project will do for the entire file. This can be achieved by using +`set` in the same way you would use `let`. + +`rspec-set` only works on ActiveRecord objects, and before new examples it +reloads or recreates the model, _only_ if needed. That is, when you changed +properties or destroyed the object. + +There is one gotcha; you can't reference a model defined in a `let` block in a +`set` block. + +### Time-sensitive tests + +[Timecop](https://github.com/travisjeffery/timecop) is available in our +Ruby-based tests for verifying things that are time-sensitive. Any test that +exercises or verifies something time-sensitive should make use of Timecop to +prevent transient test failures. + +Example: + +```ruby +it 'is overdue' do + issue = build(:issue, due_date: Date.tomorrow) + + Timecop.freeze(3.days.from_now) do + expect(issue).to be_overdue + end +end +``` + +### Table-based / Parameterized tests + +This style of testing is used to exercise one piece of code with a comprehensive +range of inputs. By specifying the test case once, alongside a table of inputs +and the expected output for each, your tests can be made easier to read and more +compact. + +We use the [rspec-parameterized](https://github.com/tomykaira/rspec-parameterized) +gem. A short example, using the table syntax and checking Ruby equality for a +range of inputs, might look like this: + +```ruby +describe "#==" do + using RSpec::Parameterized::TableSyntax + + let(:project1) { create(:project) } + let(:project2) { create(:project) } + where(:a, :b, :result) do + 1 | 1 | true + 1 | 2 | false + true | true | true + true | false | false + project1 | project1 | true + project2 | project2 | true + project 1 | project2 | false + end + + with_them do + it { expect(a == b).to eq(result) } + + it 'is isomorphic' do + expect(b == a).to eq(result) + end + end +end +``` + +### Matchers + +Custom matchers should be created to clarify the intent and/or hide the +complexity of RSpec expectations.They should be placed under +`spec/support/matchers/`. Matchers can be placed in subfolder if they apply to +a certain type of specs only (e.g. features, requests etc.) but shouldn't be if +they apply to multiple type of specs. + +#### `have_gitlab_http_status` + +Prefer `have_gitlab_http_status` over `have_http_status` because the former +could also show the response body whenever the status mismatched. This would +be very useful whenever some tests start breaking and we would love to know +why without editing the source and rerun the tests. + +This is especially useful whenever it's showing 500 internal server error. + +### Shared contexts + +All shared contexts should be be placed under `spec/support/shared_contexts/`. +Shared contexts can be placed in subfolder if they apply to a certain type of +specs only (e.g. features, requests etc.) but shouldn't be if they apply to +multiple type of specs. + +Each file should include only one context and have a descriptive name, e.g. +`spec/support/shared_contexts/controllers/githubish_import_controller_shared_context.rb`. + +### Shared examples + +All shared examples should be be placed under `spec/support/shared_examples/`. +Shared examples can be placed in subfolder if they apply to a certain type of +specs only (e.g. features, requests etc.) but shouldn't be if they apply to +multiple type of specs. + +Each file should include only one context and have a descriptive name, e.g. +`spec/support/shared_examples/controllers/githubish_import_controller_shared_example.rb`. + +### Helpers + +Helpers are usually modules that provide some methods to hide the complexity of +specific RSpec examples. You can define helpers in RSpec files if they're not +intended to be shared with other specs. Otherwise, they should be be placed +under `spec/support/helpers/`. Helpers can be placed in subfolder if they apply +to a certain type of specs only (e.g. features, requests etc.) but shouldn't be +if they apply to multiple type of specs. + +Helpers should follow the Rails naming / namespacing convention. For instance +`spec/support/helpers/cycle_analytics_helpers.rb` should define: + +```ruby +module Spec + module Support + module Helpers + module CycleAnalyticsHelpers + def create_commit_referencing_issue(issue, branch_name: random_git_name) + project.repository.add_branch(user, branch_name, 'master') + create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name) + end + end + end + end +end +``` + +Helpers should not change the RSpec config. For instance, the helpers module +described above should not include: + +```ruby +RSpec.configure do |config| + config.include Spec::Support::Helpers::CycleAnalyticsHelpers +end +``` + +### Factories + +GitLab uses [factory_girl] as a test fixture replacement. + +- Factory definitions live in `spec/factories/`, named using the pluralization + of their corresponding model (`User` factories are defined in `users.rb`). +- There should be only one top-level factory definition per file. +- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and + should) call `create(...)` instead of `FactoryGirl.create(...)`. +- Make use of [traits] to clean up definitions and usages. +- When defining a factory, don't define attributes that are not required for the + resulting record to pass validation. +- When instantiating from a factory, don't supply attributes that aren't + required by the test. +- Factories don't have to be limited to `ActiveRecord` objects. + [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d). + +[factory_girl]: https://github.com/thoughtbot/factory_girl +[traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits + +### Fixtures + +All fixtures should be be placed under `spec/fixtures/`. + +### Config + +RSpec config files are files that change the RSpec config (i.e. +`RSpec.configure do |config|` blocks). They should be placed under +`spec/support/config/`. + +Each file should be related to a specific domain, e.g. +`spec/support/config/capybara.rb`, `spec/support/config/carrierwave.rb`, etc. + +Helpers can be included in the `spec/support/config/rspec.rb` file. If a +helpers module applies only to a certain kind of specs, it should add modifiers +to the `config.include` call. For instance if +`spec/support/helpers/cycle_analytics_helpers.rb` applies to `:lib` and +`type: :model` specs only, you would write the following: + +```ruby +RSpec.configure do |config| + config.include Spec::Support::Helpers::CycleAnalyticsHelpers, :lib + config.include Spec::Support::Helpers::CycleAnalyticsHelpers, type: :model +end +``` + +--- + +[Return to Testing documentation](index.md) diff --git a/doc/development/testing_guide/ci.md b/doc/development/testing_guide/ci.md new file mode 100644 index 00000000000..e90de55068d --- /dev/null +++ b/doc/development/testing_guide/ci.md @@ -0,0 +1,52 @@ +# GitLab tests in the Continuous Integration (CI) context + +### Test suite parallelization on the CI + +Our current CI parallelization setup is as follows: + +1. The `knapsack` job in the prepare stage that is supposed to ensure we have a + `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file: + - The `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file is fetched + from S3, if it's not here we initialize the file with `{}`. +1. Each `rspec x y` job are run with `knapsack rspec` and should have an evenly + distributed share of tests: + - It works because the jobs have access to the + `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` since the "artifacts + from all previous stages are passed by default". [^1] + - the jobs set their own report path to + `KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json`. + - if knapsack is doing its job, test files that are run should be listed under + `Report specs`, not under `Leftover specs`. +1. The `update-knapsack` job takes all the + `knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json` + files from the `rspec x y` jobs and merge them all together into a single + `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file that is then + uploaded to S3. + +After that, the next pipeline will use the up-to-date +`knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file. The same strategy +is used for Spinach tests as well. + +### Monitoring + +The GitLab test suite is [monitored] for the `master` branch, and any branch +that includes `rspec-profile` in their name. + +A [public dashboard] is available for everyone to see. Feel free to look at the +slowest test files and try to improve them. + +[monitored]: ../performance.md#rspec-profiling +[public dashboard]: https://redash.gitlab.com/public/dashboards/l1WhHXaxrCWM5Ai9D7YDqHKehq6OU3bx5gssaiWe?org_slug=default + +## CI setup + +- On CE and EE, the test suite runs both PostgreSQL and MySQL. +- Rails logging to `log/test.log` is disabled by default in CI [for + performance reasons][logging]. To override this setting, provide the + `RAILS_ENABLE_TEST_LOG` environment variable. + +[logging]: https://jtway.co/speed-up-your-rails-test-suite-by-6-in-1-line-13fedb869ec4 + +--- + +[Return to Testing documentation](index.md) diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md new file mode 100644 index 00000000000..bbb2313ea7b --- /dev/null +++ b/doc/development/testing_guide/flaky_tests.md @@ -0,0 +1,74 @@ +# Flaky tests + +## What's a flaky test? + +It's a test that sometimes fails, but if you retry it enough times, it passes, +eventually. + +## Automatic retries and flaky tests detection + +On our CI, we use [rspec-retry] to automatically retry a failing example a few +times (see [`spec/spec_helper.rb`] for the precise retries count). + +We also use a home-made `RspecFlaky::Listener` listener which records flaky +examples in a JSON report file on `master` (`retrieve-tests-metadata` and `update-tests-metadata` jobs), and warns when a new flaky example +is detected in any other branch (`flaky-examples-check` job). In the future, the +`flaky-examples-check` job will not be allowed to fail. + +This was originally implemented in: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13021. + +[rspec-retry]: https://github.com/NoRedInk/rspec-retry +[`spec/spec_helper.rb`]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/spec_helper.rb + +## Problems we had in the past at GitLab + +- [`rspec-retry` is bitting us when some API specs fail](https://gitlab.com/gitlab-org/gitlab-ce/issues/29242): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9825 +- [Sporadic RSpec failures due to `PG::UniqueViolation`](https://gitlab.com/gitlab-org/gitlab-ce/issues/28307#note_24958837): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9846 + - Follow-up: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10688 + - [Capybara.reset_session! should be called before requests are blocked](https://gitlab.com/gitlab-org/gitlab-ce/issues/33779): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12224 +- FFaker generates funky data that tests are not ready to handle (and tests should be predictable so that's bad!): + - [Make `spec/mailers/notify_spec.rb` more robust](https://gitlab.com/gitlab-org/gitlab-ce/issues/20121): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10015 + - [Transient failure in spec/requests/api/commits_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/27988#note_25342521): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9944 + - [Replace FFaker factory data with sequences](https://gitlab.com/gitlab-org/gitlab-ce/issues/29643): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10184 + - [Transient failure in spec/finders/issues_finder_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/30211#note_26707685): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10404 + +### Time-sensitive flaky tests + +- https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10046 +- https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10306 + +### Array order expectation + +- https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10148 + +### Feature tests + +- [Be sure to create all the data the test need before starting exercize](https://gitlab.com/gitlab-org/gitlab-ce/issues/32622#note_31128195): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12059 +- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34609#note_34048715): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12604 +- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34698#note_34276286): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12664 +- [Assert against the underlying database state instead of against a page's content](https://gitlab.com/gitlab-org/gitlab-ce/issues/31437): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10934 + +#### Capybara viewport size related issues + +- [Transient failure of spec/features/issues/filtered_search/filter_issues_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/29241#note_26743936): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10411 + +#### Capybara JS driver related issues + +- [Don't wait for AJAX when no AJAX request is fired](https://gitlab.com/gitlab-org/gitlab-ce/issues/30461): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10454 +- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34647): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12626 + +#### PhantomJS / WebKit related issues + +- Memory is through the roof! (TL;DR: Load images but block images requests!): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12003 + +## Resources + +- [Flaky Tests: Are You Sure You Want to Rerun Them?](http://semaphoreci.com/blog/2017/04/20/flaky-tests.html) +- [How to Deal With and Eliminate Flaky Tests](https://semaphoreci.com/community/tutorials/how-to-deal-with-and-eliminate-flaky-tests) +- [Tips on Treating Flakiness in your Rails Test Suite](http://semaphoreci.com/blog/2017/08/03/tips-on-treating-flakiness-in-your-test-suite.html) +- ['Flaky' tests: a short story](https://www.ombulabs.com/blog/rspec/continuous-integration/how-to-track-down-a-flaky-test.html) +- [Using Insights to Discover Flaky, Slow, and Failed Tests](https://circleci.com/blog/using-insights-to-discover-flaky-slow-and-failed-tests/) + +--- + +[Return to Testing documentation](index.md) diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md new file mode 100644 index 00000000000..0c63f51cb45 --- /dev/null +++ b/doc/development/testing_guide/frontend_testing.md @@ -0,0 +1,254 @@ +# Frontend testing standards and style guidelines + +There are two types of test suites you'll encounter while developing frontend code +at GitLab. We use Karma and Jasmine for JavaScript unit and integration testing, +and RSpec feature tests with Capybara for e2e (end-to-end) integration testing. + +Unit and feature tests need to be written for all new features. +Most of the time, you should use [RSpec] for your feature tests. + +Regression tests should be written for bug fixes to prevent them from recurring +in the future. + +See the [Testing Standards and Style Guidelines](index.md) page for more +information on general testing practices at GitLab. + +## Karma test suite + +GitLab uses the [Karma][karma] test runner with [Jasmine] as its test +framework for our JavaScript unit and integration tests. For integration tests, +we generate HTML files using RSpec (see `spec/javascripts/fixtures/*.rb` for examples). +Some fixtures are still HAML templates that are translated to HTML files using the same mechanism (see `static_fixtures.rb`). +Adding these static fixtures should be avoided as they are harder to keep up to date with real views. +The existing static fixtures will be migrated over time. +Please see [gitlab-org/gitlab-ce#24753](https://gitlab.com/gitlab-org/gitlab-ce/issues/24753) to track our progress. +Fixtures are served during testing by the [jasmine-jquery][jasmine-jquery] plugin. + +JavaScript tests live in `spec/javascripts/`, matching the folder structure +of `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js` +has a corresponding `spec/javascripts/behaviors/autosize_spec.js` file. + +Keep in mind that in a CI environment, these tests are run in a headless +browser and you will not have access to certain APIs, such as +[`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification), +which will have to be stubbed. + +### Best practices + +#### Naming unit tests + +When writing describe test blocks to test specific functions/methods, +please use the method name as the describe block name. + +```javascript +// Good +describe('methodName', () => { + it('passes', () => { + expect(true).toEqual(true); + }); +}); + +// Bad +describe('#methodName', () => { + it('passes', () => { + expect(true).toEqual(true); + }); +}); + +// Bad +describe('.methodName', () => { + it('passes', () => { + expect(true).toEqual(true); + }); +}); +``` +#### Testing promises + +When testing Promises you should always make sure that the test is asynchronous and rejections are handled. +Your Promise chain should therefore end with a call of the `done` callback and `done.fail` in case an error occurred. + +```javascript +// Good +it('tests a promise', (done) => { + promise + .then((data) => { + expect(data).toBe(asExpected); + }) + .then(done) + .catch(done.fail); +}); + +// Good +it('tests a promise rejection', (done) => { + promise + .then(done.fail) + .catch((error) => { + expect(error).toBe(expectedError); + }) + .then(done) + .catch(done.fail); +}); + +// Bad (missing done callback) +it('tests a promise', () => { + promise + .then((data) => { + expect(data).toBe(asExpected); + }) +}); + +// Bad (missing catch) +it('tests a promise', (done) => { + promise + .then((data) => { + expect(data).toBe(asExpected); + }) + .then(done) +}); + +// Bad (use done.fail in asynchronous tests) +it('tests a promise', (done) => { + promise + .then((data) => { + expect(data).toBe(asExpected); + }) + .then(done) + .catch(fail) +}); + +// Bad (missing catch) +it('tests a promise rejection', (done) => { + promise + .catch((error) => { + expect(error).toBe(expectedError); + }) + .then(done) +}); +``` + +#### Stubbing + +For unit tests, you should stub methods that are unrelated to the current unit you are testing. +If you need to use a prototype method, instantiate an instance of the class and call it there instead of mocking the instance completely. + +For integration tests, you should stub methods that will effect the stability of the test if they +execute their original behaviour. i.e. Network requests. + +### Vue.js unit tests + +See this [section][vue-test]. + +### Running frontend tests + +`rake karma` runs the frontend-only (JavaScript) tests. +It consists of two subtasks: + +- `rake karma:fixtures` (re-)generates fixtures +- `rake karma:tests` actually executes the tests + +As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`) +is sufficient (and saves you some time). + +### Live testing and focused testing + +While developing locally, it may be helpful to keep karma running so that you +can get instant feedback on as you write tests and modify code. To do this +you can start karma with `npm run karma-start`. It will compile the javascript +assets and run a server at `http://localhost:9876/` where it will automatically +run the tests on any browser which connects to it. You can enter that url on +multiple browsers at once to have it run the tests on each in parallel. + +While karma is running, any changes you make will instantly trigger a recompile +and retest of the entire test suite, so you can see instantly if you've broken +a test with your changes. You can use [jasmine focused][jasmine-focus] or +excluded tests (with `fdescribe` or `xdescribe`) to get karma to run only the +tests you want while you're working on a specific feature, but make sure to +remove these directives when you commit your code. + +## RSpec feature integration tests + +Information on setting up and running RSpec integration tests with +[Capybara] can be found in the [Testing Best Practices](best_practices.md). + +## Gotchas + +### Errors due to use of unsupported JavaScript features + +Similar errors will be thrown if you're using JavaScript features not yet +supported by the PhantomJS test runner which is used for both Karma and RSpec +tests. We polyfill some JavaScript objects for older browsers, but some +features are still unavailable: + +- Array.from +- Array.first +- Async functions +- Generators +- Array destructuring +- For..Of +- Symbol/Symbol.iterator +- Spread + +Until these are polyfilled appropriately, they should not be used. Please +update this list with additional unsupported features. + +### RSpec errors due to JavaScript + +By default RSpec unit tests will not run JavaScript in the headless browser +and will simply rely on inspecting the HTML generated by rails. + +If an integration test depends on JavaScript to run correctly, you need to make +sure the spec is configured to enable JavaScript when the tests are run. If you +don't do this you'll see vague error messages from the spec runner. + +To enable a JavaScript driver in an `rspec` test, add `:js` to the +individual spec or the context block containing multiple specs that need +JavaScript enabled: + +```ruby +# For one spec +it 'presents information about abuse report', :js do + # assertions... +end + +describe "Admin::AbuseReports", :js do + it 'presents information about abuse report' do + # assertions... + end + it 'shows buttons for adding to abuse report' do + # assertions... + end +end +``` + +### Spinach errors due to missing JavaScript + +NOTE: **Note:** Since we are discouraging the use of Spinach when writing new +feature tests, you shouldn't ever need to use this. This information is kept +available for legacy purposes only. + +In Spinach, the JavaScript driver is enabled differently. In the `*.feature` +file for the failing spec, add the `@javascript` flag above the Scenario: + +``` +@javascript +Scenario: Developer can approve merge request + Given I am a "Shop" developer + And I visit project "Shop" merge requests page + And merge request 'Bug NS-04' must be approved + And I click link "Bug NS-04" + When I click link "Approve" + Then I should see approved merge request "Bug NS-04" +``` + +[jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html +[jasmine-jquery]: https://github.com/velesin/jasmine-jquery +[karma]: http://karma-runner.github.io/ +[vue-test]:https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components +[RSpec]: https://github.com/rspec/rspec-rails#feature-specs +[Capybara]: https://github.com/teamcapybara/capybara +[Karma]: http://karma-runner.github.io/ +[Jasmine]: https://jasmine.github.io/ + +--- + +[Return to Testing documentation](index.md) diff --git a/doc/development/fe_guide/img/testing_triangle.png b/doc/development/testing_guide/img/testing_triangle.png similarity index 100% rename from doc/development/fe_guide/img/testing_triangle.png rename to doc/development/testing_guide/img/testing_triangle.png diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md new file mode 100644 index 00000000000..38b1fe1a193 --- /dev/null +++ b/doc/development/testing_guide/index.md @@ -0,0 +1,90 @@ +# Testing standards and style guidelines + +This document describes various guidelines and best practices for automated +testing of the GitLab project. + +It is meant to be an _extension_ of the [thoughtbot testing +styleguide](https://github.com/thoughtbot/guides/tree/master/style/testing). If +this guide defines a rule that contradicts the thoughtbot guide, this guide +takes precedence. Some guidelines may be repeated verbatim to stress their +importance. + +## Overview + +GitLab is built on top of [Ruby on Rails][rails], and we're using [RSpec] for all +the backend tests, with [Capybara] for end-to-end integration testing. +On the frontend side, we're using [Karma] and [Jasmine] for JavaScript unit and +integration testing. + +Following are two great articles that everyone should read to understand what +automated testing means, and what are its principles: + +- [Five Factor Testing](https://www.devmynd.com/blog/five-factor-testing): Why do we need tests? +- [Principles of Automated Testing](http://www.lihaoyi.com/post/PrinciplesofAutomatedTesting.html): Levels of testing. Prioritize tests. Cost of tests. + +--- + +## [Testing levels](testing_levels.md) + +Learn about the different testing levels, and how to decide at what level your +changes should be tested. + +--- + +## [Testing best practices](best_practices.md) + +Everything you should know about how to write good tests: RSpec, FactoryGirl, +system tests, parameterized tests etc. + +--- + +## [Frontend testing standards and style guidelines](frontend_testing.md) + +Everything you should know about how to write good Frontend tests: Karma, +testing promises, stubbing etc. + +--- + +## [Flaky tests](flaky_tests.md) + +What are flaky tests, the different kind of flaky tests we encountered, and what +we do about them. + +--- + +## [GitLab tests in the Continuous Integration (CI) context](ci.md) + +How GitLab test suite is run in the CI context: setup, caches, artifacts, +parallelization, monitoring. + +--- + +## [Testing Rake tasks](testing_rake_tasks.md) + +Everything you should know about how to test Rake tasks. + +--- + +## Spinach (feature) tests + +GitLab [moved from Cucumber to Spinach](https://github.com/gitlabhq/gitlabhq/pull/1426) +for its feature/integration tests in September 2012. + +As of March 2016, we are [trying to avoid adding new Spinach +tests](https://gitlab.com/gitlab-org/gitlab-ce/issues/14121) going forward, +opting for [RSpec feature](#features-integration) specs. + +Adding new Spinach scenarios is acceptable _only if_ the new scenario requires +no more than one new `step` definition. If more than that is required, the +test should be re-implemented using RSpec instead. + +--- + +[Return to Development documentation](../README.md) + +[^1]: /ci/yaml/README.html#dependencies + +[RSpec]: https://github.com/rspec/rspec-rails#feature-specs +[Capybara]: https://github.com/teamcapybara/capybara +[Karma]: http://karma-runner.github.io/ +[Jasmine]: https://jasmine.github.io/ diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md new file mode 100644 index 00000000000..9b9ba0baa71 --- /dev/null +++ b/doc/development/testing_guide/testing_levels.md @@ -0,0 +1,173 @@ +# Testing levels + +![Testing priority triangle](img/testing_triangle.png) + +_This diagram demonstrates the relative priority of each test type we use. `e2e` stands for end-to-end._ + +## Unit tests + +Formal definition: https://en.wikipedia.org/wiki/Unit_testing + +These kind of tests ensure that a single unit of code (a method) works as +expected (given an input, it has a predictable output). These tests should be +isolated as much as possible. For example, model methods that don't do anything +with the database shouldn't need a DB record. Classes that don't need database +records should use stubs/doubles as much as possible. + +| Code path | Tests path | Testing engine | Notes | +| --------- | ---------- | -------------- | ----- | +| `app/finders/` | `spec/finders/` | RSpec | | +| `app/helpers/` | `spec/helpers/` | RSpec | | +| `app/db/{post_,}migrate/` | `spec/migrations/` | RSpec | More details at [`spec/migrations/README.md`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md). | +| `app/policies/` | `spec/policies/` | RSpec | | +| `app/presenters/` | `spec/presenters/` | RSpec | | +| `app/routing/` | `spec/routing/` | RSpec | | +| `app/serializers/` | `spec/serializers/` | RSpec | | +| `app/services/` | `spec/services/` | RSpec | | +| `app/tasks/` | `spec/tasks/` | RSpec | | +| `app/uploaders/` | `spec/uploaders/` | RSpec | | +| `app/views/` | `spec/views/` | RSpec | | +| `app/workers/` | `spec/workers/` | RSpec | | +| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [Frontent Testing guide](frontend_testing.md) section. | + +## Integration tests + +Formal definition: https://en.wikipedia.org/wiki/Integration_testing + +These kind of tests ensure that individual parts of the application work well together, without the overhead of the actual app environment (i.e. the browser). These tests should assert at the request/response level: status code, headers, body. They're useful to test permissions, redirections, what view is rendered etc. + +| Code path | Tests path | Testing engine | Notes | +| --------- | ---------- | -------------- | ----- | +| `app/controllers/` | `spec/controllers/` | RSpec | | +| `app/mailers/` | `spec/mailers/` | RSpec | | +| `lib/api/` | `spec/requests/api/` | RSpec | | +| `lib/ci/api/` | `spec/requests/ci/api/` | RSpec | | +| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [JavaScript](#javascript) section. | + +### About controller tests + +In an ideal world, controllers should be thin. However, when this is not the +case, it's acceptable to write a system/feature test without JavaScript instead +of a controller test. The reason is that testing a fat controller usually +involves a lot of stubbing, things like: + +```ruby +controller.instance_variable_set(:@user, user) +``` + +and use methods which are deprecated in Rails 5 ([#23768]). + +[#23768]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23768 + +### About Karma + +As you may have noticed, Karma is both in the Unit tests and the Integration +tests category. That's because Karma is a tool that provides an environment to +run JavaScript tests, so you can either run unit tests (e.g. test a single +JavaScript method), or integration tests (e.g. test a component that is composed +of multiple components). + +## System tests or feature tests + +Formal definition: https://en.wikipedia.org/wiki/System_testing. + +These kind of tests ensure the application works as expected from a user point +of view (aka black-box testing). These tests should test a happy path for a +given page or set of pages, and a test case should be added for any regression +that couldn't have been caught at lower levels with better tests (i.e. if a +regression is found, regression tests should be added at the lowest-level +possible). + +| Tests path | Testing engine | Notes | +| ---------- | -------------- | ----- | +| `spec/features/` | [Capybara] + [RSpec] | If your spec has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. | +| `features/` | Spinach | Spinach tests are deprecated, [you shouldn't add new Spinach tests](#spinach-feature-tests). | + +### Consider **not** writing a system test! + +If we're confident that the low-level components work well (and we should be if +we have enough Unit & Integration tests), we shouldn't need to duplicate their +thorough testing at the System test level. + +It's very easy to add tests, but a lot harder to remove or improve tests, so one +should take care of not introducing too many (slow and duplicated) specs. + +The reasons why we should follow these best practices are as follows: + +- System tests are slow to run since they spin up the entire application stack + in a headless browser, and even slower when they integrate a JS driver +- When system tests run with a JavaScript driver, the tests are run in a + different thread than the application. This means it does not share a + database connection and your test will have to commit the transactions in + order for the running application to see the data (and vice-versa). In that + case we need to truncate the database after each spec instead of simply + rolling back a transaction (the faster strategy that's in use for other kind + of tests). This is slower than transactions, however, so we want to use + truncation only when necessary. + +[Poltergeist]: https://github.com/teamcapybara/capybara#poltergeist +[RackTest]: https://github.com/teamcapybara/capybara#racktest + +## Black-box tests or end-to-end tests + +GitLab consists of [multiple pieces] such as [GitLab Shell], [GitLab Workhorse], +[Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces +are configured and packaged by [GitLab Omnibus]. + +[GitLab QA] is a tool that allows to test that all these pieces integrate well +together by building a Docker image for a given version of GitLab Rails and +running feature tests (i.e. using Capybara) against it. + +The actual test scenarios and steps are [part of GitLab Rails] so that they're +always in-sync with the codebase. + +[multiple pieces]: ../architecture.md#components +[GitLab Shell]: https://gitlab.com/gitlab-org/gitlab-shell +[GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse +[Gitaly]: https://gitlab.com/gitlab-org/gitaly +[GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages +[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner +[GitLab Omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab +[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa +[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa + +## How to test at the correct level? + +As many things in life, deciding what to test at each level of testing is a +trade-off: + +- Unit tests are usually cheap, and you should consider them like the basement + of your house: you need them to be confident that your code is behaving + correctly. However if you run only unit tests without integration / system + tests, you might [miss] the [big] [picture]! +- Integration tests are a bit more expensive, but don't abuse them. A system test + is often better than an integration test that is stubbing a lot of internals. +- System tests are expensive (compared to unit tests), even more if they require + a JavaScript driver. Make sure to follow the guidelines in the [Speed](#test-speed) + section. + +Another way to see it is to think about the "cost of tests", this is well +explained [in this article][tests-cost] and the basic idea is that the cost of a +test includes: + +- The time it takes to write the test +- The time it takes to run the test every time the suite runs +- The time it takes to understand the test +- The time it takes to fix the test if it breaks and the underlying code is OK +- Maybe, the time it takes to change the code to make the code testable. + +### Frontend-related tests + +There are cases where the behaviour you are testing is not worth the time spent +running the full application, for example, if you are testing styling, animation, +edge cases or small actions that don't involve the backend, +you should write an integration test using Jasmine. + +[miss]: https://twitter.com/ThePracticalDev/status/850748070698651649 +[big]: https://twitter.com/timbray/status/822470746773409794 +[picture]: https://twitter.com/withzombies/status/829716565834752000 +[tests-cost]: https://medium.com/table-xi/high-cost-tests-and-high-value-tests-a86e27a54df#.2ulyh3a4e + +--- + +[Return to Testing documentation](index.md) diff --git a/doc/development/testing_guide/testing_rake_tasks.md b/doc/development/testing_guide/testing_rake_tasks.md new file mode 100644 index 00000000000..5bf185dd7b5 --- /dev/null +++ b/doc/development/testing_guide/testing_rake_tasks.md @@ -0,0 +1,39 @@ +## Testing Rake tasks + +To make testing Rake tasks a little easier, there is a helper that can be included +in lieu of the standard Spec helper. Instead of `require 'spec_helper'`, use +`require 'rake_helper'`. The helper includes `spec_helper` for you, and configures +a few other things to make testing Rake tasks easier. + +At a minimum, requiring the Rake helper will redirect `stdout`, include the +runtime task helpers, and include the `RakeHelpers` Spec support module. + +The `RakeHelpers` module exposes a `run_rake_task()` method to make +executing tasks simple. See `spec/support/rake_helpers.rb` for all available +methods. + +Example: + +```ruby +require 'rake_helper' + +describe 'gitlab:shell rake tasks' do + before do + Rake.application.rake_require 'tasks/gitlab/shell' + + stub_warn_user_is_not_gitlab + end + + describe 'install task' do + it 'invokes create_hooks task' do + expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke) + + run_rake_task('gitlab:shell:install') + end + end +end +``` + +--- + +[Return to Testing documentation](index.md) From 694fec5ccf78b7b8318e0ab96da27f5a6ca5930e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 11 Oct 2017 06:07:55 +0000 Subject: [PATCH 069/104] Add image discussion docs --- .../img/image_resolved_discussion.png | Bin 0 -> 48234 bytes doc/user/discussions/img/onion_skin_view.png | Bin 0 -> 45053 bytes .../img/start_image_discussion.gif | Bin 0 -> 146627 bytes doc/user/discussions/img/swipe_view.png | Bin 0 -> 16483 bytes doc/user/discussions/img/two_up_view.png | Bin 0 -> 61759 bytes doc/user/discussions/index.md | 43 ++++++++++++++++-- 6 files changed, 40 insertions(+), 3 deletions(-) create mode 100755 doc/user/discussions/img/image_resolved_discussion.png create mode 100755 doc/user/discussions/img/onion_skin_view.png create mode 100644 doc/user/discussions/img/start_image_discussion.gif create mode 100755 doc/user/discussions/img/swipe_view.png create mode 100755 doc/user/discussions/img/two_up_view.png diff --git a/doc/user/discussions/img/image_resolved_discussion.png b/doc/user/discussions/img/image_resolved_discussion.png new file mode 100755 index 0000000000000000000000000000000000000000..ed00b5c77fe184ec4a39109dffa020ee87ed702a GIT binary patch literal 48234 zcmZs>cQl;s7d9%9h-g8y5Ya*mL6qpdcQI;oqehM1C8CcKozbF>P7tFLVMOml7o&Hg zMm^7XfA9N!XPxthm9_Hhd+%#s``Y`y?`NLyH>z^@IFvXT7#R2p^3q@oj0aE*jC;D+ zn85#hpD9OSVB9@@qpT^jySsaHb8~)vzOu3+Rv*MAZ^RM$dw+kQot=GWXJ>0`>-X>9 z3kwS!9UbOB%&xAl-@kvqv9SSz!NePaKYsjpeRH$4yv(g&e0+R7J3GrTle)aJe0qA? z-`{_BcJ})9>(0*3si`R!7nk1N-inHfqoX5TU0sQ$(96rq(b3VWswzoINwKDowY9au z!NIn+w!_0iI2=AXIoaLaT~bnV@aNC@^%V+*a&vQ&Yz`A`2bx%%Cj&UaK^y1pKXd^8xt*57lQ`DfLp~0yyPw8izl-~QS ztSs5@5zWoboC5mYbK_FTC_6hlQA8j=KR*NlIlVfUYmX``D~pehFI()%7;O|O{hLSy+K-pI`iF!vylTgfe0_aqw=N1Q>vh@_yhcj|6WoQOP+Y1et*xzA zKQp+s&GfR?=9Yd>9;}To&J6yZj2!!xl#~=38~bjx-l03o&Oh4S-F^LZpOZ`P>z|qK ztvU7HBx`GH9%G9s)I6`lyX2gbgdv1Lq-$t@x%xngQpaarce{kC1|?IM;oZgB+S%y ze#{OvP4_>aN!8caFX}rSKHY4bJWiWw{n%e@Vq&tqce#CZ=@SVXTs~_$UL2j8jZQDH z=>NjQ!}E2iuXpiOCVtt_&~R++Z~y7~&xO;3-Tv(P_6+2KU=+$JG_IU39 zJD=(?FzVSAq$M;xr?GE_r=A8FnATm#3P81?wX@DW*93 zPZEhSB!k3=dBcg}s?Le=ToVt&5cqre|Nr=@`>w8*DFx2=_u6a(nb|{S_sKC?Dx-tx zC*KOXLS3;3=0=(y4O<-?J5(4v#gv5i-l%v5ev#r|qspVe_;?dcf{C$@55|+ORoQH! zx#U039^5i7!JrKz!Bj7scxUL|@K?ntx3Nm~7wdhDJ9!jgHlPCqD!ystdZZ{Dn>Gfa zI5eP;K&z-ZQEXzXN{|@?n-(dqxlzfjEn2z}U3B*&2H-o<&Rw;`+ohhuQD)X5{(m-a zt(b@^*0$UxjLf+V;TW_ZfrwgsRx*9FWHLfjL|YDt|18#8ABHw3$Rv@jSa?M@yt$3o zrEwsH{9DgF@K*?_jKugM&q=6t@<+SF_4C#B@Drjf<4aOZYXtKZU{ zmMyIRpi?N%2y>gK*eeYi73fvQ_=H|PEt;kv3SXUaMq!F=&sEyYh(G=5L=zGS?~o1f z<}t!)8)|cCBrR?8yMzA;`?DV&2Pc30a2(PxuC_~aJ&Al{1m~>`&O}CP*gD7c!XWXP zO7cc;fWRHqJPPaa0>`vt)QNF8`4;{o`!y8K_fyNy%`RI0+b! zTAouJilj#l1(0CA2DMO-O*Tx)bAAIVrveRa+Yj2=;REBT$aB6)MwvPpn{AmeAm0R$ zVAg=xDcdHHve4`YAS@dV1D+hQnV%cS{1-wkyVox(=H)rZXptWokX{Vu)uiXwAC{>= zA!KN{twd6T}D?6R&&->dA^L`^;t`*gRq{02KH?ICJE$xIuA_xcw*kSU;+O00Dt-XkFgh8DC3zBvT>B$F&?Yczys|( z3g!ac-)S1x{o9iItg~+ZE#!8>z7K&2Z&WM4^A%j`+hsr)_WRa=+%o?C&!(Aq@9mY0 zdIuoGa1zWF5?o)Go?7GtYm)owc@&8h0~jwsCTSf_RnKoEeC-#2tZgpAhNU71Q(&n5CZ3c?Kyzv z{|p`E^ebXvZ4ijf z?v#TE@d`WK`rL`{S3{^C8B3CaBLU%7^lF$=bRHNgJG60y;N5FuqU2$j956QFU;Hj0 z(`MooXc=nS@V1js8*c5BrXtNlMRJdmA1?<&m^KH0-0n5t?$5J#;460UV?X`qzE^$d zDbTv{gcoGz@XLaoq~(hvdns^aN(_Wnxw1V?-A>#ZlCW4y|(J zD{K83y#JRp@Mqrilxi{IUp3Xw03nI~SB2ttTM8PsoQd9<^RxCqVS!n4TiEeB!x%2x z1cd^>=39GUQ@X8Mu~#Fl3UNuIa$fHIKP&!~!g$qjo{Z^hXz-Yj*T@V|n*Un}4R(i# zJ6}_kUWB}$ZmRGD0s%ASpFs9XKf~|GIk7~|H+efxAusg!z>(I#1ilrt{fvBr4>L%u z8iG*ni(4t}8Zn^syLMeWn0at@t>EZ54@`g#=)X+8^Km`wISnoLJlkG@ZJm}kiC*69 zZM7WuAj1gJy$`6a*q#dSy#0Zr<3gkR#&yKt;G;*fBkRBS|3;CYets(8ygO3k!;wjY zmlsgL_X{Yc)4vL-vCADUy!k!cFQmpnEmqZjz^x-TSLgQiuZRD# zRnIH^q9;FI&CEWSBq_$9u`Bu!Y$S0va8&pAZ%LUL%7@zdBNWSt5#qGL64++ak&0%F z>MRAXL33+FkkHFM=?jL&gW$iaUh=o1IkM8Vw1^|DpD zDBTD5*Xx{o4zgcF(m!)7cb4beCjibWLP$Km0>unYS%X2Wkbzuqul{nuY9lh99IBPLnDq zsPvl@Fmga6C#{cpKJ1;j%NRSs$T*=+TyXdrl$&7UDVHe)4N#*+V$^{w>jboZ|IlMj zoBC7tc)W~w4N>>ZEjoEB`ifc0x&PyY=^vmU^ZGwsUDztLDS_Uwh|Ies}A+Eo= z;)*AHOM>y3U*Uo#M3K3DCmI3}VGI!Q9$g%kUr*=--VS2z=x${Zmo1(A-Zf`V;A-0K z@;IXz`_B3=s;cxfl-*zU9qBe+5J8UW^v(R^P6{KBOE;i8WmXWJu4z`4y)f#f)jNbNS~?h4LuQaQ+ou zeRP=gUhjswkdvZ8WxdSQ3_J9?SdyAnbnu2}Fl2AANo8O)AEYbb8@;>dg|4_gUx<5bgiKQ6X=M;d%Qd zI~-H@ti#k$LR|&#qp&Fi9jBO z=YxOaQL#Se2~BEO*?ZcZKOq~4N(vkkXZJWAyPJ=5#k zM=Sse$OO)Hx6L_r6D?_DS>m56ef)cPomt_cq+2OB!PIkx2B=9ZS^}XQfPe)Cn9V{W z(tbRG6^mInG{3!!k?z<-d?08~{yze6PUn@$+5_pg16o&`X- zTU93PBoJ+yI0-7!Qw!gxmg!zU+R9@JubW#1$n?AiB$tLBD+mqQr%eKOq*tB|lx6Ym zTgTUVvK>3Tzknv7`B!e|-SgBX2%>wYPpzg(W?w>5Y)|g62ivs2)`!=M6+0?}7hbCXtMfjP|^$%d)f zzN*Dt?vTHgZ~Q!ptQ1H9UjYucS#(9{fQo(NS&lBbFu)pGE3Pve8I9O?eI`qH{a~HdSV5bz1+$n=KsNDSII)XqygjbQeY5byOAUPf4_S{aLms*FZ7lJg@FyLLW+X7GdBC?-4z{z{KH&!(sr~EdD)ouBU37ue zT5;Pz@c@3sXM9#$#_baxz((m`8xqpqW zR5?A?MpaYaJOME!pA;Oj>$95FUgCkU9s!yCMe}m((pfroA_WhL=uvPQvyLpcRJ6TV z@L(N?f$Tp5*c~x85Qz*^=$Ang(kijIZK%j);poWuGlzl3p z`Ws?;#gW>rUNR~M`vuIWE%*K_cYZ?I`oUV7=?u~Rd^+cyMOXivl>D6%R(Z}dU=}gM zfl7cZ?H|pjyHP4AlO%+q@`hVeDB5zNvUWd z&^YfT+n%Y%MMtNyA!Q%dRR;f)p)b^8F1<>7%dy1!0^7AZG5~=o`Szw zFAFWwgc8Uy6V+#DC*yfSr(r?3oW!nMW^hD_Hn8;$Aqg}E>gWlr{w5$({Rp<1y*E|Q zSS=#9Et+Ai^1P*zG=mOl0$kc@1$<%QQDxZN8=EUKqZ|#rYL)s&X={)C5GgBs2)GqR z%e`EnOS(y{AZDyTN_*sTCnJ=5%^TZnPPbbuIC~xKWUS;lQvm(T!}%Xm=@VHRQvXV& zYRQm=?wFiQjH)b{CM`^+qN5Q4(O@V%0I^kcyTQDFX>V&39O$Gt5ZS^cR5eAXZft&=Kyq*(zvJXb)VX$RhjK!&(Dz4U zCctr%76v(pDqABt+*)c>9rf5Tu(6wQ%v`8ZECu0ud?X1J09Z7KBKZ)R8g?}k0}aCw z%Al&iw#BI{+X&|WERw-58wHSdPB(}P&}EF$c<6qE&kP4)>?Ji&DRkbM=qT%vJR9p$ zObe{5Bj)5Ht$$n^4NNR;75zPML0H@X$p<76j&@fnPG0=;Vo~l0utj6r#HG8B;; zIezcbGh~i3G(zUX-et?4ZuBJD4O+oTkSV_+`GE1Xor3Aj2KjI+HX|C>+6hIn>M`n4 zz=q;q8*FmTTn|#euIGsmp~=aTxtf=jMM7V305Jf!MEN{jcVj3VXQ#}&u1ko}$bO89 zR~}@8=aZXSYaMAQb}34k^iI*v2d_QZgaq&y3q%Et9Krog>+8P{XxmG&1TfXe=s)EV zijU{ZQ*iJBHnVu3AmJpvyGlgre;1oXu~*p+#$&;oGZh&N4;&Bw);?(U@b^o5_SBH= zQgRe&0SmiV`!fde3K;S+@|Ei?Bt#pV{if?ahxv8$8k@b1G=GC=_F9<7OE}^ngRTBz z$Wv(JEm{S+t%Ju>bBoTtFYE|lo?A#*q8ydmvF+pdmG2GT#LT-b=0hZEP~GtfbJ zx7fP@?3(lQ+vVkh$*5Q`&7(gYf@g(e*0CfNUv?cGlEdFRqZ81$O+X2qfSL30y8&`o zX2Nv;&1gATEuv}Y+i+7VPuN>)A3WedgYNKfl89X;bua$n&~hO)sGQ=?@{O5X(J(y+ z&;8@!uYI8TJK)REW+C@?40ucY1MSp&u3n&@hwOVagby0Y%B9xR7efdn8+EZ$km5>M zp%ZwS+0=@a*mumH-lGd7d>$sv1w|CmmN66LyD{6>j#Qe7Y&aj#*6aLXX=)u@9~12* zSKae4;Q$hRja)s=Pf@TtYOGL-cfhjkCN8M;R9PcB-g5ZBFh9%X4vdLX;B|H$eDbn| zgu8#&PEL)xxr>Bzyot`HBX)p1HD@Qo)1`M7KL+v@cqJ4gf6%|91R^c(vpsdyO|i z_B(yJ5K{*;pjU-}b51zP!dOd!rpFJMd$Y!$>byFM*N*pI+nB3W8gGs*w5HIKVeugV z0K|Y(<%9lhrH32*9|+(c5nf_a5hbpusO6wyn9}xIMB|=bwX|eReIuZDU~@B}MeDHe2-zYPKR0Ot3MXu@<0zfP63 z3?QmWUuF1U4lEE4=dy}15`5#WZJ;^Xt2T-Ekbt&{%5#1ZT*05)%g-UyMdPa*jxD z#?o>KdX6ioPGN6TnsC8*2w=+sgYDdn6%8hZCwq=O1t%zK3LfY;|KjiSu=QC)>%6)i^uuV25%i1djrA<~ zr+zdTfF2A`dot5$ejZrS;iM#)gRB1S%aN+&qt)=_Oq7M*m_ntP&aC0WOoHM=(<63Z z-zq^b0rA#zFj`#FkFF`2jtr(i;Gy%R8z6uA(nQpQ_~87LK4_d|N;8lF?>`wh8!IZv z@%ro;CHnsI;HLW{t5PYqY_o6JItJT)ozF12k4BGf@+3&$1z53rf4^=7Wbp8y40tiv z^I~eGCvNAQ7;~^@muH2d;+iUrCM1d3}7?eT}}^T{1A<1h7YeHSBDF zh;AOozxn-RH{9WqgNa5oG^J2!X#chY(DJ}-1n3C3du@XA4@)7IN7wK(?aQf&n+3Au z+>^e0oK#J?z}v7=;4KXhZfG|N9BnYA&#}6OC0TmPrJ;Od0e8JNXsK(dWe!8??tLN& zYz8Lt7)jdH9GIL}48a;~^0hH zANpRso9OOc-nSTu?c874?=Q2u_;mmCDPT8=gPy;WlS!J&oTGLn7c3T5N_#GZl?*$B z5_yMyV#dB&B6!qtMF$J_7g(`yZ(ApAIu5D;@^oK%+nk;@Chvq|2oK*JkpzAiIo213 zvC1`DuFHdd`k;9Pj)Tv^b`DA`s+{3}ucueYVXpU-*XHF1!ZCFgZpc#;7*VdurUXTQ(DJfFE8 z(g1Z}aIsInm2r>6fXA_na?RrB0s-fW-og7T>t5VV4EO)B?ggxGCL2HeV*0@!y(GLm zklh<7EKI)g7jt0Il1_kG^+izoFdK6a1r844331^=Br{gy7=_-sZI~_#3*uY8t|BOV z?3*)3mfr-fm`!8Oz|CXTa6w08nTU*w;WG4CWH$6p8D_Ub1G}4bpr17DXTv zh96t6ou2u-RF1m+*^>|sI&GjvVI;N-aj3Whef=_n9->a7W~=QJ3FCq(pTMB^;Q9&e z;1{ZTKflU_kVvHixdK3%85!#n5?AseuXdfxsXHC!#fUbpRXPP3b3en8?%8$U4AhHn z1Ay^30W{Kix~EAA3F(jgn_TOEV=t}48GZbO{(R}Ec=ClKc?9d>A_;q~C;>};v;{za z$QjVB^x)DDO3=e8oATP{_hce$t0S8m&K7IuHX;kvx{x&ydL5XM59^}%sF#^!omlbRqhOj|R{KW(2ICqM%fVit2( z2fv%%r7CbcMwj7Z7n97uf@7Ph^NFF?1~HH@^j=Jl6s*lt)l1JPyKLx^{-KZ@`sKXE zR`$K$&Bl9yogqVO%DI##!loMK7T{hb1zf(+b*N{|Z)nwl85aBS&aG`^*Hk_cdnYS@ z&TVEZ74)UB`TR2CMR*YSh)Q3cvjf!8S#53l~=gCvv!IAuoa@geRSaJl`FVNGTw%ICK~!e?XhTZmMx z+_>mQW{WbYB%r{Kj&3KB=5eF!y z)<1_ko#LsR3rP3k{c>*42nF`n=>WJ&VEzPwORe}=(osd%6V!ubSW)1GeF*oZ7_#-o z(TTfm$ox&sWXXch&3iE<Nwzh2>H(?Mt>`#jqt(cluw!&oa za9Ph2CN`y?Zvjn-0o(W&&;)`JDPWek^H*?j?Om=wq{$@kd zzN&AxNdS`uQ2Vc>gylGVc2aU2a}Vgw9|Iwn$0OCsG&RIp5AW2fwrMtkrzuap9ULBDe0 zLJA`CK9MY>PEsG(NgUJ1bG|zDWJ=?C6x%l-oSEwbq%z;N+fhb~^hR^#2@Fj7P>vcq zg(7K4Rcj6EAssdc*UDA9#`fAn4xWc}O^Mn!&&>>ey)``}h5++8Ot~_8jxF(xO+iCk z?<7{Z>-4&j`Jd|DMUZ>7|EZ+L{tImE_JL*XVxc%r(CZ9zD(F^+r}%p)y%h<^)hNk@ zC1m-=g0|9bItJs6tQ9R}b!d#{S~HN}?0}zT2^j!wnL{@`Y7_FknfOk2J)TgjX%s|AR<&2fQEs$xr8vtIj~U@+c^6_PbR{eicog>9RK#$ zh_!&AG;y zCVA>z`jzGx!}$-{3hlB_{6A5m>B7!Wale(WRW5F(R893t0+m|YO)_eARCX$Rde*tA zw`{;N29jDW38iZR0(6m#?m2+AEM6xoKBOyT=PKhfT)2GHGGp+fap<`;OX7_FYTP!u z*1$Uw+60J5qk6gvujsgO?(7r`-}K+ozs6O6-E)=e>$MtNo)}o8c&vc2U;vDTY?7_1 zC>$($R{g;UR}L-#zxFM44le2w$f<@$D5tz`?UO zYbl47-?}CnL#cSj77wIZ{@@cb_v+U_rZC4PV51!obmC zz>3=$zs|>pI^U=UD(}g<*=q>5BkDvITW494?D0%%7DN`R^H`mzEi^ef@rzRkRgh=% z%=rneaYp9F-@Or4v>HzZT!@e!0dTHTmy)VkF+->PgzvX8ux?{`0x?hr^MAjRdVi-K zbxP-ViSPKCR`OLmo*WVKMdNze?o9L{~(WAjrG9FUQRXNhfW?6s=v^l@>DLy=H+m)a+|6~u{ zIYBZ*fzQn(2$93E!>v8Foi!PeBs zCuIRlmG1*(6Z^g&tR(*8@QC?Ks6>8ZVOoAhY6j^2ywt^SpP0hjo0rtQht%2|3y;VC z>dgB&j$*$Upt2NPfDRFs&Lde+O;*e0ZiWck^s_Fnw=p1`^z?h`WY&^btc_>ij=Au8 z!tW=V`2)2lD^7{=(Zt(`0xOiR$PUdyh$IiGnINK+WJ7W5I5d8q_`Sw#=B%MPPWtRU5Asx|I$RY~@9d4!&|gU} z3)2sQSH=+9TNQ9p>%*Y=-{ytIw)sA$q578s5TJn?wr5Wr@75xt{CECRDHR(W=0d|^ zZ;(OOEV6EPZ^3#FE~i<>KMc)#J}Ij&i-+aB913a}(tN3P{-9Asr|{N1_SQVa)$So{ zWB$VBpAy6o&P27A(cHu*<&WZ{CpJ$e>W^)TPPUXZ&iv^HF9BK7-vXke)<-SC`Ej`t zKa#`P<-$+A1=mteRSJYAEV2T)*8x>3ti^yNu>r4#VZQ<7Vax7V}!^3 zo;zo;(ccb6YM-84tDm)eu|+Q1`hph74Bli~g5aY=VW z)C`%9Pc5I2WKlYrt9Q4_XErh@en`3xv$-PMryo5zpbf`-AW!%raLN96FkHL!1fqtW z=z$J^hc39*OB^rn+TBmeGz7d!tl9Y42CBUUa!yYL-sCVp%hwvS7;51!ed%~vH+2vU z7FYu`UJYn`STk}xEY3k z41zg%n!(M74y}4sBD#YUD}|z3UJr{}OFy%lX6R=c7CZP}PE_V4c=_}K3=`iLduU5BUU43 zU;zQwc3WT!E!#_+Pm=Wy$Q5I?r@OlPpKJVTmn5u1gcm)hr%y`E{6lN)x;;`PmhGh! z%7mD#JsgFeUR~a~zxSt&@8U|BBTc^%=_x+u@I(`j=PnBtZ64&4EXk6rx9Q%w4OkU= zGfp=;7zpq7h3>u$8<(TWH00usm}Luu28fCyF@iLoHWM)l>5pF}N_H}A+u3t~ToEK` z`bslF{cIeE?{@wc3I}z~aTQWnV`}1w%OmeaF%NjvYfnDSInU*{Slg0f+Ip#JH~v7y zOGpMd2(8TN4+u!q;;~>gV31-Gf;nXvpej$JN#m=kWnP<4!ww9$BHv%ZG7j!EtZG-C zd7FKb*-ly{9}{QIf?!CpL{EpI(y0OukLfh1R$>>Mg}PbDr^N&G$c|G{CMo1W>p9 zgSv)@QA_yz`n;L0fgFa#WL(tTm3J*UTGD-|p#rJn@_9K+0UL?7NBm=7Nra@Z(pmkb z_?E-ru^VDJrW=1w?eXKwCFddOvDqr%A0Dx;pf@67g%s;Jx0w!8sz(!AHZ*B##DKdzs6f;xHImIab?-e6n+w z^i4khWsNRDs@)IP&5<5|t$_y3!AC4u-v1cq$tGAo(lnsqVze*5*tk5}joj0>g6<7& ztFCF7e<1a6AhTIH|Djh?d7l)MEdY84yNDmmpvb?ov3adNoQq?hPwd>4H$Me-bm*3d z&&W}9@IvOct9L%paIlj=ip%}O=tvmxodp7$Zya3Bv-bSv^fWv>SbxNp^2!6ZnSi-VFI?)aQ$*POM#f?K+$=qLqKOQ34kC3K=4<0>WyN)HtSku zv<_9Cs23NqR`i6)?JW>crq>Rkb?5+8=rRYTO{JcWt3D^xW5}p%6uoz8+I95#Q7TRZ zjjWeAt`!a`X8%X%9qT;Eu`G$1L6~z{!cL?H0gU~u#f|;!`o4g*Vk~}YRWOI+lcmU- z_wYBIL%4+$AF&pdj%5>;_*WB$*D4|y9*&U1fc+@<^HDIoqib{WT2k+sGOiVLpK%BW zu$E=fc9a(Q@pSe~mxgQVW;6!GW9_Z*+PZ25S0#9QF&&hi)L;R4eu(yrw(X`HtsZQP zP&Pl_82i5bCvxx07pUI{OE2e-rS*&tRP=HGnCJ;=0L^m`kQ623!;lOOC9=p=y z6y>}&1s4&1Dk8L~6X{M5V=H^wCm^|>G(f_Fbq+MokT|j@ikYs%;AU;>NrSQ4i;#k3 z!{DuVajZ5TUV>j}?HsfmP6i*gc`BjFu}71W1(1{Qs>G+lMT)^*I&Zqu!}4nWP|K;J z^&7(j73oO4#ORY^`0u6~?qFyDJ3u=RxKfWn5qo$y!8W**`~|m@zjybsp;GYiQqcwQ zsVIuZS)^M=#25rC-~BvyhziOm zGwI5Mp!f=2baJGpx2U^0y|Sjab3iudJmqm3NaF|SSjWBXFdUJ>7-&goL~!dF{pdLf zcciW1oQMaFM+b!Z^WKT8haoo}Z;dLSHCT=yRhytVoqN6mCW#c+2UJB_tefA+81#;P z*0)N5*IcoP0A_TLez#$c2zS`2ku~NZLK{m3R3$OG8lR^1|4+_BFqoXW+|H7%wCTEO%751+2=X=_yS+#5x#q=pfx4@LT?uKq;dGBpF~vyT^Nzu z#?>u*z_@rPcki?FlR@}Xu47&ybHUe`s>| zgqF?2+mMPP%;D|)t*%>U14^CtX%1b_h4lMV>x53^8ZgOOU)Qb!XsOk_>yg*4jT`(b z86ZtO*Ly721M^Rsq)9uR(rLC@0+Z>IrtnuEO%lwwNs)I{zkDW)%^LPe6B{T0L9ixS zZvrtJu8RGm0MkMXa~UG~9M}_S88S*DA|lUqgIY`CPj45>xqHP!Pp>}WJX`y-*zdEj zM~n-Vj{ZNM~NdlgqvT=O$Am`2=Ay$~^q=EX+B=fB9DM<|#m(l6| z$1C(U?m%l|(*l)9h3=eZEr0&)YI>60ORJG%<~4Ana_}tId+*I=_|8-lTc-Z5JimeU zW)lZ_YESSYP|uLUT&BLD!Hqo z8siudy4#xi8OrcuA5aV-4rj^nQwD>`tp^)gpap6^n{S*2}ijjtb^dJxFNG z=r(UTdAdDEk8mKewn^9Deow}(+9f_Zze`t0Peq)2j@ zd(pt?-zt?4i|t0DZr^NchQ2L3XBVvw#P_c$3=#Xamo z3P2j}es9tToA?({4i9pweC+6w6sZUp!0_qOGnIr`_iMVS%lY11K`FAp@C{8n)4n-L zjizFY%Ram{UPDWOL4to7gyM$F!al`p8m2E^aY1)S~s_nkrFxX2&PznSTi z%yTf-31ITb(3h&8G9n}X(bNkZKmzWtV2Nu~%Fra}nWvu5A1#odvs5{&lGuM>Py^o&xZTD~SI?*IFWcs&wjV~U%8eqY|n<|xb zavP~@y>dA_97dwQ)~zQ$rIcRA1;FdtD+(`o)y6|4bB55(@%6Sjcy|LKHnE^zQ@ihD zLAEVjc_zyXVuo@Z-oD<2;lZTZxGGaY+@v}+9;>`eiBMehu=oTVcz~j}i!%E^r^HeD zvSU}|T-!A!f=HbxFHq7rctv!x^0s*L1}fMrII#nyT%3Q1#3M3m()~$x0+ql3Cftxv zVgGrS=aKx*QDWGqe_OV5Ig9v}_9KOtuaV!0ENeyqeN`~Dkm#v*8-T}|W zXf{A)#J4K51XLy`b!fY!rq7aoY?s(gt5M8ZYD!~CX(GKKjL+6*E@!3)j02VoL-kr= z`{`}QPOn9m5U2zp0J=-(VA1bQhXi~g8u@U?d%6K7H5@ahysdFy>LVjk zIl1@;+Hkn2NwpM7DryCUCIq&BUs9U+2S|z%k>ftYL2X-$CLcQx+)ynU>^l`h2XR}t z{xe#8HgXOXMDR?^X-#bwyKD`N53a>UR?@HGkp}(-Mp8@#s{JC1-|EoTPv;qbFNuz} zr4G|K8F<_(?fC|c?{^b|ALiNbAdLENH_ET84!e7eI=w2<*nb24y|3;xEXv$TA?AKK zpC~^&zBc-Bfj0h19%Xy}V2hJNfro&HaW+LaBL$C#b3pf7mw*(K)*2AVSz( z)l2X9MlvX8bTBnj(9m2jEPqrH=^i=q)vD`NREQGTNswCqea#SwIQ0i9Ez{vS-jJyWWhW&ZW)itJ-4MiB-hpnpGNHF1^P>lP8g4OIe47iHMx&`C{ zm}HuAwtAY=ex6;sIK}#-XD1dQEr<_t<$;y~>x{@#Z6o)05=^$Dg!rDJuf+9+wG{&3 zy=8_3>=TYyq&KE}zLMxW=DdqR4x8u8ULP`!PXv|s^=sqYa_>IEjj$W0 zV~)WaX4K7|m68B~)lFk9Vy_NOuw`vzv`I)IQ#TsapPrE3929qXpBIcw2$9jxE{_$ynAeZE@#AzSaclh(9SzE zzN@kk;gR-O%bY5;oUGz#=dr=rf|aiXzT|yi$P_@EeSn{o$EId{G>SCSJCRyUT_lyA zqc^`%BDR+@O_rZIooM_1x@LttNNE)N@6C`61f5-p0yS*{YU)`$S?v*_1iJXL5vAPC zueGu=foL?e6sG=|oK8KpoCXp6an_4-coP^^*tmc_)XRj;$Aw=<1-tv|8d}+2AS{F+;6B;(5r5oBm5S9D0(Yh)o@(g- zJU~LRHsJ>}o-QynzG`mIT$#U7{wDlRWJvmSSler6C_Hsq{*4ZSbLYgKkp@@ld~+t} zglJ|FK{;xg(on`w^>k5tG$8Zhsz-fvkNbo)kPx{4nG{X7UUbKfECw6yL4}CJ?~TjC zE%~}g!f^VJKYyx>+TydD{@@#xw104w##_43MWLV9Ise&`g@>sB1F`kU(vC}d@CTMb)W};`v1DhOMq_t+0Z{ODOA4X z?n)@f7tNe-gg%&AlYdgJxlJ4&H+XY&ECr`O3v1x@gA|q(7eno&M$0lUWRW>XeSj3H z04XLx{TjX`tRB@Xxh?@+)Zkkg01?i3A|l4C?{%UDvz2^l{VRY8O61m#?Cn4QZN34a zwm#3hM}7;X3*C;Im5*7qoOWk-CUCtPqLhWgKPki57L03k6hTUZ+r;`W$rdiWt&~=8 zaCn}Ou03*S@pCB3ha^*I;{8B(V?N}|?h884?25)o>4w;%5;)y{8iYI3f37YacK~qk z!wk%xepZU9()OVw8y?{*rJ+&TSV5>|2O*duBT7?5U+7WPSl;M! z33&L5CDF|hcVl6SM*Y_%Dw!-$Mme>B^so9v`8-vJt$weQGzlcF@;_zS^(T(3J#wSz zd(j*I3_3^qSY|NE;nY)Wb(iXB14l!>z*$j@OvBZoK7c9bC+d&+wTZ{VTT-oOSLmZ6 z(JhzFjbHAurh8q(*{L{Q_-Ocg<>drC?>cH>Xqfa{*c>Hgve0DT@KhtVwkf2jS3X7X zXO{UKUcMV>Tma6Bi|D}(uCzLh_hR-}rZk_P=GBZX$1g{1_Jy`t;MR4DR+mAZq*dGP zOTh)}pthr=Qug{FOqH_Ji+qn(;M^<;ydC@43N@@y82H=5J*W~>l>VwqdZap^2KsaL z{bK551*@_mX&N`2CPG2578_Fq?o#5p@vhZ5XCxO5!1SM;qx@Hb3G1^ad&cdl>B{PO zTHy1t#AFYw8CDqSgFl-%@v5bM`$FR(aMd3~ZY||jK4t^zj8>iGW3-y+Ew5yfP($&n zVbs8!93eF-0zUbShO3P(D9Ioe zs~gSw>D9>Uqh$Si3^sm85y=mQYX9h*8a1jD8q`^Jwm=9af&1D^(&QtphjbKstskmH z6QMu5KPh8wxZ)$%Mwh57xd!Twwo9tCNY1FskzTTm>&Y@H<03WB()V^m5 zdJNpdZs)1iwzPfl!^*vP9-L{7-nkiTLN()p>q@tThRg47!4a?~N~ z-26}7v8d0dJ@?k;srB=J z@slU+#ILEWY3~yqhX<(k;H!}9(E#5g)QzDB&g}@*vOCp!IDo6hyHGsHRMa_ou)6qI zT0}qo%H=Ny>BW)O6ibqAK?|Xq!-hC;hl0u@1&5AuD!$iqp*>*V4q>}vEvEsQ#kSns zk~oEFT8$tRVG(bCn{tzMA2&NM!=+jEL(xs~cqb?EFie$hEgu>YW^7Qu3nx&U2e*Tw zqylQ|{bg1-My(T=z0{PV62P|4J7wI-f4VvOr=>GL*GIH7T(CCeQLMc+^u^%=2hoj{ z=(tt%tfK~q5StWgC4}YhM_<%8a6MC<9(XI5_UBs(GpIzuR#81o`^3~15j3XUGl7at zd?}^W7^vsI|CLI8YY<|Y)~~3$W&E%*l_>7=EwAm0ab|jTVBfq;f4b1=&b8>Hv@6nG zMg+cBYF*iGZuVDi(B*|IXkEopfSlemPO+Y5~>*;&MA{ zXU8ms;yN&JeC)x`^s?0CM7@54#LfX%_Lby}bl}%&|I#`$1Rs>%-#X1SV#k6(4bQ;B zFR@_viX++FqF!|}4L<$raHUsT0U7StXm-x(Cbj(gYdkyv&JuVyY$Z#|@C;Kcl2(zF zO5-E3BxS=AmsLquO?sKj?y_CC1f2Nq$4$VG$~>|>#yDcGdseV^u9;zTWPjDuL7-LoAFeIw1<6#kPaolmvkAV zr@n&iTV>_Q-m)Z5YU!S6OTgVB{GW|m@xy(zqi{?ewU@382hR3Chn{x&D#Os8;{>GW z*V>~;=0&!m+L5L9WavgV=y$aE7vI?Lu zZ&)9o9i5(!9dqqWG3-q8iz4I_c|Ec~L~WYYcod$-b9{+6EO>x7$I5?RqC=0hYu zdYpL3w5LvumT`Xc3st!vdU_|1*ulguHKkF9+(J>ApOucx0)e};UWKw?=k9GCMH%kjUH3&>JtPKi*>7JwQwD8ky04_*Af_eJVK%x(hAm-QQ^SQ+qg=4rc(op6lP}^th-~ zlUomtIwMp)!YB}C)4yVedHoK2y&l@dpNIQh_mxS9lPqTsv)M*unb)!UraDS?>)JZA zL?9r5{ol0F4&_dE#ek?Q3&rgh1qer^8!9VL6FL&SRn zlGSk~+?w*j2JonV-btqd6PNkg{w+a#m4TnzAx|Cgko#Q&#@oWLIq(y^YzDy1&zsMy zM*2jaR@&BIxW>LbKj5tlHRH#x+~h-&J;mT2z0l})_$OpV$ee&@0D7b5A! z`Jw4@y|=DjI`tUb22eAE`xfrJFnu!p8xXZWniAdwKezgcTba^YjcISHV)I^I3-xfQ zZ?8J2K73fQSPa-#Op`*D11A3qVDeiD@Z6E?D%^MRznSMgL$t@v^vAW2^w7dK3vRJb)Xi+<0A4{U!tyK?f9x)8o2%#pYFWCT+<0lUBKC zVLaCOHCoi2z9;SMDf~{n8hYi~MiS2b>qi)gy=UUzwAXjO%Eg@=*o%BVA4xQU(?buO z9&UM9E}72C{p#Y~UttUAfn`A!<3l0rOuh_fzicG#pBGn*;-PbBS!1SAN>fW4k2|4IJo>Mv3Uo176chy~Spnt{#1I9}( zg$QW}c@+!h=a*j15FsI2P1>R*W>3dZGEoH16?776kGZT$gtdt~(2oWjao62?dH^6A`sPZYrjoyT7m0`!|4b|BENU6e8S9 z_bWb@FAGq4ocsMS{|d2Bx1t@zIQQS>hCw-+MMI@;kR+$i?>5hMM?aRu{y+|QPrnrE zRJbmMfAw*~jCxWF7H#)G^Qb6j_h{Z5{Cz;!YxUF0F%7P$Gex_L=XvSv_~=L+?&s0j znR>2oTWNN^P9f~B+TQm_8!>mJIVNut{Pd+bDGS?Se~JuZr=~($V9I&FG8uWnL{_ zM^a41d2{YO2@it+mhUC|$RqI`*Zj)<>JZ3HwFQdvJ(A|7?sc=PvkaIv9|4%FMl9<; z@_w3WY5lo*P@#XCrs`{~*Fs&Y60~2-!v%ej#Sj|4>~KJ~iu+G&8RwwB*JwCHJR|jj z`UxU$4nGSvNG~`<$gva*7F#_Y2d@9-|07o1|6>2HbOjY$jZ<+VhEnK8$YNfm@N1>u zm-hF(szbIRzJ0hy1clQBFXccL-1x=TX{c`Z9Nun$>!HH`E+oYLaR2V2Ts@Ci;>q(# z;F`~0(xv}hn8Je9YH~{eT|ggZE$*?dt({OkS1}#mOwtd(M0}Ok@KN=eOncLk^{}u+OB_FNy0rSiZ7q!o@^~1mXj!RMSi{2 z(OowBVZTD3KomTv;gF`FoBOtn-TPi+g8^0D6<$K~p5uMHpotTi z&XmCgUjgL%-7`uQqT0Rp-RwaRV+o0%;#sVKfUj%Xb;@SbZgO?siIRSo?!H@W$0e1k z7_%6gke!8uMIKhauk-%$+o0d)8E#J}rEfa!sm`FPd6yjr7Z+J#!OBoeU|Axz_T%l~ z`jPhE!#cLhsZxuR(8~QZ^B_Elr2zfT^LOsfK7YRpDw%dQeYb0>F7M7T%xm`Ry84|) z=Vnnqr*zUOF46ajITFZY1(doPxyo}Yym%pKO1&PFi&`>W#frdRaF$WyepM0x^_=hDQ+M%sz3hk*3Zx7c7ih z3mM?8n21m&a|-QK@`<#*umf0azKB#_um2T9UA%g<70p5e7EW%o5#m_oMK`;q?hB>|5)WeyXr!7jv82KG(MVon$?T&Jg4w?zGRLB5C=4wUEBLv z61ul&2o|q|c`w8q`U4XohB8ouGcUWOcxsAt7GH4GaY`Hrz%_a9IUZmk*AiB&-+OEh{C5z4GUBDxOHVQvW{8?^)F$uEaij}mUro94wNw7#uf#P@0}E3J zc&n67X6!|AlUk2P?gg5@T^F3~&G2pol#u8HFkZ=kN-bZ+X<-dOHc%R2n`?dA8Q`}7 z1VKefdvIW_^OPbNuoPI59>)ID$tFM^Y6-wcR582$HfJ5<(Z&Q)2Q-z=LVsDpYBZph zIR8lU)qqVTfwZBY06rE!;C}|tX<@Oi0QOoUoag`n1wfBd^}xBE1V9xSa3U>9upf26 zFL{uD(s(%(vTG?&6MAuAXVG96HJN+~#HEZazhMLm529NTchrsWwENSzz38Kc0r_NK ztr8)7YD2K|WLZ$(YZ&g&SzhLA3nQ*4fnX@eH4%bZ$9iVnWM(!a#~uH#d>f&bYkRk! zYUhx&1pCUajTzby(}h3eu?SbPBt~pgVLsv ztqBn@0f~RSdw!X!1i75q_JzcQPym*+!`{~N+aZUCmUkAIn8^@S$6Q-)aszMRA>9n- zkypBj`ZkPEMzdhoWfl3>FH;qJh-38V`tj9&;v9V(ssty;{*I0Hj&HU*ay0!<;{!Dl z*gT8b_-?G%{9k8*ga4_$I23`6-8u7Hgp>>BD1d)?p(jR$v}_txb1lCJG^eIV0Av8SFO3* zVrHmil%JxJeT*S27l^OYf2Tu4^KqCHgP>#@i;Ighb9>!0SWRhfp>KRPtN;4V7V>n` zKxU$hXoAsQ({}5E(pe!y+jWL?$UB-|g?O5IIqNeE#vWBS zW2dATDbO8|e}R-g&`4wuo@%Z0)8Z`WYMF?vAoESKvWMBHaVBL2+0nBS!yYuU7sEgX z$;Mv9!Y_UYT3;3A&&&%{nRivrgi*(7#KJ=rVEpXpZ1~a~@5Im8Vi_%E#CV7b7nUPZ zqJSb-*ooE_)b!W<7r`?^U+nQeTK8{uBvA4cLugFMo7{uD%a8R3WMA6hCaj{H#yb%`>gp#xU9_mhx{O}$xov%b1ItQJ;7sjtK7`q*EJynDewhjYgvR~nlC!_ zkH_YsVxIQ!0@7fPQL`Us*ZQI-3A&y5Q=I|1@Unp3{9*Xt$=e4WYXEQ3z3Md$e;&!$ z9BQxjdgX+9PzuD9c}JMSf!nRynCo56u**f!rWsLm^VFkz|7cO!!WFsUKr(#vfy$b1 zJ|U-z#_6nM)6vP>u{j2bZGK5JM_T??kVmnR;7GGAHMA&5|`xuZG zE^8M-%pZ0|gjZg8@Gz{dQ$s2`+AI@?+%)tlIXa(_uU1e!Hkv4r6fbF4nwE5Th*@1_oLSt|C^}FbNL5Bih$i(_ zZggX0)TJO`j%1w+VSDMr?c$J+VBe=1(|YB4LF%I;`bKgKAFp6l5$*s?g zLZmB)_if7JjFDP9^+Ij*r*_Npq-Z@%ClR3J3 zzeM=^`>m12dt|M2opGhB*_c6!o{i1x)Fwm|_RkE5X&px%F|&kJbwJ)V1JH{GUtZHi z$d@^W;!S3A5JpUZtv06q0#)&K@#I67S{R==3(cf%#O~c1VS<_FT*Djw)M5#qPo6zo zNs!~;(>-vT>i zUkaKrq@8=+X3q&b42W)bKs2;)KTgMWv;1h~8@BX*jgYac*IRG-04r`fIfC#40jq~EDvBJ_}i$7aKj$lcL^G{#$_o%47NI$M??3@Y<>xT zW;#)&Zf7$4Vj&gZ8++g!@_PDKR6endRW@&9s~(6OK8C8E2@6?G;yfGbbp5>Wj&)IQ z41g;^VRy-d7>zcSbf*1UAv1f69?%Yz(GSBsN$`c6a%|265qLDM3EJHJ?4+p}Nmn3q z{!qU{ba3$U!sucAo7$)2wf4NQiWiL9H3iMDdrX!soQ&PHxoa>nz`;t5f+oojYJ6c- zM8PaL3)63Y*Z_MMTJ#r&?>E?uXf@)46D?IMxKZy~<^rlIYu>(r3C0-AC3LR#&*)!Q z+0(#I>uROIO8V<3h{=Oz>{w|#4{4?L=sxVB#jEa^@nr!I`nS~h3%4C2i;ac3*>S49G zR3o|TO^S$~-9w4$`N+p@gH^YM@YGd0_L?C#ZADvJ9ZJ#9tAEqBT}|LmDTEjv26x{I zoe*PhurBIR%w!f>dD;%m0-9btQcSV7s;t#vRQj}-RFSRGRMkbqoCrLyRw$sC1RGKc<8Ba|?7;0BFN9}@%yenvGUAQs^F z#gGf63;e7bAz>*!fkPfN(cd|%3JOsJ$$H|v9r*2@25SgIRfIu288)fg~BB0HO5=YZW9(

;pI#L?~ zNfJAj-R|Zu^^$}=rKz4J$^;GvbAik%GT~gim14a93RsdjEMwNO@q+f6PMt`e(HC_B!a^JEJ@3Aqm1b_iMQ;kc{s==coPv#!QU1cy=qt zB~VQ5N7VgjTr@O2qq^^|--?jcYgSuuk~q6>MAaUfsV=BM_bD8(eu(|SuFtJrpIneY z2eM=oewkk#75Q#}N@Fn$1M7m_Qf({9`+*kUbgZz5qm^ow)56Z6*Kop8uaC|DpHcS_UAJw_c zl(g#yp*=4_UetEbdiHfc29ymjo!VIo>_Pvz=uRC|Qs7J3?FxL>)L`N%52cXGLC16y z*Q0yxW2_AhC?Hu7W)m0fu=vMfW%8I86g-j(Uybl^E}sYzZ1WKx&l#zW23<1vLRDTx zfx_}_G|p-V`&)*xF(os4C85uLff%5@f8Oclb4(!sAwhY;R5rK!w5w+ImbP@1$oAXN zxwkbBirU^PKI}Obo+i;kVini8SnEcZkXOC%HjZm>y{%O%x6yNs(h?}3*SHW`^qgdI z4w6`b7x~x3 zFnGLty5smKOu2S#d13b9e<-DVU4`qk1`{xVtH&+4;I=)mfb}0scuPFoNln7{`#;Hs zt4x!Wi~mV>JOIO%H$`defJRldMuc`#R{y#PS?aPP{1^^G@9(y)`e;f!l*vO2Xd zx2Nalr}T}*`tZ?vk)wa>VYE;60-vX<6#(ddId>2|?8C>aHp}bs%ct2F1{AL|NV3Zu zvRj=*em2%#?TWWm7dZeGfvuZ)g-_RwW>`RDwbe$7?J?C&PE3R_`73c)F&L%LC0OGu zaqC@n#zAqqHr1j{zBnR?ecvBCL^oTRPW8tD9n=imSy3jhHa)`qF+k0qSD7hO9<qx&Bk+Jal6;qm5ye?3C4w-GJV18I%oVizb<+CU~hs3 z`GuudL_Lv)!);9h9pAEvLJ&S-G*kF_b?> z1nnlnnMb-+JJmboUgf+5P13`jli}Xn4@M;+#{8Br{-gJ;Yn`tB@G=|SEO z958}KDYgq&p93Iz{FyO_;=EA`8t!ua4ZIusS5#n~qg^ORs89`L?p5U9mdycuy({rE z&hQqR76!SCGmF!_8qEZvhi(;&&|=5cT%EHI0~GY0kZRV(NWB3Q6x6|>6O@!d+lIOF zqR`x>?3TNOJ=B8?R3Vweyxw*@7zzPBy^JsO`vu?bCg;1!BMDP(&V~oaoMyglIs zwb=8+`j)x^^9I8?F^lh`kw^nSdojs1Bv$OTgP^7_l*5}%G5F?vcukLmCAy3lSJ~tS z&vVN~&C2{Ymoksu&7EBw+*1YnH$~Aoh^qNP+lU7n{VgnTjQavln&})=)%>Bp#Diy6 z7x&j^9mfi{7n?`kn|(CO$%FIF8`g7EnlVVj5k7%-vDe*VdWr%tKUase?Uqs_5gP@62m6a8O63DXmSF&2mk;Qb2% zXw^Q@Vuwu)7bxmKTJt;~Y`VFB_FsHbHnGtmucXLDv5Mo;Ut)O(?|ie}Z+2n!kIkt1 z$o%Yg6IEZvzknrI+d0&VUGUfMzfh!9eTeoa7hY0k1GMCDyXuA0me0=qC;q>#C1wSL z-UDi}TF$F`Yx(Mkzy3F{|2p<*+5g`u~*e?k-XAOQKtJDnDQ^u941xS1l0QW-!n+bXPcJlwd%uG$WXOPUseOd+i#s zuy*9x;c3aX4}A1VT;a!sS&EXrd5?vYA~#XMO$m{fT1T})UH3~NL-ow2ENa_RlE(Rg zcnMnbna7aTCxXH~Xb5E@M{Z0UT`te^74Q=y<8;VN99*=nQooUa5~G8FQeIv&aBU9b zwa$URJ~{#i8e@VKrR$inXGYCiJ_7_o>umUwDtTLmU>7pz);+7taAi5i=NuUX+Dw9{ zyf5SbnhNFvZS>n@!C%kwOHM#ZL8rgJs5UNsM=@d|1cBn@pc+Z(3(G`PX=&c+ae%~| z*Dwz$fFKA^%EsF+y^&_BZ$SRnt50_sL&{)E1=gi-y4)#Fzr79Z%XsP^M% zNM}7u7k*O_3aEsxpD^%6*VwJ-5Ag!;-&eFC_&lWm|67=Ny8G+^&Z zL5az1Izd)iSo-_y*3ZE6#Q{Q{JjiK%l~;@v#p5_<>i2lAR-s7%lvpBZ>}?(wkizb}32 zc!z5`B8at-lKHT@JiolQ7~`W9h5qLC(648S(7%E05(9zap`A>Qn6KT0X&NSZ^Wx)0K>GPuktWScBSNS&u>lB2S8d)hj=%gsW{E?V$ z5IbD?q;rypk56uoM*Yggo|--?v41J@-9E}HiA{qFw$Sw#8J=OLLokvjN!MX(V?T$; zr|ARrK*YeyU^a<#`qeKzUS`)pZ=0?Zr;3NFQWHWF@^_PDX*$X>v-LfO!5g}~YAAz? zrZCk)_Fy*lS?Fs>4*sVDctIhAYA&5SU6F1v1+1pS)PY~|*@0EovsQ2Xvb`$7(%bZc zx8-BBoatj&q$^_}k}_TLfxPN0xu=a_HW6(~l4Giud}UHLr$x1{=f$?RoBdBOHNC@5 z!SV#UC_^7F#m6HH@qQW(A{HV|6_i2zn61ul5J;>k^@6oWoq3d$&7BXq?}-InSrU>W|_^~-(7zHBoo%8 z^CQWPl;9xAth*Rdttzw2&(#3O#@97BhW+9|L(bR1!j!yj_>Fmpi*ZUHg z`eQ%d$I1lwSzE-xzP@OP3N^xErrMyC0Xg@p>*;g^^$dtL>0xMSszm%eO@-fjXoc?u z%5Jpm?=Y9Fc>OG=5Cht~grpyYp4&{u?`6MPx{Qmk?914=n)-g;8oTE$x5>(;;C{vF z;YDoctx1E|vB2VE=FDMx3Cfat7sprWkpFn6p9v-28beTCqGF+VsXR!ASxXW| z)@#w`bjg&0T5>LL9%K3{W=rkc980RHQ1j=kY1LdTL6Ibi1Bka+g+|M$X^pxw{E*aC z^>d*L_@#ACnimy4Rz$*J<}H2MMHqCb@pMSQxw_sw`1y8wfWzmQ3;aumQJYI~B8f9q zSCJ!Ip+*$F*~L!LtEPNh)+Au`3;stJ0d95GVhF9&li6sbR;DJ5OMqcgb4AdmAU5eK ziB*4YbU11>Ss*I4&p%-M?Hk&3a<;z&g2vD|gzNDYYX7Fq?{mqScS}?n84Uc5{fL{X zx@UxM|LkSAcA5HQE*TbcQEHgS{s!y?4*alzybqf7DRpxJtC zA$(7j7~IAPg);M;Ib?e&O_H&lJ`~8pat_z6DJ<3+K`k(7qgWqrx zNEBjN^aOn6f8HNxW=!3x4G`^}&q%Hm7^|M^dpk^qaf6_fD?zY~fL zrXy9-{^`P>gOzXU)%G&>$o8V}J-%-;&Srs_Qr9cTc-~T{8bar8eb)sX{Gf|1o`tQv zaptw3+X2B2X`sUabJqnvnu?}GLvU;=yIYr@p~DsvJ3AkbqCPgih}vAcO7eVF?xxYq z{=0rdbpf{L#wmUgiZ!~g1|OJu-X(ocSDElZ-&ZfTOxEwxQSU;|Sjoe_Mi^fWMlK;0 zP;Bg++pnQ6YPtB%$?wLygR1z3Phb^pFeBYGJ2{O2uC7^l^#VQ8BaW}%>1A?9y1~et zl-;{G3t}KF^1vFcAw7AaKVG9k$|#B!UfH+vT86aXhNubN_A$ zMi1|gD2QN7ccTW47TL3+Fn{yQRQ{QG{@Aa&(sPmK-l*10^02)NZ(YS{fF0)X7UPu+DvW|9}mc>_mF%dLt9Vauc>5SaSS)S>eCAt z=%sAx4s3&OWK9r>$06pMSO!XGIU58qBIoxFVIL<fb9DmkHEDynhSKz?wFr>p*ulpk5 zt#R>m77_(-V=qwb`ucnXVv!C{u{3-qKSJ+%IrQ+~`W^j1?iyw_5|HeAegozvk~bLa za0q)B3Tp6i2)jc^=x|NbFy1MhD< zk6u31!kfTFxyGb<9@Y;s?~H;MX)x(0>f}qMlX4fXY}}Pi|$BJ=o`1Vigq> zH7JtmB}snJVB+>0)SZ4WVTgp$LWW5AlUVYNVRO8pch}W!p${nBA(BHw19i|o8!4{c zdbCd0>4MER7CJ04Zch3Ark#$qv$yBDpbd&56++e?>DT7ZUkOZ7rn) zwp9qL13M+HAvR(7%*Cr}1Q`i6V?BMsHuBBS`a%)uRSR(b!vT!$tg2Ft#&NwM^A`L9 zcZx}7)%hb?7`$bS*s&4Yq)F)q_5{BcBrpVAj9tojCmAQ)?c=u;rwm_wE#&r6SG0;7 z`P$zEMtkR?1-?&Bv-m=u2VNTJgLY*ZT~r-Pz&+Q4N33h?S*ckK*Z{?g&J@abAFrs5#;fQ}vflQ9=sm+ZDl8KG~+(lpB=6`3N%<1|Q zpJh<99D9~}vfzR3({<&oOPXY_JEv|O;}5j&OqRns^{Yf*f(QRL`rS;69;KTOu?XfT z*`yf`RDSg_-x2ZON(|Ar9F1H}F`U@GqrV&A#5YxNT5o|W5I z$)5sqe_E=7RP&4{x0AE#>;>a8f~&F_v2RZDd3k&IrLL)*_@vsjs}BijSQMg5y7Xfv zO(D`+Wby@4?)JoVx25pdY@(M9gS>8yGFYP)GT*%Sf;VuNm0|N_e~>WGk1#@_ zFMdfDE4+1ih~W}i_BKc=xg6fbFiFM6r|GYA z3N}$Vw@7|A#v{heyY)yH(@nE3>fqw&ZM~q2^P-tVQ>*e|0yL5JN$;&L8iMjfOEYm+AR2W0`-gKMbH1{2WY7PK$u2qbrsjlq7u@S* zsWwN4e@#nStM3Y;B#=rmbF51}=O6f;C65ff6+LK~c2)0gWl+;+(NW*md z>@SZe_Zu7KNcPVu!Hj|?^ZV;h`M;m;{S9#%D_DFW-v=g9!3qs7XsnC^jfYZyg>`Gr z7q(M0elH9q^q@H2k5#9L$bL-;>5}PhppAs-U32;{PQ0C%7OLy*;(NhX0j8qWWg~Y& z17Cbv=I9Hp!Kxxz2F>?vn%wCgR)4rp+$QLFDCJ4$ySyNb|MpbT{zFG!$?M#hf$ygP z;uU@!dW9d_l&F*;UWfs62$%{*FYX!R96CI7#l&5xWu!^m0?xViHc-M(Jo7H2G!`j> z_j~;F6)l0&qet7ATg8HyPc{`_mJMv4@t$w&bdny2a2;`L_4$a13_P5=rN$2Xo>uLV z)#TOWE}8blkf8pX#878uw@>Q^?bG|5=?b;JLy+pYxu|8RWL_is7S-LYG6(t6CZ$iF1^ zp51Vsk1jku*kq@%D1toWWhmO}dc4mkTb;vlzfm-RIX#$^@1n?9K7FIu%ox+HAi?=^ zzRS_>=}so`5o&ciFgM9;3Bm0W20y5=={;uavb~2qp)eFd%GQPwsCdrsIq8&e+1YIK zGLe1Ro4EsLn(Q>WI2W*n2nQSWE*$dnNXa7FX-$LZE*2dyV`jUfjxI{u}>D+dKqg&4Sp58b($a- zTEN=_4aQoaW!cnLedF>C_L+sg)o*>0*Vv&VQ=#|sXoP}kl_9qiVoOmwW2Jnr^$lFJ zk%VJ-szsI3;sUD(LL_z5&PKDig*AsYr)c$(1yIZ zP=aZ)CCxj{Nc}ZFRUb^FOEJf;ZV?kAAqC zRD8$``w&^_2eyi>I!ty-8eo2KHx}Y_O*)OQpm6oecZ6sJ|hB;$*|Hkh(sTWWv^RXIG#+qr-!jskqZL%%+71A zn6M+^Mh7b&QpJ`HY%}9krJ5#h(ZwU;x>z+0*q)K6RU~RNkAKx z$l}T32h^(nGRznmOHO;dq}0>Mm+MRoR7e%^L)h zri%7VCpH?ro+xvRJ-=-ou4Gl3NU0b~v(%$|aRjQSuR^M@Or}wRvw=tPx?W(0T~U@C zliE*-AA66k$hs4gkC(j47txMxehJMqh1KSF+VeG$)^1T;QTr`$-JOvb zdTa3ALEk0{Ax{T;I&3)gm`Vbqx`ay--(OkepOP+cX=7;d;iG(NKa^t6^5;x}`>YC{ z6m}t%tQ?wuL9W@Xc(CusXCXqw`T%7l8&4%rSGmd|(Txh^j`^mV>B73OnBT*I!ebT_ zBe!0nd#20MYWd@-{T5*xfl~(QTX+d>`RZmu5IJI6m?v@P7>p~KSjTdr5DxS6E%Z#w zkiJOl)#TFXoOsFRt#Z6UVCgCFb`m0-#0j3YGBv|?3osT@$Vp{j>$h3NT1h@s$%{%E zM7G>JRMCrDJ|oVt14p4wy^vF_fu!=dd6%E9U^^9>pL-tr6^HP(*7H*Fn(fekb9&ED zis&91W(=5s9A|~MLA05>q3_cyodhoUS4n13%A$Ln2(s-tzTP#3$K7YN5eY8XOAh`) zq9f&&@hQctIWywb>|eBTnCg4Rz?=@!27O)^72E0pr_n@ysSHG<0uuhP($arj2!}goecxiQY`7HIiXoxNGs0xG-D0@cUBce?@ie_of)9w{LN$M!oGd<5xCVfA4wd_s zI1Ax;Y2U(EEKJNiab90dsa>1;8U);cF&9JzRd5wdQDDiT)^L^5Rg&~8xQS29WO-Uu zpc>PjTf5!LN73vq-E_Y|ExGC`pD+3lsz7NNk7`g>l`tM-FJY5Ox>q-nbT=hl^?I04 zYYFt*o*|LToN8D1n-*L*#TjYCATiP_%V&~bYzFls_}^x48V`SxeTHKjc=VP-dM!WI z_u$>!Ch8%M#BGOwMH1k-cD`$DOW;jRT8n@V7aAY9Re_K+z0?ON-jT<-16{=wW|$&a z8mAi@vUs|LLG(MrOgiXQ9psC%pV$3)j0dbGs6ORf|5pWLWPd8kWNSmEB+q*u>4YkZ z0PdH1aULUY$;N0|tnzM%_(I$GPrZ6g`Y|G!;sgwV>vxRx&Y~3EErr$k4hsiVhv0xC zjw9zW0jcDv4rqA*#lSu8uPQ&8{%9h{i5yp_V1-$q1w5S#zLDv`aUurJM%^K8=qC22?d=R&Ui(MVi+~Cwcb<^KLOjOF_W2^RXvO)c+pnyo*mspM5xIj? z!L{>2&}u?MkxzzO+0Q6tsz0e=Rn@nc`AQk6L!$kG`Jd(#Y9w`;PLPMaPBnjqgJktqdz5_O@Y*~pLD)_#Bzp$O2x>ZSB zc&BTA?#@nLyc2Ii*gkh!>0R#W&dY(biK1iLLNAkL1EU3tbOWu$kKM})eVGM6eBJ0& zj%@0WGt2%qz;#%^l^CrE#_)S~pu)J^n!iC@4?a~>#HWvVr539iRSDBh9arTlb$}B` zt&=SoJ5RPTI3#U^y-6+1xFLGq zNU4>j7@f&D{ieD_pH=Fi`g7qIDagDkb#Y==482KAbedAMoRo)FWd$85;SPqEL6fc; zURtV%G^qAr=VP(LPBlC>d;CwFvo=iITUETR z&%hxvv%M;UuM!@kM=s9uQtZy_YnMd#BYP&H<{1_14&U=blo6w#Cqucw`@Hp(Er!4# z_X!P&5j{jX;Vr(6I0NyY9mgNG%oeLzUdbs9&&2>+d;38|m0`!%E2#|Wn@~GV55MB` z?-V@*Oj+5SLrf;mT3kYJY5rOBSwbgc&TPsG9j8@b|*%Fx=ozir_vUaCOo32fqh3^7O5tU|%@v#WUyM z5H8@wdr8zU+-&&-msf-;wv0v0TccXdNHL%Q^A_Eis1<3dn&}PZvrdh7wces!lxV94 z)qE{6rBFNVMv9fPxOPMPo9d4ZbX0!6aEmfEy5vPP7c#T#nSta`kY3k4gJYCXq%19x22uQyv?j8>%1w1F4s^}0eK`!JQ!CDqcB7`2+%_xIVs$P7j*|lZj z(EJnFJRYPfZkb(vK^XDqFgs9Ar(J{peQi$rWLfLJae>((Bk0vSkhJubkkeL`C%8_M zR!=a4eb1CwAbG)@3(hhDW^Z8vO^@S_!1Xk-@3J7G9tE8b&JASd$lH7Ahd??eID% z3k_6;awEUoEQIgi_H|IrJ`uuh!rRQ_(bdM&Xsh(iBNhh)k_5i)`W3UQh}tuxZ{dpm zTl@tAmH5%EO4T^n!fTqr>ryIJ-$NTIkU*faiNifaq11@G0Zk~GZy4yW2` z48#A+Db@Wdc zvmoGWtAZLA8kb+hKrjBw^zLInIC_y}ifAdDnj5MZAL$NGVY&pyi)7wKG;`jWQ?_vU*Dy zrr)>3BL7FBEm^|lrz)9qKsp(K#h4p+1tRWyW79JBc@)1~Z{Ac!5 zX8wVwD`$_}?ik z$;4>a^Rhf=`-`2P>I_?lY}O#oOzSZWrZwX)${yl%E_qxT}Zw(S^2Ie zrOp}E_GED^c6{1K&hhXXEm8^&v;H#NyGw*nAm66Fn%M(}e&4B_qaSL70G?qc5Are4SxEuPA)h}(p4GA zF7rgovCD+wArW<+34LJ72*v8+MH#9)R%3h~$DWng9?ugK$#}gKNjQ`O$tO^AV=})X z9XI(hP({zW7ufdQLY!dQm-TH7@1&`1j_cI)lL+3q%;+%|Pn68y2MY*}AWT^hj|LCA462`#ggj7J zx^;@2Y=iG4(&$2ftDv#W|&u%Q^;bp~Aquq>Lg zU3P0_a$zf7i5E^BdNoaHmF9jj{x^N08qW&!&lxWCG@8gS=;%g~Yd;A7Ks3Ea(}#a$ z+$caoRevPT-QY>|w;J-W_=$;OX>xkfATnW{Ul1BH)z>(bpNhag6;F8K06O{j7pauG zb7LHI7+2^ZKWK@+Aqkt?E6p@RtcWYTe$FLi3|jKmT}>cw)R6ibnrQT)L0goRS)M)Q z;>N<<>QG#mq4z{BO%J6@lj_*mw|a5`6#oW-^O# z_m7OSq+P-0pS|nti=T=fi&1MI!Xt;@Z0Cbz#4zcyr=u>eYEG(5RfkqYo2L*(mL}G&TI=Pl7|&n{FPvUH6JN_ zGDLiG@@ebd;%jaf86NEek@P!ytAywC8=>?ul#PDrhJ4~Oen&> z2G{P=Ud=u+t`&xNv6>~gP7t)7j%FMF+Mz-C?AraX`~GE!cuty+gHg;O%U3V%oI{bB zL=FR%ri4qLYwKG5YR@B)5t%S$?i18k9$klNvzV5zLvdqcJWKhn3X>tqFIr?rMiP}- zNgLQR4Da=87l?((-srMamuIp=bgX2(ng5sG&NHg1CtCDDnn+VfDAEL^B!Ccl2kE_p z(4>dnn;=T>5Rl%BNbg`kdJ%+xG*RhIB{V@1LX#>l{Qvjfb>IE+KD_nb`H*!OX6DSy z*|YbaS#y57YQn=Byzzu@?CukJm1i{U*^N(Y>+=PC)jHlg-2P~c>7fC+XVXBVUm2af zcG_G)`qj@soXo=oOE0o-=iH@fsCA2#?+@81w=0&UdxU&vLW#01T>Kfz|(kQqL5u4lhOq(WT*^y?9F8bEh zLrTJf=6u?6+7b@CoHpQZ78@d3EuXV`H%eUJ5MrBh#VKEFm7R>gX2$%0!UDA@)Ps-L zofhI~K`r(^>Po`u#-e6HiO44hMr8s-mul; zu7T7KaO=I7n-P?aZV5gT2Cv6|{9WNeu#|RvTyQawo)~m9+3OgTfn;!h<^Y)7O~%?7 z%`k&m-mzldEZ$wszMr)ryzLWWt8&52OPf-2fq^#*SB7lidT{O^cneh&x568IKXMwr!skx#}Mk z66b6>DU_|biC0(>#FJbtG(ZTc2qH-1A zXSxwJ10yRJ@UO`@U?X0i-*E7{CLloRsi5y!&LQ&G$QIsfQ=e>? zu$7ee%rAa7JZ(IpQbHzF*AY*^Asqb0_IwuiDPw%9HG@^Jf)pHUzsqjopGx$b9IDSf zXX$EWW^`;Edzn5O|LU*CqQzIdAG|`;XBlWCy_O9HFD9di6zexiAG{4lIxU@*BjXCt zw_1-SDQnxM4Ek{2U%-NDQ#!+Kzi5m7#p~3(CD_!=Z86t$c4oji&IoIGL*G5KuA%Z| z!u-i#tMA}QN7_#8la=z3RBzaGU^<=R&T!F;Gw1iMIErKjYx|-S=PpJS+c!gpuw_%YMH2JQg zLcCg^3Rz8c(kbL_W`Y32!N94EA4`$QSk?(iy>-dYg^@e%R`TF|- z4XhXRBS=>rs-u^`&tz+*oclV&{P+MkvcFz;sZ&?Ii!lS_pfAp-!w5gitx6)Jg2;9y z@_<`2Q-ydJ%trRln)6M88i^`fr`7Bnp$CKZkL(e!jszO6IN;<-lNg=_bvVPD&%WLf ztHh=;CR1eNC+Ahaj*ftthy@n)l%|XyRqXCWp;@dnsG7^Bw?#t{#GZp#U)V4g*}4G@ z7Nw+8H?3$EMG(oMKvDhnWTfub)|vU-?ZvMSJ`q7_8qhBs3_7zJ1a@#o?7`1S#Ns&X z>YcH9XWmrOj9?d`Pc7TC>_;7b&JY!h)z~wwoghU?9)w`)W;iadQ5} z0`E>yjc2WXoraNgN;#i}hW3ZwLrcaC1&09Dj=n|MvXCY=Dvi6l%gO#zxH|+L@WMRE zi%RRSM98E^^+j-9zd7np|NV45W40JgDA+X)nDCKA#t8_r9XIrk2l6cB2&C$o@~++` z#NJtCdq~H7&nHF=HFQ^FnCQubg^(IHyiUT?-l=)qbc*_H;V)-K}DBiJf)gYzA;{O6<1Sf zqR6elnwSouvH)FEA>6QJG{5~q!`g3+Gela*o+!la*zb3UBg+CJG@v9Osy;%b2BT6H zM?syk>mZgGCr(NJ$2rA+TfF-62~4uTrfChPj6mUni0>|k3&@wPiTfl5?cwJ z_+=OetwZ;o4*td?DFu$1qR5$-d33|YSONh_r`sOhF=|}&Ag&z%9JzW9xQEq(Njqm? zh%yoOL|g9<0ItC@b(h&>svB=aI)Uj;G3rpu#1l46l*5F)tiAsIl-AoMM4C|rtFT{m z<)`8$!1S9}-lV8SC-KmnJRLDjs3@FYmLsTmIjM2oR3P^|>y<&h? z#3at688{cD3OL1UeuTH``N3xltrra_jCw;;1V@Sh}9DoO{-=6?AzTGs|K=Go5 zIPl^al^tT2*mHjao$Vd$8}>8Cp#6<+bA~owUYQBUXO~;nLO;YjD)64HI{sBLViScU zy(1@A;ozTj)#YH8@PY+*yr7J%*)(Y+&n+eK)oOTvP@>)73I_92R(J2% z?4%MnJDzAR%gM0MKpf{7AeVo;tD)k9Mlm&zu0&klIW;p1e!v*vsOFRm6_!M9^uq_G zlC{6?Qmasq7^kYDh4XM?f~CYC(%dJSwp9VsG;moUwci~>`4U9VfMuh=`*Y$WEr=B-lT6oX5r z8>wrh(Rh`J+^^t{&u?GZ##K*^wc?^RmeA3dOK2jUO&!20?1khNS%51)uqaNJ$4_SW z_8FWKQmmVlM&ci#eb^I~l)#*L^fRXRvHw^SB=5i7Jf%%x_darWp~VZF205d?LB{`&c74HBnO@_DfLWH-JjrYW{n;l$LPCalubJ%je!ncF#d(? zz$2W&WXizQ+T!mYnR)gc|MWaMtQ{&+*wu;_mLv8_7%WL)ZU$)d{q?xAWRje-^m1wpYfQ^y zu&O!*9Mh#Dq*vTl3II@*?E@0>7yPhpDvEr7aer{qSj z5_j=uNTffo*9XtWq6JoZpDXvw#i^kV)>I^P?FN-(?NN8|ev*RZXGw0f2IEdaNw@}- za>w8P0Hr9e9mo0KR0r4?r?19|CMS}7=?!9>k{+R_<+^XV+GZg#;9(Bwj{;}ohm`oD zuUDpB8kgVCnY9ehW#H^XK5o!@UPYT=Hb(5a?)83VXJ}n9S{T-ey&DUBcH$VZzT-xw zwd1lMzhbyd$%B5TgZy?sA+aq7&y?lG{CjukyTb-XHE>^af7JD$SP}!eTZtr?Cbt zY9)40(;P}%R6Q%ITOxFX&jYzV$a|t{dY)Ab8E^5YCrF1_bkDTZi~OPgHkjnDVoT7 zwjywe)a=V=7=H74uqulmGQ#wj`0Z(k%BvdYkf*1EqI<`hs7S8@sJe$)FPXBFy7`>^ zYGRx6O2ER;bi@-HNizw*vc9&UK34iUx^yk@I5A8h+?@F+rUxW+7Nxv>uES``^^lHF z-v>cHA@MS%)kza15i~O}pZC(4y5q!b=>$JH!EMJ-_V_~M6j(e zyToWafql$*Z00+*8lj{pveCi8mcS&>w?3%93~UO*e}5#vXYdUJ)jM27eweQrmrV}w zR;trV^(VlWrg#~z;eLYc>z^Ozl|EgA9y*uMo2@i3EtAO%*t8=XX^un9triS4;ermqNtx? z8nD4!CXt$0P5eglSu!pi<7vJ@GJ9n_UBCX)pwE8KA0ABba#DehMKmH7(sCI!@ClPE z*HUd0i0qy^PH=L^Iy8)LE+dYdMA!DV7<0v0l~i9oV1AeIP#gZKwbHa5I4n1VqrzX{ ziBV^N&dbQ*@eed0rLF^B*TS^?DH*Vk=GULTQt(=CUtAAHv#lyfNNGIm?;79eJO06@ zJzD}aE0Dv9O|Y~K4W6zLDRl)va)V>&_t*Xz@U^ZvH`Swz=2E5i_$K*VWTC2uoL%y7 zU_m-I<)@zCk5R<|g{E{5tTMjgoyuLGZ2wz34E|s+`T`}-gWdufA+IT3wc29dMbAT{ z_ABPEX=ssAq#M7u`^O&Wb?9k~f9d^m-5W32_y%^_w!c&A*f8HT86Rs?Eu-JnYSn4d zvv8gFPySUMOSvj7t&N=MXxz)-L;u=!oSa7=i8jCa=YmNqfK|GW8&;XjJfWojT(~{Y zEX3eUHZzC86u*T%%|DkBG9H+Gd(u4kd_nB%bD;8Rq1db7^Btk__y1fPD58rHx|`w& zhqw*7f6Lr#pBf?gcyZSw*}r!G4Sz*>l@*2y>j20^8j|(N;kZ-f2oV0hE*hX$ZGt1d z8{l{b5F;qy`u|fFfs>C8#`+vM9x(3*JRT-dC2H2Y3T&it{W;B0sBC!WpR1xZ*p=er z+CSsRAL)~G&uN5iDf{_NOa!%=i4O?uQP$?*U|Ke>q7BC(4!E1IEJj$}(kDmn0xXXF z*2xt4642LlO=%dZ{VtQs$4wg2VafTrnHY{(-LN56%g^{F*HRF8u;zk#CUS zo}lsW3aO%bKKWmopTDcZkjtNcyLPnR4z5H7_jO$IbZt1ZhEV&|USL~L#qAx(Hh(Bi zl4DaiL@pBIQ~t7paqTccV;oW8;md|7@%1H*+qsJ8FCi44)W20BayPeWZ{?-eU1uO| z`}}_M*Un3A7J9`UEMI$4cf6jY|F>-Tfw|%R)v6paEFYvI@T3frefR1)sHm8$+YsBP zvrZ;)Gf+m@WH@%3SRMF$bMJ^(UdC8et@#19nsgDDD?{gLV%m%hs4LuC_|JwH^(L1C ztNdK$Fl#nj;^s>MyAdw|(uWpo{=p4iFDl;S!3#WxO$ie6fuuq#3q_y>sy-_MShiSY z#&3aXmhe@z6LtGkcGNU~(bl?W$$k4d;_K{j}3R(`h} zw_{$nm~T{M@!;Doq|)P#1KlQntl=fZ929yh?Uf$dMbRKDtg7ntwpy{GlsrXCEkexe z)zstJTQCZDla1H&rASKS%YaKgjV?f1C?cgNBbje|d$i%}DbGx|5tycTbV=h%AS0h` z2(RUy>*p{c{^`!{omBx}rWEVbyG2~oAeKL2Z(UhyWM@6pDnAOj%oTgXp4nR>5`Trd z`we#$dxt~j_b7ct{%Tc~dM#V#-9A~Zo76w%WT23&&X1svIUBP9y}NCU=AyfO98)n) z4C1n`I`*jSH$+j>g%=yV60G}?kAOiflvwGh$nL)`-*5qXyw3Ca#CX)YCrs!bLu|4a zlp{dCBSYsPu}z8I8-}!Ixca^Q+#9KO*V#%X*4NiVeLIdbb>1S#<+I`9ijxb!_TWiz z4m=cZV!WCm(iBZL@CIpxH)3FP2RwM6kjh~!6^ZspSM0nEX{lcP?VudWH>>&Pg~3c4 zE@h{C{A7XzCXv2*pyt}&UfJYQNq)(^VQ@;t)067ooU zk`xAQF)kD&Wb&wK3w@=tB6*{D3@`7PgJ^d=H&h;MGl7cq^%mjA@$Wwz&DAF%3+0a% zZ4~*J+|q}2^=LV5G>pya5%8g_xtewF8ce3;Q~v;ybR-z92wKy3ZQGXHqwpP)FGExA zcxWg>PNrjPT@*>)=v%&wMr*Wx;@Kn*E^>pL=YEp zBmYqY{P;3=CsNB}iA0}RNJ2zkd<3;qaN4xqwB@bdb)Z!CXsX&mG^Le8$!gX%jSwA( ze>=_ITP>MRe(Py}#(@^30+?*bXs~kLB$YU5FWS?`V4HJsi#oUgO2nRBrC?rRCJSht ziQRG~Hpna0_tt#kXZTBh3=U|?{tcymR@+ut*m(xxx=~1tQAQDQD-^F%1oy4RWlOTUC{Nzp}^W?V|0D*?V_Sowp6P+Bv`Kp zX=WA6RoD}ZEb8KiFP}h<1(8!`psDQoF=+dWw| zpY)s=-Yp!Vz5mpEe4uOKbmxk?u+K!sABW7#WXj1o%jN3@r`I>U`8@K3QKNw{^CFa@QTD*CAzlPI5bDI!R&ojf1tDf&KH zHbbU(A494=La&l_dch%f)qkq^V{K$c4J|&B+Ob>npb+* z_8#GT1?^@%4P{4S|JA_6i{yu#)~uj}kJ2YoVK!Z@F^|nbQsE+WJ6I=BrXB|CT!MhC zha;UY9pS&ec8lujjnI&Nj@1b5Ue)LKSp)~GZtBKfJ;1jp}HcOma%}p-_#+%7HEFuCz~ z|DR^T&=LcB%MgG^QvQhytYidwZ~WKSeg2*)8Ww%4`FUfRV5G2Bj8NG; zr3*kd0f;Zp1I|JK4$^VtTMjggOg;p6#;m@~1Q()G7l+Qx1ZqM7XX-e3ZW;ni^gs>= z4>N-Cq5u`KcF@-5eRdq;+p8 zcpcX5T6N24;fZTfS+#x*7`Dh(ob*gFU#=xc>DW z{*Qzp)H7KrcO3fSqaRI2x|e-~iauL-`rX9ahGfwAA0;$wC9s;BJE>Jobc2Wt!uw@S z;AU%~JYs8!rSXzl*?zS{3X2KSBmGV0Yqwz%dnE;zPhP#sYjVTGDw(G~4U;_r<5Q8i zRRz(Fu;pC5hrD7jg4X8XjKTkHdJ$I%qO<|$bXK%i zQ?yoG0%QgPp=L#YD;M3gy99puMCEA{g2Kq^LuLp3@{2Jxw-@`$eP%(hhv>*{8l7`^J{=N2J2xxCgDjrY++lCB$A^R zp+DY;jE=~NuM%&Ba|mK1C9AUi?v1;fCD&l6M6dQ>H)EP+gl(Mn8JpLFqjeMM4b2nY zy8hZ@pwz?ad9LBCD34?M*w+afz;c6O`0r$p{+;OMxr=(hO3rKkcsox}R z8(fG?s1a#Av}ZD>?R3z-;PO`=$QoU0^s|mtp-x7!hr!qz^<*NZznrnjZZdCTmd85* zR@<_gvndJhwt_^Nn$>67CVF<3?nZpV(Mp4cnkoC-xX^O`@v;ozQSVf zRpaM`;(zrN?+;LvzjAy1_zaPDZ+~x|vfRG9r z+B?#t;S?=Ctleh{aC1Wlk32_HQ|djT+ICLQUOzpL3R84)Iij{gVfjHx$t{W@Lu~Dj znI^?KoWCQjhwhL^OHmOx!tCikwWuXz3zpfcKs%-bM#qEn331_gSD(1Azb%;b=hFgRA z6^_Q(%x)@gQI-2Ip6|_=pw%h$_KH9HWx`sX`o~}9AF~VVh^~EcWQTd1?SaT9YAD8U zW)&qIOGH%FY;U1@^6S#9KB8ClGF|lP%<_|;8XfoIUi4W1n3|78YWLI<6pziw9TAfR z2F}SIKkTNn{;L)u;R>J!6d^S?QP`#-`doS={nYn|2it6Y{PU`BKYs6L04;6XfJe9VQAM6!1$t zSk>7j%jSr!`O)Vr7JbEiJe|jAJ%NlMHvzJN@mkD*zNR97lr9-x)1&I_H$)FL4`VKg zzAH?ADM~qbQiia74I<-sSIFI({06j=w5;B40O8z?&H4G6HmX^7?Bx>VYgQ8n;Aroq zVPr-Id!5L$*Ot=MuV$7k>X1L3mhQAU!o8nRaQ<=4;{k6d=Mz^^TVd8d&8li>?3N!W zvBI#j5zUgEpz8S=0z=_RcLbdSt7cF@nRORW*Dje&DArJGkC)%7{?7LuCcHd&o&q{x zp;kb4MRg>|=qSBFr|MLsWWQ6a0&}ayn!MSe4C7;|q*Y5t>e6FAcAI<}GRAUeCRQkDd zk9IgvIy2rTC${#q5N`qPz5mt4HHP@@M?ZOHo`Uxm{N?q=xd|tq^s{%>d643c!bo9fO)DXJZS^>?dnW(|M?qhd+UuU|lLSC6;*z4gTmo?&23CM7% zl*qg|x<{1HSI1QKyz^A5oJ(UL)i2$PL*av{4OiY|R(RozKe3L3;5+LW-Sr`77z;-s zcdOya)AuwcACuu46I)H^n_KgkavJ>6u&@){TI2Efu1dDBnf!5`^OQ7u zm3Uo1PjQB7)sCIhJfc1ebkEUgN<~QvZQ8cO62m_sW+F$>yLa(>P`h!zUDIQRkureR zLJUML0{f!1CC1kMBM1u_GC3*+B^eG}9PvoWW#dA0y_FKQeja(vILKZ_xBJ<(UxOu+ z%)Rb&Xj9T_>U9L|PtV6_SrO&=4{%b+dd;CjeHKrgkpi12BEZP#vsnwW;+A-4N@fYLn{TWBzb7m{tIRD4KpM@M}>%tQg zY8k7iFh3*SXV_>cgnXmQbI?MwRoW)+`=)%PZNQAkrRBti@?{MB;=pS<>q&^zHmpeJKK5?IX8s>D1SK5(*8f%Gdt{>1Qz6t%`3(juD|86=7k zsI^I4kj~=?X|vd?ia&C-g$#1+I-V5Hw8@&;R-NUP_MwtKSw0lLuKfD*>>Qcs^I!YP z6qekfw4jT~Oen1##rCrUX^6j!#OvpSm#S1OhG>(KgK33hhVNR9k5|H)*P8R;cuVEZ zASs-$@_ci=0?|xO#w^xJWj*tfgWd-fy(4bNRpo13@;=Fqc)W-s><#<)w}?DC)?JX zDO>aD^L6t<9|ON{svj8^N_tbsKXjgJ)V3@LW`Fan`P{{*+~FJCa|y}%3)VCOX}Zh# zXQo8vtLSbhAD3p_vvXGzEExE-Go0JGh&Uxex2MXlwiNOoNy@A6-)Gnfn2qr^J1LWK?Tbv8fhf;q5X?*%oX|-6AUBx{Lm8d?VG07qi~IiSlnuR#s{zL@ z%!|bp#)b6gqXko_;vU#qtR@1Oe#loTu0PMZkcBBT1?#9`nZd1`Qvkj6@p(*;Fnr#| zgb{TxF9Ja;+Nz_TA-9H_c!~7Vr}LgW%OXs>6=C;p{tHHX$f=`rk_Y&T6o=XQ z(IDZ6L)R%}W9&vuT@_^}C+Wb8N)t1DfCwt@%t_9u&Gu1S9x$-NWSrrIlE91mSHYRn zw|lO`=D%?{X>sdndHhra0HBbH8v=ln+)V;Eu8(+tT~*xAZDk~Goqg%BmE_G36V81H zF$Q{meK_>Sb{!aXXp*zaE>L{SS(?rhX1MJbuXG^PdkLUwirwyc8u8`1s0c^It@1%j~Wh_TrgZ zQos#yIrB-EtJ$PVh)yJ`=wx?3<}n?zq$+W2P(w4k7srjI3uB!R%}aR=w8P`|)p<@E zaGqB0oab-f^^J6A;4n4hhMc_n-amg*>z-n0+z(3uz4zX$ z^bXQ{KM6kXch)-p&kxsfg=?P&?CgjB%&Pj9B*EV543Ae?d%_(*je!z$aWvhGPVjRuWi~sIcQ&- zh##%8ZAfwHNB0blI*nqT;L#)7i;iu-iWhpOmgi^p{vxLuJjQYdw&ult`DFiTsh{rm z9nG_AMR@jNs-}83j`sFXkM|A^=2jMMs*1nNCO$n+s_KhHdQ?+a-{AP{I)QycZAKfhveK&C#_G%$F4c3LdoKWDCE zXk@f*dBm+VL!~DC&(dt+M7vdcYVXSQ*y42a>Uic%3;K7tL4B-x&DWIC8o%x=F^o^r zM6I!zzpj-}%j7^=Q%ldE@%Jsswe6h}sDRAzM&wvs``qZt#yYxlw|REBbhN#6vdiJq zcbPPQuY$F~g~{@cR@dMd&HBjcg(U?{U_o`=*WSXOh2#AE{P4;RSY~Kz-#}1Tc4$(1 z!ElR5M114qp;t#{d|9DV`scW~IQ^{eRYRQ>eeJP*CD@^cl;fKG$tnGne^yTr)nyqO8P@2P*!r#H`VvgY$c?b-%EXPmusX zR;|3W1k`QpZ-1V`HEs&{%u7Y4Mw#G*qYJwgK1)`5!qmi?!!Yxf54-x=H5v#)h{Q{X zABrgf^$Pk`8Oj81VF8naOzwk%fcam5(nO=uLa*69_X>G zvsHE1#AGw|;YDL~?vwMdA{%w+`j~fOd03hCLy@Xw9okxW%>4*M1s102o zN_YGXiNRtfJ$^s2miI9>9H$W~djBqh+t@)D#>W2liC4bkfxzuL)!Pj0&eC=m@XKEtGn|!|^6@50EQw5^ zYy94~#Peu!_hSnke3f6q_Wydl6&-QUH@h7wPfI*vq7Lhxj2R4gRuKD~o5hCCz&PEg zJ#PZ3YM?acOJfTzrW+Rp5;grIjh*@b{|{e3$~ErO6Y>_>!d;;{lg!-eYZ`8HGLf4x zn+wz-@84dpQCa8Q8_zx{(iaX@scQI?0Ylar$;_IXV1h$HZNxex0bd=7BBd|&xbk55q;O~ zGAl^#;qH-MUcYJGK;2GK33Pe}mCLnG*YiUE^#`-pnwt~^od7&&9WVbgQ8v@G75;I` z)-1!TKpYU(!`3rh?_14n?Wmh0rtBgS30mJfk@fc!>!adGc-%_65)OQvprh-oow`>k zw))y^BYLx$W5A8;Hm#0k3QM_tvfaSleF?MB7~#s0b&$zK=^D#_MuNMxtQ2>7l<&7H zSLaB7MU7#@t_GVbQAaCe@J<&pOK3JTp1kA0kd5qI(@=|6wF?wu+zXBvVO z(F=)0^i)}u?@@W6xUiikd>4QH&2w(Z((NZf%IrTJ=}8P(>JR)ESgT99>FQSQ&FJH< zIv$`$5*#dTu2i77ZBA8jwjhvv;vcuOUFo0I?>YSD2Y(+u=rzlDWwAeG|B$~XERmU?Onql@9l9d-8UVM2xIM+2;t>6}$ZJ|WG2DcGK&?vUDT1;ki?+G>0LatFHxUva&u#Zh7_2f1dG34+ zl^1#e2wxxKulraNdY2~6Ix_DvKBC9^t#=9gXY{AC(;XwOuUNLHq_X&1qOBOmacS+% zVe#U#z^64aABhl}(Fc4nk-RoNaz1v?_PSYU3mb-?f^7cLrz#tEkN`B%rkbHu!|jX; zgvYVZsTrQ3Ir~&*xHRiY7p|Vc>ap)`eE^BvNex^|z6Cd|Dj=tPA*mnyyEl%HVSq0- zf~0E7OOWnKs_5y{cA8i$Dxd5#Jd-FNKG!^m`S4T4@x2x9lBFV~I2L`09cq8IV-? zLprpEq|xU z)?Mgr7lxx8UcRz0K@tAy*CU_Fevj(Y6svb})kh@seEVb&(VBPrhhb59yI4IO;mKRn z><5ATK63i>Ulx7XB7#417E}`}!oD=!2r7*+EG#4DAwC1-k!8nhCA{g$KzmiO!iZ@8;;OSu( zZ=lF9Qa|kL1Y1A&F85LoqdInnAZg<@-{st>2hJVMniH9jTBbE%5s?-Q*L71DtWq(j z`mJJN6YVKnlvqAtPl?Fa8<-JD>iQV#mZ*YKW73wcg2${wy{L)svP~bbo zgfZYdF16VJtgR_BN};_;Ao#SIiG{phK$}1jHozCnS>AK0vLv!4kfcHNoT`|`Y(D;S z`>!bYx=O9kd169HZ~(T3!2n$DTME$VdpP?h;hFB9BokIFJV%fJ7O+QwZynfN5IeHj zUJEgp-?3MK{II?Xom_yZ^1y?u2FxwsMqsCvmVb--rH;lrM_1#9aY9w=tSHNN*fg8_ z?;nO^BHe0$S6^`Mi5Wl>8slI6k*W+Jd9gcB&(C(7U&A=8;SoAS*Q2n2Y$Hvw6v0y$ zd1zcMI)`1cKfsmurRypY1!((i$1rHve1?tt=g20ljntiR$&b*-Cj!4LGdxgW&M6|Y zQXmPBwxQ3ObzryWeX1U}N9Afz-gQ%Zq~+h(EmPT9wug}Y&w`e^(h#qeV$1LL6m7_d zqsl$h@&{m&vL1=Xa!1`c*19SK$A+59+M*l^Nxmrwmk%_Xrhiq$u$LWbHTzG^7QGQN{MJlA9DGCZF$3$IWS1O0c*`wC1BW zURcq-^r4UGfJi%)bvGw6{Tzt?jQ5H$Gx<~~M9cM+%)%KLHH2F@p_X^5QQA2o$6@(# z;zezDd_ifYHntoDmb=@#g7fL`bh4x__29~B{I_p{x5q)HZUnyunU}3)YOzM;v*S3# ze=5BWNx1s-jo7Cbd{utmGN}#?QZYx}T6`!p@%73vySa(UL7winKq~Sv&=+f>zSZ)C zI8z8|(;RJwADUqf;Z7nX=Btv^BLGb0`-b7Xo5V>bmns6*=x$+4-nfZC6o5(y=C|Oe z5i?$P>&s}eCx!AHhg+P>M>RvzW%@VzR+OB-P=Xa7UI8^=cM@Q~M)k^C{^u5T5ev(u7@- z>J=H`&px#eL2YQ~B7SPX_v;u(b ze`X+-^SfJO?m}Di?}8+`#{1f-b`N600@NA+Jh>nSYrtE#&#MkztfC=bET?_ZC$8sS z71ZHhR&OL-XvN;~SiXfo45dL&M&Cy@jBNwZT>5D32-$hErBZFZD!Cpd2M=Wcy|IR8`&iB|7TzP% zCYW5ktEFhn4&HEfFPz^tPelbkALr5Pl?|V4>cmBW+VdREAL%{*vBN-9JE zzhP15w$g<{xA1Dh1_oKcJ4S9?h3wx)f)!HT`Eo;v9y#V>78eP1 zLOj7t_EF@cz;m{D=vZoh-f6HZ3|d@=wZRMVHAUAibVQ$N3R&DeC+Q`ujF3-1;F@3Z zT?FvCc@n?meQK7^({;B3*Lp^S=O!@CuXw1fhO@p*Zd4TG>|}`mPdLK~Ga$8e?kfVo z1<1E~2=ij%qAAsRTM0E`_u5^z8_ZN%?8=vNQ1x5cs1}?F8DHc4#NAv9{ScaeCd)XJ z(1T4nRB#0IR{isrCdGtiLu)Hy*)S`aLDy{Gw4dGd63b*S8XIDMrauUzImSE6JK$t= z>>Q#A!W(%Wl6H558B<^PyySxH7h%_o!%H8Loa@dDT9S9B+UZa}nP>YU*Em-0*+H{D zL?!`sDz0PfGsl(AeQYIX>SCWi>wzA`p@W|0M1(JBvFEp#>EkPlGMoi7cJf)28#Q6a zv1o#bWBn&L`jJ<{{mzK8&iCAb4BiQbxS^J@I|x|nNm=8;=kDPQr+D%$v!P3lC~BM; zGgc+3)bPe#4w&Z9n8cVsAcKrdity|m(d-A)RD*9Z(ciz}Rm*B~=Q(k&B^})>b5Ig< z4P7z2nz?PP*~qm%Ya5F(j&E4%KeoP42b@XH<4o*s7k>0^gkA$!II z>$e$}&-(Sel`&-(sg}d}l)dnn%(?#zGx4WchA+PM*1Wimr=0S0H)DWeZ4JcWlblb? z0+kvb@+_H$IoYf$0z|HM0)Ffz)@5IiNE&sZ*vW@*J(u` zhyA(>k%lLZ9ewj9$&WWemgGHGJbM-LIv05JlQ4%hhv%+~IlU;N+#{#0%a@Yt-l*V3 zMJa#v`6cnhLOY)b!1nbB0%5?u)#-k(DK$CebZ2|c)u3Oq+WTO!k-zZK>A~V?1joG3 z-x8n8j}P@(XZWp`x$Z81TpE6*{>AYWQQrz-ivZeLH;z0m0Wdslx%~P_ari#>op-1G z!y%f#ld7sbz7#a5uTOc*F9vAlJg^D#pL$h51q_#*KQ*ezDeT78h>ii&NA}VM_pCYZ zX7pUSi^yztO`T8`Etsv~jr|oj|48ZcDQ0rnd|26HRKr zW72O@Z)zaZb#7C0n)6;73?Sxh)U>$PQ(XtoGjiC~bxe){f<^Djr$qA{O%~#zV7>=m zLhDlQR%v8ueJP5J(LQ%k4g%Y61hu5Hw!NZRQt=r#-VtMu2)1{Fp0#S2ys;t5_6IK! zlKJ}45I0c|S-#7BK3;ZKnrpehmPL|%R}+@W%G!nSU_LDdo0utrMknBrV}6tSL|URIeLg6 zIu|0y2sOx>TrFNwDB7<*B8^q+5y^QEZO-R@&S-?+MuOshpPtVx6TYqojeJY92i&d` zn8)a-_n_z?&jy8gpUOdhq9yZ?T+Y%&*kBC$*YUY{V*S*duv52{+&9h{vHkjXd{0Am zovI^p3fa}Q&@EDgySZV6{sy5{UpPSmeGqGl6g{gr z`(OQEk~JGn^=Ll)3nS*#3kvGalCx8OP)x|X=EZyG!3%VHBe4&P9!i!Gr3yXUGB0ta zBY4-z5IX3!OB*ZorVe7z*_z7e^6!G-)AKxaZ8+-n2#^kQYD7n_YU!Gxgn2kNpmuaF zA3B)iAViU8`3CUJF}PlV?@Vkl4omKIVidmwub&!f{o8}DZe64t(a^795H1QxeO{S( zQ);>e=_Ja2XDJxiyjqMhQ26F3{e;DrGB8RZe(U#En}&dsQ0p!NH9gpnx`Z2bL_?l` zHk_x{RH=G| z{do?UGeN^nv)y?yY^sJ$P3X-Xa!_|_#O0*=0_3iH1hAA+TmIWH0^%{p{!3?)Y0$j>`b9#a5aw#-c4&Zk@+$lk(xXetH_v@{Z7K>m;Or>#sI>N z_=tEZ3~Q^_wR!N!B@MAz|4Ouo%H`qr=GACXjl&u{?k=?o);U>K^;52^eBt(`6uv-> zH)?q|4npUmcljSK?>X-zgClT^+17ma z(nYBv`*p3x? zK$`n`Mks)n-(b7Czj##fvsGIT>VuBPd&0<^@u{X8i{S?LcP0u9@0?e;Y2jDEnL}^= zfuYn}Ohi_`d>wwY4FcR+)Us(&C*fQ&T}Z@yJGeOqaf#At&Y|tlFsP&LE%-urqMSN| zD<*vE)C&^OaT1t~VjBV46VTNN1j)jTu84dZP4n1 zw8#tmp0(vs&1LD}L_i@}LxF$pxiim3E%lZ11)t5VfM^iPKFe0+_mL?`D(#LDyKfrF z8Qrec+^fFHU^%IJygW@Eo+2o#?Nzjx>5 zeA6|^BC4Y6^@4RikaXOA;Cp;TDu7xpQ~v2qr>C677Vggru9(`y6udeN?S!c=2>jjf zN(^)uIl29U$f?Xa!>XmNQ-c`hRU8#K<*N_9P6yw!eAu%BYQzO7BX2EJfn>DxB`u>1 zypYSVx=y!YXbjPToL~GsRg-}i3(2zE2vp5J^}gn@Y_WqI+vK8qIe!_=p*Y*xx5Ha+ z2sIF`4mE9kav9PKCJK8Dh$IzeaN%I8UimQPpoRCfpW^pq8$h7Fp7v8*84@85T^wT1 zx9(d82aAt*oPg)-CuSI?e-bJ)%WKD@zG8@AwGd||P}^A(sqjh!*ry(~R=N^%5Wx!; zKq9toYrJ=RLMR8K@d1!b$223#!f)AyQxd%n*+{ZJU!nG0W&q|=N#f#ZUtg>uR7l+5mHM7TI&3&5kYNo=J#{ZPDk_!I;^q2TH5!!QHIs{>KpI- z)o9A!I8#3~;3Of3W!6&%iJn znwmYn#;Bv);7W@CXo5&*Bj+kt$b1LJ+UhL## zq>TDBfCg9lZ=g5*I2&tn#7}=!&W7SS+oJKQ2J@bK)j$5sPpF=MEoaP@Ke#QgznWM$ zNfx?3HC}uo9-%XJaYTPo#{Z=%&=8>5`{3(AFupW(yB!<2m5a=N(P=1UUd}qJ4fbpB zXXbY(Y+X--0DCcOKUeam;aOMeF@r6c*V%`kvv{`mOtbxn(|sF!Df2BCRG=T^A|nT@ z)RI|URD<5<;Ln*oj4{9*CI_vSbD7Bt022)>?V1-9=Gg?0k^x9nvwc_Q+##laKIba$ zO8NxK>5FpVzL^Us()S}MDVYfd;y5b+EDtVRGSig#K#>SZt%b1h`e>3Bv)^+zF)$b9 zxuaq=%cy2$)T(H!SwW0moye6jJ?yz}Pf@Zplf$d`jgH71`re*mmE#T{6JSW~%4m|7 zIq702*)pSlbd{D3TppO5)!cwOTD{O&CRdai&9||SJ(}T&iGlnO5N3^JD+Bi!C z=pm_!5`)G@L9t6Gzy*9D_`}=b$wC3BNA=ra+mRv0k(C;?_cbfYMnAu4RD-2)a3c3( zgBaX>;8zjULr?{7!xJC@G@B65AEYHn#`%a)D|k*P*b?z%0R|upI=ev!eM|?J>I?oq zqei?@GXNdTF>Xj%5*((tUA4JuzHlGJq? z$H`XW0_9{(GOWxnjP#PliyNG^=F#u&FMXc>N3)5}&o@NwLx&5sFJ8Z(fnpcn&xF4oX&j8Rjc~}k=AqifMOitzZ zWc6soD@$st#!*t2JD6pr^R;XtOR-05T2D8o~~;QhSXjU%qU3yB%hm=mziFr?ZXg) zj`ySwS?$*LQZCIh{kFw~2&{UqF=NU~Ow_3~8a?AgNh`-&ewr1674~_b>520!Zto|Q%g?WJF zM0NIS(V8)9xZuko-N+yrWH<`lo=*pXBl8-{k?O`~E4qwd6<&(mMcL!W56Q;a!dnBg zS7(=H4U@NX`^VE`)Mo5UTy|#Y<<;i%%XaPaDKC^Pv761&9}60?BYrNYrjQ(i6Oq%Q zK!0JMkBe&zdJf~DqER5PU@^D5jVK=j>9B^Vbq?p8rH>YjHatAzUO)1dvAp^kZ#(Rl z{%tath#XFnDO@jhIJkK^$Y0s85OhDy?eBk-=|BOZgAJN$+pDIcnqr3gl+}%Q&7RVL zBp@Kr zHQ=@3#}};cha@4X2zhQE;?nh%yA{X}FP<>M_K2e}Xf`*{qyF*IfRE}C#01fZL9X>H zjuSoAzWbo5f+wtkm_2g*Po~3PCr#*gtcq1eIpPCdjdW+18K;G%3Dkh>TnBk+LUz@5 z??-`*2Q@SgK9a>*+b(I;<@GNh(MdY$I}&h2@H1B1^}i9FR(KmP%x09=41S6+nX*54 zPNe#1li4wMwBko_>TaO75NAae!sp2Ga{cHmu$tHEQkZA9~chod{lVZlD;`CIP-_iTJedTGFGe zxcwWS-!#Zzv?f&aVs=@lZ-xQX5&CR9|`^17W2I5zZ@;;FFsKE=HM7Kr_gztBs zuMS*}gv~PY1}@iyB3(+AB6;m7N4wnDXYu(*PBO6!*f^^{`rD16eB#2Sm(ha zOQIbSMWQHqjtE6p=nkQF(jS%PCsVL6u6ki68v~5Tl7#=yOh4w2|4Q{*0O&mAd>GmM z8FD@HtKdl0OMjO#N4Yc+fF83Ds>|(23L;p1av+gT!hoNZ`&Y~p#4+zDiQ5w+YQ4DY z?QI(wTv)k!qok*3bf7EB8v?`qk2`xoapgSA^y7lWZJc_a#^EKyJzO$LcY_)gP|$Wi zQq|lPplYeY@#rADjPMlzIsuQQ=kscEM!;AaA-`|_0;X}F{@CGmA`BR8Xa z984C6{lBAYk0bfgMpheK%Ftl)!JVQ#vFk(clHJHjp5SVR?<(A;(G%B5gk{)nc&ruo zS8Qa34J@y|3XySF%|zFc$9`M1Nd>>50U2aSk%>CuRt!j2DDW*b_2#tYqtC=F5Tdq1DV|GPu$aFa(+~Le(YdZY zQ+-p^USiHVyzcJc!1-Qa^XSSFBg~a;-HGV)2T21lCA%dKb{|=I55>@_!c+`?JDq(v z0<_lr)YeD%kxZj_U7~=*gJ3B~;Vy_?Gc})LwT_HDESNmjEO*eSH3c#=aN72tng0mz z3fJo4Q9+|KM^B(^K1S=5= z`TQpTIk4il9~kehwn7COxDO+`VSqtZ5gb1z;}N60E|jFtWeQc5ZNv$%q*A8_)h@&I zRv>`_;cLvW#;{#R2s3x(tbU9uzb-S9P=4$PE9sJ_CoDq9V3Gn&m%=^Qv ze2r{7Ym%VvP;L74y$mY_UO>B5X2HZG+b$h;wPmR{bVR1m&`>0Kvk319iB^ZlNJPg_ z{C-#r>M2E3xOP-1ZPd}b?FGf3gH{FKyl_Ut4STeV4irBi&w0Wn=^T&D2cpwNB7L{x zHravk6cy=vHF7F0^B6dfC5C8!yC;WF*#cX`^Lel;^bidUbj7R(7qs@SyutcR z6zGYZiH2#|39+xQ+Wv0eWdbK{JPP;c0n4^Qf{ZuZltcmEA9KJ%Ji_YNgmr)RJO4q? z|3Nia@2fm>q%2+iAQGgI(d>Bz={*jT^+-W-y9dc8L(B~&^+eP>J)p5Z9(MZT0ZQb}J&d)V_KOzs-qh6ZZAm9DJM~`&A zeNj+{xmL$x_zI@;EnLdNu?jn~PsUcc3Oe82*=Z{iFyB9FehHpsV?!Fv)4>H6Z)l%O zKqqXe;ys|MJ+e}BH=vVln#rk7MjC&QhHiRn(e*Rii zTkfYw2Z-^lya`0-4p=)EkFtUeICF9K9U|})OyPcwoUoI z2ng;KTsy$S3rs-IT7U1f#X%}`X2 z*JPeZ!@W9Zh(RaHKp|mLj3;Nsf%^aFg!tm8`FZS6aN!<$^q%ySP7Jv&@ElqiyC+x>wdR@!;b8dIKOu)F-UMq%0ia@ADdC?x6tG zjy7WN^Ys7EL#j1=$+6prp$af38-=mp5-Zf=N%{@`!Vi9)eH%&y;~M&~s?m7Tb9z(+ zk;UbSx8)HM_7!+#G zey??!dSs8oJ3gR-%A#u6rvR`+&brwa5MI0PjnIy~C+X*Jn4%OtHGI{6*Pc@I;5>)@M)0 zb*^G@r_-Fa(HNs)>2vLaaJr+E%x0b+PLCz;j!?8@Z~ zU89^aTHAAE3ZUyso|iZNMp@m z)>~FdDg_tRikK7)suicFd=xN;K3Xitrqro4|G!&{>jI3`Lm0h-=GFSAge0JEzqsPQ z%##t;O+JpX0nvutK&>9M?=e)IrCk*<96Nn{ZE7}fH(+joABR@X#b1(nS zVcAi?bJa0(Bfw+ybs<@q!r}!SbZN~07z9ryLL=nI1|I3;Z z13S@x7nfNJ)4%cAM;Q6#OFbnW|LJquKhA%X|4O@Rts7tVtnjY`1$^`p8vPO9y)lxw zf#wv4Fmfqt^mn`gQFfovaJKg@S$0*w>iTvK>m^p(4Ta1VeE|rl-5&7i`$x=oFQjC& zBt)js1w8!Au}553KtHtnAEZu{eRJf@>R;1N!BN$}s+1a5N$Gpnn8sEj$wgq>N zE}psAa{g$oyyPu;jTd0*1jtN8=O$-$wxBMjGF0nBDbO?fs8T70aD6X5>VDMWcqv=Y zDiznxO}xC<#p*G}qrP*YOMBuQ`&8W^&hgJ!z_`}77OvFe^nMIRy?W;8`D2m=KWIW% z_X&)XGe^!YcGgfbkfGT0ERB$N3UUkJ`7c0)c+}@NV!*y}dVbmX10E<|3>oFaeT`c&@obgPYlX9#@+2oCcK4FP>9{ktX;>1j+N)M=exkDLXO% z0_~QEv*OVV(hEe{E`~kT$1I!wMtE7lbFIu%`ZI4IU;-F9?6f<%(=HvlNfJFDM zXt3FKEyH}jSY+x>I`?}p{F}Aba9XY4-{PHjC`X9Fv_A_iQ;Tz{HNSaT2;tsth*dQ7 zn_cQKcryO0U%0ZMLYz^Xi{ffJ$aZLy%x!(KEP7#|k7Tq2w!7zmU9grmvFq>Bst%vc zos|e$A^bfC_qwbf(}R}+a>VOOHETu)_anbaOz`MxkJ`8iLQ=SLxZ(Jw zV==F`*GcnmdpXx)b#SYZt41_Z(04TqNbtn4D;|~Z z>Mc4+N^DGY08bdfH6k^Q_Dnd@;S8ylG6ksnDO+b*ru06aTt7G(&AIVgCJLTGUeJ&+s2()A{>U?RNGorxSuC9Q*8TYHs zlC9AYINQcXO}Mgl90%NUK@+P;th`Vzgk=U+rwfT2c9qJ0m z32!xtY-6V(e8g1f&-5X#moM<1K9&Fnpp{vjZxVU4uXO-RG)yNiOTWv(yf%(P@fmP) zDV~jpGZ4s6B!Ju$j;eX;)Rw*t-m);tdEe;n)U^!w%xO;6^=4NL$IU$4hO_;b1_U3O z5pZ&&&5bor2wP&4Rv}OabUs2`)70xRB=JVieh^ZRJE)Bv%m0BN8*Ue&!F5AVapwmX z7c0;xw{XG`M)r79OkKbJeZKvp#J3W+3!lYF1Z= zIh>1Bi+ixtq{%!3Q89Chs76pw`iE~(&n~@Kt{P_BlkSzFmsN7n!y>JU4sS{Upr1Nh z{2Xaeo7%MOeuU#^HHt=B5>s?mdGU~LuN1z8MW~3U^&*~CR+fqp2u)Z7+!MgPw74AW zhoR1=u&O)zBxvl1O@242KN@^`>m5t;WioD{1~1J5&hpao#5ciT_cBHpX4pPG=N(yn zpTQ457H!fHs3kZna;NwcoO2()igzbMX5M5c<{Z%MWz@`tzj2_bi+x%G^xTG=?VU9| z7xu)&cU6P{G|N@4@Yu=1g{$^XB~J6r0+Lqm;+_=LZ+By;aV_#LMZjD*#7`w^nzUs| zlW3Oh!fex1@JXg`Lzd3H8_9KT9$?drTA`#xgZhr!}x_cehH}N_=YUkzxv5 zWGc;KA*WPV=FNy)pKLTJ-Lndf7sc){NU%*0Dm3w`v!jT>04oH*{kofz zbyrRz9GDs&+#99;+fOB;5e&3NA0*cGdZ-rwH#VFSdgDz83D?=_l+L*;aSOR1_){)O zNONo5a#nzTxR|AQfz3X8n8R1phP@)N%F9cjznIgmg4&yvUF&QB(J=j5Y3lw*rtg$il>VxuWV~ zZiGbYskII6*PpoYx7{9O=j!{0KvjZwtsuLwk+Bj^>{GD#?R40SK ztTA-n-;p(MH}Af}A?kMP4As~v@hp-Rb;##?6At_U4Kie}V#8KdR=f^ad}{}8cwE@M zNyUPF`#zz<=Gz{q00}x};A&mK0~=W$uN0 zbG;&vVR%?9k}tdX&GAP0fcsu&8yh|ZW?!t9HXA+Ksf>)p=XRP`ynM}D?>X@}m?tu< zpVXdWxeP&=!Idnga^$DMD)%o>_H)Mjqo&B4iisjT!fzZY1lG>82=$d;=oKhe=Lkdt zWhUg8!Fc@syxMsOMVH5dJuA7ks-KO0k+0s`#jH(ZzR(YMu=pDL?ay_#&Q}D`f#y-u z3n+WbIM+3k!~I${gASy7Ybf_kfonIFx)XxK`gcFC*3)ReT~##IUdkmI*}*fyO&I)e z^PW1dVrJeKi2>+mK_b%}szRZvc()(?9p2OF`2o2H2qJb2M>hlqr2tu1>YOiHzyR=5 zlaWw1ja6T6)uJz;2<|;$l2Ybli%Br2+Ct@v;CZP+2j4K?s!G5Hs)?mcq)jXDHH`wd zbML)8x1pm!lbQ3-?RV4O;_rSZg^BYHaiqYZ#}WUIoF2=8pUNRX7DN9CNBtez*=N3P ziMGGQ5?jY0tZOr4jpLEuhb?Tv{pmnD^q@GbYeBCy@YTD@%uokh5?AOAl2rJij9nMD z=-|rH_gs)4|PpV&x^C zRYl8%FYx%2!-JJWi!yO>S8Wj!hE?cE&+@LF!aG^qzEG;Qw%BLu!9K6sui!n!YVG z!)D*qv?`eZ)K~swTx^!^w{fff*7QMpP?{i)@LR_0bV&Kb0e8|1@5~gGkkLK+q4K2t zsb!Dlnb_>n8*$d=_hO+WFM4jN;K_pUD6V@hzA=TRoDJ{-p8g%bi6d{X@Y*(myMXRs zheq^REw<`K!HS9*Wh(?1WWTjcb+q8pQhM5~L|b!gwTH6lXLa(@0)}?I4R>^Te34zv zBR|d5bzL9MxyN|sDiwfKqCYlgOf;@`JJ^6SX>f`lx7ipri1ToVuOKQ-JU!w~04D7= zzAt)lPcUX7?2!pMQaOd^RX1Xcu55}$Hy3%FQQ7k8s&KR7m0I$CnR~a0FCR#?DM$o_Lfs-u- z{tMCYJJNS<;$3o*kQ<2FAupMy6Ec0ffA!CbQ=_MsfO_+4l!U$|{RW_3>k2jZai@<{ z7o!L%XuzS2!?h82xEk<5Khd~KEOB(Y<~sM5W6^RlgTCqnee@ijT5}1PXL~tc<|I2a z_(6+g9p=F$!c0Pny)rnpj<@^^Ljl){ma6g};u*^@b=G{I(96MQA=(3-z;#2&_QczW z!=8j}GIMwJ+P1zLOu%sMd~j2uRw$B5jwr(J!qL|4_*OFx%NX`V|9JF*V4nDVMY$1c_8u*q z`x4u?s?YVlIw}fp<#gIPMd0Ml9UsOkq)YRMV%8UVGSj_&+?K@{T7^pRV z-AR9SP1IZzF9H+ZyNpSJw2Y?~&N~@f2KVgK9YGHnpgODdaU2DCCmj~{A52F+;I*#o zd{JEdu-=SMw?96Z7}ppsy5H8i+z-S(QgxLnAO*PN?gs+fRy@Xn|GW6j4gEk%ZWZ$l zr#rap?Bxk{r}s6l*k2#uXdlxO)t=gm;b_Kb3*uHWA%f3*V`~RNd3daY{tG9=>B}#_ zMK+JSs=Rb8emAKk$649O+SKqwuOlZ0&`$EScfT`NPSG8YZ~1~7c?|LfCF`kFss&ZL z&ndNXx>=S+U1SvV@k~#|O_zjrQXBn&w!hDxIL@)zf5y$=F)1#T85RrPuDe*7|5H({ z`wia~+%*JQolnT7iXWK9SpMRxVT|qGlc}Lw*{d+hqTIEHm+3y&COgp_SPzaiVaIi- zpmts^&#~#+Y?ifV0=Od*CtZ`QdH{X470c&rwKpj-77Oq^*sobjGiAQJT%XeY`{$5X zQ&OVXi1~PJq46#6U&wDyd;BWl(?^Kvcum$7x^uHXr7j1@=#BWc7=MjRL(d;q*jh9? zl&g<>-hV#50dp17$4SQXM(>l6d6{TNU&sL>q$=u)%K zArsG}@ExSVCe!IY!t0&bap%;G?`c&dO_)lI=_HN>V?C=D@?~ako_c0yKY4n7^!azC z$mI$5JIu?$mI%ec&eo`?BEBm<6O~K-NX1hcDJ^;m-imDRa)E3w;?gu#Ls^sc_*}5( z;OwdVX!BvuEfkq~J_0II<%Zlpus83upuy7*Cuq^83V~nVlW2VN7Ju0O@-9w^xU;Tz zXGUCmlNj;$E&Q-21ZQa&3oFsd%|q`Wm^N7bv!nxslCmu%%M9*U-QNlWhbBXf1c(X? zszN=0uCU{8qI*WG^~yxX&Jlk;$i_@SLMXXp zMYcmK^Yt8$ z*Yo{)Jzvjv4v<`0!7Qog&UNACYd?g%n~#sjg49m!dnQg)3?^{>R=AOz&p+LsZy^Ph zP!i2oS7XHB?n^}26;ntn%#1gdGY#r5Z!2IFco&wb+PXT`Qfk;;*7wR!lajE(CKAC0 zt)KCFk}F(XO%(qCSt2>U#&_II35ieKK23zoYh$h=yqhxHQXc~5)a_LY0I z-uR-wEZRIYNKnJmO5sL(5IS&vz@AAwZ!dA*ojIi)B>buf$`qkLPD2pA;y8!4ejq;$ z>-u=2tFpfVpiOp5)i-`Cg?uLmSdoIIoQ;*pR-1s{hu{2l11-w`A4`YD;V-HeHo{{$ zw8_LzU8fUzVmvI6|6MM((|G5HE&O}xT`p~enFK2)g8rpE@zkaQzXjOLw32iZYs(3w z8W9b0+~J(k0Evska$=I1@g#-Qio|@}xBEYl80)8AkQH_R?SD;|Qzb}rwL9o(eZy8M zaHrtmis*MiauOkO%;m}}Ddm5kC*Qxs#QJ(Qt3=bRLt5s7`uLU;Ckcvv6Hrg-gXuGb z6)sl7RDZc!31!W&6QKAXz+7fHYI(YF#p*#zbq+AGTqamLFA6?FM7?)ewYn)@&r$_M^( zDY@^Kc?aV;&(V4^@h8hw@dhh^2fE0zZ`$;eUDhZ)E^T1*#MA^_qG#cVI zqn>H^0dnG8Cu@>s>zu6Y0L}aH${ND})6R?q`eLw;2w^{j-Zk;J97C$g+w8^5OBI3n zGb7JojtbHEcwgh^-&5}7B8ZFxWtRMzTb%@hQuBB7$|b6PbZ=9j81o3-fdsVN9dJ^Mk=~B9u=yKgN8x1oqadq z<`UWQ5lttS1(mBdDaA6hsrso?C!sOrfSdVu2Ll*`%1*;%;vF#P%`;iy9M>h;n$hA} z_~<)U;R#Jri#gvE_MCshRp#$GDYaEJ2WzmHS)zYjWk1!NkxO3MwX3>l4?SH;u9Z0e z*O)#sR0~2rKfNnSeDe4RXrQn(ULp2^MYm%fEU!~A6@dViMB@ltD^<*d4K`Ez{d z^G6vtv$-a@lThoFi2sE9eG)pxe-%CXd!qmjNkjTA3nja1}}_!yz?(~7G|#- zJ9ed;vzoQ;gA~Xfi~c9yXvJ{g*}x|nUE?Pa8QEIY$vjX0$SUXz7@hUfdLX7kMio9~ zpQKB*NcDG-KKvhvqsZO;nnjDH>$%KqYE{%kZ3j|yX{}%gW7-dh6}6Xcnfh2Q7tf!= z%5%|2uBrMyyAr^v@4@Emfr0n4ZWr-AZS!W1n@>Fb(%DkAchLYwi<_n~QuRiEEdFg8 zmP)ke5-Xs}Y^^ZcY)P`!+mOUQ>YoPQHNFYjqwWdu_cly9hrnzx*iS<r;GZBk4qV!dh7Y3d zWd1)@N>WvTN10n%$nRp_w6|zV0aVo45b;YKxrocZ(425^MX@w?fu&vj?NL(!07N}8 zhE@y5kL&a;O&OYEMxT~X6&ebO&|d!G_GtHC!ASc#DKbem8v?>n`R_Y!pl=cjmLDU? z*oi%rG))ZBdH)U9)Bb6+JGZpgrZYOf!|v6&A9J}qWJ_O8hOx>~tbb5P2va2n9yXEI z2=hXBEai0wsZAJ7`Bc849`gY6SB^v2_70cM%=e{+Z(grXI6a^i7}HF<#-NJxcxe## zBjA{$dUO?dUgWcKZiICi7c`RWLrhub!I#2*>cr-6+2_q?Yof@(qHvW(d*mg zW`odWQyKp)4Y$ljZ@3%E$#u~B&wHXVyjTHmsa_18-Mu(Jx|YO~QQ=@JC~(izF!RP5 zM^fm?z|n*>OY*ezVRu*8#X#2NJCZy>#hx!Y`+pF2yw8khU`y3A{!Vi9ZX44r?Sb(eow|J5h$hQFhT{|{`|?LO{)NnLhW^6`mzNtY(vOAT&3vNK z@c9b;T9K3EZwm|{RG^yux2Vle+5gx8LuWXo0c39Gv&J(-sPg^?RUeGi)r^+bim)NA zt*XdBJ$xKfzp7s+RRspjQW^7SyE+B86eI9b?nX=+MCo14_Klj4?nl9-IsTvVB$2N< zyTYvjxo1o)+REC7%u^)`D)#LO3EyO8Yq&7^1MbgMe9feD7ykZzZhF#qNslW*d+f-RfTd z$OcQNF$*b1->GJM!2C};$+BPk8`lz2FiEvI!jCw_n_QsY)Pk}X^g)Igy*citZffOX z!*ywCxOIX8i6Q>q6i2{J3}h*s8F%MDR~xDW$a~d5Mq|HDtFkC^sEO`HjD=V`v!lIg znY;2l>UyEFac9t+r0wSR1cqgt+$UoD66#jA*pvu7+SS*7YY-74L-erk4$y;+Ki9kd z_Pk~Ea4i|3Oe=D!?|ALIG#fL>HYGM^`rxgHkL)DWrF6y7&HqDdS+%PEFqGqmE5{=M zoFpW7tA&ez0on(4_^92g?{r)ITNy|yc-yZIheme;~h`wK}fP>eCSZv_Mi#JMU5;xt>AJFvY zt0b-_RW1I0kvX7D1@17~nNS~){6NU`_>etMZ~zi3=uWfSlwj`FORrYb<`aqYta3O& zcj?USOM&zs>(6|K`+fRFWQ@bl?q|!NCzY{ukr|c^ zU8gW~VFv^fY><60HIGBNPutKBD^Du!R6C3&z4o*+4MD<)>WLYz2yo}Hel_C%ljVV- z&qsZ|NmfSrbNXZ)63}(rQuzi2+BE3*2R_1i`eNN?wa6X-jAx4{39TzhVz0k+_}z1ohE2%lz+xLd}gW|sPY+ZY~Y-10a) zsoe6CRaK8(Ay$*>FD$6gt&EKbDgPlnnFPl=jt8ursuP!DKR-mBI(UYL3C zxZfGBwz3K4qc@hOe3e`Lvs1T9jaca*0i?RiL7jh>CRZQw9GFd)Or|#Y28+KvZEL7+ zo-dbS1N;D#RY&DAhgPdhX{-HYe`2MUS{0v2%j*Pn_`ysmo_^SzBdidY5l5G-ycW!C zXtmZfTagcmv4t-qJfJ01vyPK)M$h~u=)s?fH3c2kT@uh4}$;8%6D( z=>e?_&hBEGXfgT}v_)Wv*ZD(x?|=BJsZP0L^tEDfe+(kpUoP;Xm#bfLP%!&wKrlW-ea26cUKUukuc zGW6-?;&6VnT=32fbd1sc66CdC5uvA{lL09wBLh8tL)}9?0ozyuGf3u;*Lzn|Cs zdH0Zbv83mIEKTHV@E!)7lxCI@S-T$ZaHn#pJ^u z;e79bSdMCrxwd^(Ys|fW^r?CJsXk#lg2n=JMOa_c!pK{fN?2!^BzXAcHORQu091(1 z&G|Zl{0=|a+t@A52N^iNlgHAYVP9nmimyV5Tk2(6Vm#w7R-+T&BJAjW7N1^O{CqwmDxD0Qx-yz*bxtokAN{>NB|k!9Dr^C zv#{kEXbv67e_`6c3tK**U1Q z?BLCxfV#{@n`XwGK0g2oVKSo<<39)>Dli4+*;f`Po7%PN4$wRI5o8F>ER|e^w77Zv zcW-8)nMOKED>v5fj_e~`8Iunw4x;g!V%8YCaR$~x0<&T~fVTCtX^E(XR(^e}NQ8wY?VE&)vEZJqr znxEg6dlu2O+x3q$FEKumU^DQ8m_^N2J<#>((^n!UD;d-G-n|sPn5onzMhdWzp0~F7 z3Mer1$Is($6eDL3vLIBV@s(q_@A(tr6z_>s@JM(4-K5RUcK}|PMp5XzBRg=h8>)?# zg98nVF_4I+>HnCUpWk{rb)K~NC3x>xUVsRqY5J*HtA}kJZ%w#(Q zK@0T(KtmS~xH_su6>;BmWn*6RPhB&(BtyT{p9Ifc|=!90J~1iO5+# zlb)&5tt*a`tEHl5V_lAL?)c+WSL$5m;BnLA@PHvQCm?eUAoH|$_LTkEgP%5Wv2L;x z4$L~n*KO^HPfR{z|2a7XSt+%%yUH%Pq7y+(rReRpWA8s;syAq{YSF-%qC1zAP3-UJevE+8NHG@%@?vKrSP$ z8Qf`PgdzfJH&NBok^sL`${@rG@Fyr}VdIU|+dSG)UT3zmEbWXj=fjxf;!MvYnrlQP zqWS)Y!mP6h)v--BPvH2Up63VV{i}6H?K`|&1ag>PWiTPJTUg;IGzB1gmvXHknA*Ju z&mB;IjOsdQMkhB3MV@9Uyf$7)b4;^fK)TkcPw!UMLA!T*;0VDKf1m8)cR2Nxby2|Ca}x7Yb>~B zM08hq=Ld(^@8G42eCpj0?P`l=x1XX;hNQ?ZzDu0P6YF|jIkeWpnA%gj=kL*#<%#8= zGu?K+WRxym&3khI7{AjA-qV|pT4yydOR7Sh*E9X`%9=^oj5ZnG*zF~&Q1ZVWf!nwh zDq7V%mDmX832(S#I{!b)dF;aGZ0)7f9A2 zuUK-ENvW6c7b=x}t!T_hJQ=ZiH#jI%W;6+#T59P%6to0=sPEQQHb|%?S%+8u6UKip zPL>mNA$jJyN@;0W{NWKz$BC>E8>ln(neE{!=^}~1qr|HvaRDn*l(gbVBHrR~6$IR^ zW3dPk19ARH*;y#GR%m1gSq=)s1bu`4rtGAGv4B)w!dwDm!f{4s`KW(#=Lg@_wdJ1` zRkNCy^qXyxy%-(S+oU?#0+6cO?{tvDhH!TEu|`zJVa2sUy(-Xua_`FPDtQOF8heB1 z?5%00azG31tXpthTN>DsB>#iI&mD9eZ4O^p^l6bYKI;S}#|Lj0Bf(jizn%yD=IMPu zrM*ve1t7<*!CkKeU{^FI`NRp z_e|LB1_P2(CVp5J zbyZEy;zb%uHel<9?suYgow&aAMOve@W z%=-WJ`N2D{08_;+b{i{$8^C*IF?{z=RotSO2&p~DWI%nPi{z)M1?N_?+#uyc-y<&F zO?So50ulfF32lII8^|)XTf;(`q}?Rs{chnCLkc|wqkCkUCMrZ*&D>Rgo}!D?dZiN` z9@U5cpc1B-=gEm$d*0bc_&pkzISJ)wH8r~y_VmBe>ji?{=dZv`P3-wLZa}nK-Y(@y z@b~gQ>0db}`AOkbK(LQ zmkXpq-*+t=xcQhS(M>DImB*m^aSHg*N%bGmC7jXl7BzDGWy1}_TFa9g*J7h+%N1*S zT8DlxD+4LSgBUYIFBE#;qX3p_FOS{9A%M_9*uS-`TXE-cTug{MQrE_`G5{4}RD~6x zuSg&*uW=inYTLkdvDCHoW$Qo99-SwmVu-#yDt|DITfStS!n=z(t{x0{Ec@++__V>% zyOgjcE545elbl8X-vg%xyog^0{&sQ2f2!THUm>hkm?o%7fAthtt-bTh>2`7Hz>~v2 z<@7W97;6<=4m z43YK$JBaIIq1R?|Ocr#hMWBXi{wRzE^6q1&M!o5`L~m&_R@xLdD&Y&e?Dh%L^gg{M zYIFEDO9rEGGf4@Pu5kYW?%$|Z7R_p5|5K4JHz<=XanhK(F$EQscE$H$f^?`{78D;Q zlni!rx$=MBVI3mRDIQghT|Mxp_Ke0Z=+-mC{JA;|O%~KzVgHe|4%QQm9rYL z=1Y^5E=lE&yxS=K+bCO3iA^ERf28%?>7CzeW=OFTN8#hI5bgsTd&JfPR-y3U7B=#5uE)-uvcs^H~`Ye4}RF|t@tVYj&)r1@U_?xI&_sC$`C$7Dt)C&nr4wjT zSV}h}pNWX);dJbzLksE+;qy##e9us8_6F9eB=Gr`@O0g}mZ;}iQTrgQ^ZSL7-%Oyan9RO(VJs zJojcVE-Uh3ezF^s7D;}`M03ha2PJ}57|(uu z_eHMxW~3717>luoOJE9$wNP%eY|dil@*EZnFK`(loPf51!dyA<+!rz=Bla-;q0~(f zCz2Ufm_zF(1cRdOUTa}WS#RI5IdtcTOUh);!ZZJIP-Yw(P1K#O4`&Xah02%RrQ=@@ zJ2_k+8|Es|KlC_J<_@RZ3%zhu=siShVSm)v?i=+Zhn&_U!O#Au*KxTeU8DNfKgRig zc>vmMl^l%*{|M~RlDz0Tw5Krmaw`#DiI$|d%=qL`(8{9 zY0BG!NddON1+w9>kea@$&nO)cB0l#4e{C zdLH61e67d^UKUZE$H{{kGcbPn5*k3@Bu94J4d9=7#aG~q^}=$2lCP`pcbWF-52yl1 zu5s_;=wK<*U^t`3KeOEwvM4i`D#f1cPT044FR`R6zkM83RrBoPdLPi6`n@Q|^E*1L zF$j~<1*GtcY^$$B^xgVvvS&eteD=)eF#Xwi25XUGFP^D%=S;cwarqSC#>_iPHz`qj z2fX7(P+Q5Ekz2D5l!Eqf$+y9%i`0wiM8X@ap6bbTkZvB?v`bEoGGtP^9*G)ThdH$I z*ka}e^>0F|OW6f`@Jmxt8N$J&kl6K;@6&R}l`XHzsZP%~-rB%nG$SpO&OaRd`wzV-}>j7V=mD<7o$f^phl$C;X>byU)X{tN)D?;@B`N35?l} z-e#%!Ov~pjbrbJL;UcqmdCz2P!89zk5p1;EZxX6$W98TjeDQ%y*~IOo4|MZTYWd(e zy`JSfLl3kdvI|Hqf5+92ss$HQ&DTgy>_y--HvVpey=qRTl@|iZC$L269j!9)?=W_Y zY2K5lRa|SxIKcIJDJq#flWt%>*M&9DD4SV2p`>y_zyq6522dwcYJr_>h2#!6|1MqK zL3$enQ#%CZ=73ht(c*jH@f1Z3FE?vQLXd$%JUHsJhN%{w{4}tlEh~i=dcEj4CXOv^ z&!~O8ViyG<=;PEWc%S*Y9V&-c{-+wj*{*De$CIPJEL{T-r~jrASbjR}_pIgHz^cYm zrfDtK=JI#dOf(~~j(z$;Hwj!0CkLRQW_r5c57EkB&jJ>Y?!&v@jhKDE_iW0@%ATRt zSVwgEBf+qoqWr~FkDuB*vxY1LPhf`I5CDRdt*FN0)!6bbg<<|I!dL1py_-rDCO#hUQEvzIe6b#1~CmHqp*K#s>&FUn8P5;*}q_y)m@7wP-N7Mjd7 zzq$U)7OW`J8pG}uIyH#J<9r-E87 zg}`(7ocgH#YUGmqOm0tB-%V;OMWtIJzh&a1@4Hile1C;MA&MyjSAV71zZE^?rtD;N zzLFuHL<O~>IV+K(d6%hop_dte5tb@XLk#*aUjOhWJu1OkPzonxdx+_j zIHedlE8?=hl9|=FEvLp0YQTjk-(V{%sYLUV;Xl{5%G>$~-V_skgx0CazszO0$)iGE zPC)e7)~I%6T7Z>=3QG`0PH*;k_KGNWZB^__!rKioI zBNPqkUR4iy_nn}3HXfHuyC5oh-fmP=O5!LEbSa=-M64au)SaG1xRZ<+7gwQCnnqQE zhIEvMLvvveKd7^MiQJ@us~(thyD#>@Kr_!OQAH)5y_Uz*7A|QWUG@7TP}6sL{?<>5 zXEvwY{|T(#B{#D;U&Jkzv#CqvB6H9J7U^JqY_eiMNI=7sug}n0tGz(U!^YT!&c;)d zX?dR%RwU=Bs35#^Hb~MGZwrM0V=60cI+#0iffnceuKYaVCSFF3!fAptbnA{(o}C zJz6>VxcLk;Dj^N9M!W;bzfQtp_y=W!(2d~lQCc@C9{8IqEr|%~9Tlc=07B{ortqJh zGmmSEy!C^v`jLexLFN}p-iE=%8{$(%*Pb5AfeshW39p_4?@a6h_}IpoP+@T zq!qTKuwM4?E^;Q);F|i{Al`eP-391Jv}boK$~GlW?&R%MkK*Uqe!kuP)k+ORn{ zp&}M9kwM=3kM}=HkL!cR^ow2EH)Po}JPLJ-6ic(48gKDKylz5<{>WX4lQ^IkS2X~c zlx7*0YHscI4?me)P#3wU+~NS3Dbz{AwPF>YT)s7n$Ksig<2A9ak5820TW&^TjSAwm zV3?Vu5kF}u`7-{O4NncJV=%{Z#Y7_hcl#%YXl}}2;kOK7cFdI>DtnkG!NR+YF!XfC z>T~(8!v-h0$v4myeA3qGNP2p~Z)+qP(V619+Z^uP?-aX~y>#;~rC`))#6NE-d2M?^ z(*Mj$j~@}Mr)>EK=aKBA95#jvn#i={-rL3sp5jL?J>|GEitm|!F&$fAKiT7Xzid{G%_J$`(A#;``f~qW=byT&I0t!LSETTPDm!oy98@QTWQ{c)C^jY zQMuqb`P^3g%$4JJ+aM?>@|%2QU+HJM5{-0`nE`D)eFcr32xVzmI>R)01h&rXvlg;j zFmJJ57}A}DyNM;-MpsM=B5#As?=MnY5P$OEmQ!&ExIgaCP}N#I$!%sxSy5TftNe6J zu{qd~vw<(W`(4o!7dA)2s)Rz-5UYWf9$ON+91EuujinuXlk0Rsyxv7-yV zCCrmq0nceYfv~CSCb`EGa*CSdJ1!Wp@o*jEbRGV-c$0yXYHbqNYQ!pyRMMs1+vW)w z`@Dia2_YJPuucGi+URRC{-Aw|uyPn!Ku)~@b828h;yw{y8060qW$|@23(fKls=s-b z_%})G=SMCic1?S6C13LDFU>a?sSi%g<;*ShmgHGS2H+Ew) zQn*dhBXd6^HS>1kxV~{A{j-cEGh*vb3wgbls{S?s|7O>n{y+-LfaD1YsujqhyvB}6(L7H?WgMd=6RL#*rr`!802dk4 zVO(LBW9%VBhkPhCswrxqRLOS6q3R}F{5KQSd8g$NaC+BxYfUW*oVOmpb@YW*5$wyx#c3L$K>&9?xYLOL!lhuRpN5>nnQV*pb{_wXm zJW;W0Tb-wtYiWzd8K%RQ*t$fCK1e<57ZRjbibZ(WvvfTl^WWr*`?pPoLT!n`S*rY< zRM>IeiPLhAXg@|&HYMBdRCjMMr`Vx}VsbxTEKKf)WKL)rDpnNDunA*fd(Yg8Te38u zm>}AubjB;E{gwv=iI6XUe|}PdD(w8-1J?ML*B@SM~DSvgZL+R=u0 zMny2CB!_{3C3skpcUzS4n^%CXKXP^4TPmlp0TI+SdcQw!YJ>iTF=NF;i0)%X#3XlK zma=}ePtfqO)VMGZO7%92FFX;~&igFv1t&n>)hyCG$WU7+QAkhZ##>PSc6KqbLZH!l!}VkF zD$@F9U0aE2zuy2bNmR##Y*S%Y-^W@9Azj5H7oYA8{wfKSkmMxnWtxN^C?27uk@S)t^K> z=c76vOabNJRSnkjF{C#EEyNis?56kJfMrF6Pv#Kl>u6u1vXTdnscyw=Rcz`@+!lOH zOv3NS-zhc1r5?*I&bl4zm!`0q68Ol3jp>L?n><+VUiqba%M{*ji)^k4PHzXGmSKRKF<8og+9~yV=qhXs} zK>iwglH>}1+`)9EV5Ze<^on1u{j-0V680YaI5m#J#Ij7)U^+5L~s2jB@=&ezFKP4 z%Nu4&ha~=_lBbIuSo03#}5x)`=S4chpJ4AMg`|!55#(Bbnr{ zh89n{8hwU*&f7S&zu)rWW0ULqbgD!q#lw`!Hd~deSaLmoQnLDqlL&4a4=lzgtG6?h zNyNNSG2>~Zy-h8ql4Pf^HK+R6g^T6XLGLQ+g7WND*HS{A09>K%Z2LZ!GuL-SP-n_g z`|R;WNXW#qNoq3w9=!SsWZ@Ww!Gv=x{9ux9=JA>q1b46vJ6rDqe+@5cK>JRInEM1r+h2atNx4H|j$+frJ)ZCxE? zE*;Xz?yw-pzy%)1q9D$wA`(!mH6{aOxM~8HZ7PKMNR4zb_XowV_=UzSdOC{_(Da&} z{X7e(FJOjcCELKik}53iWD%!6kP2O0+ZKI)+2!yy5~|C&dhpO2Im8LFrDOLzlvjJMztj|j0d%%I3H4G99^J=r}s1A`>Auci5;-h}4Z9jh6U0lN( ztoI;oQA|yiF_jQ+$TAHS_;MV%TpP=5KG2yM;*$Ke~@qG2F|;@3#<9* z0*_>KzK&WW_Is)Nk%x$UqR0RC!(<&BomBc;L!>#M31~DCD^b^`R&sdXR)2v|Y!W=1 zq#CZ);G0Z~l70AnvWug7|Hgqqk!&aR_{3HQ#EavDMEVFz(eE@Be=0UL%XlPi-$6cc zR=^28(Ab6)uxOchn77<(HrJm_%X($dAtL8m7rE3m_8s4plh*yYjAq3| zN7N|ZjAaSM#zKl(5)g`bdIGfbrg9h}K6wV9D?QyY1Kxe?4>fzqv;d;{3e@-mAB^=C z+g{It&$j2bN4H3LP)lb^-B$QBe?ODz9@1+rhccP48g6D7_%#4TeZP4Iz7{F5Q4y}3 zclU%&m{P_+=ppk*{1f&f#U#NF%jn>SU_Ea%!^@T5M*3TJid|yEwP1epXfq}zC2OIn zuEm`Z8KBJ)OUx_LL02X1Cpv|3;|xQ1ecAM8c_~`)(3%#y`spLL02&T})P33dab_5j z8SOYgW257+znX0PNe0M#tcl(JoS0IuZko}lwCOcg-nt&BbYpQB@T%x&k+v_q!o!EY zkvaP6`Qf!;ZFdhh`Bcd!1kXtYB7~peso4wsf!#XfYW)T>VIHS?8cg`H zS4E@$4IGK%k7GkQ$20~3J`!?Mz7u&VfkR~0MIw1NA&S~aH}=v_V22pUs1p~|{W&?K zkC6Tn1#JOmig$dM;&@Pl=pzjN>AJ}s#@|1JzjGSQNSxmY!M&YdU3oB3 z3w8D!?URYemTs~+u&m)SoR}zP@!JfUD?id*OM^0zXEfI%VdMs1*aS3a<$&MzIAH59 zrB$}b*Xrh6qdXEG*pLNfColJi@k(}FxEYOlX3ms8DIG3q;F``Hoj;Pi>{_43>vA2b zdmIkGuDubx5Ti3yI*S-2nGmS|c2l*d1UJVc=WFG(iwFTY? zY?~{7-w~0~wAPoZ>7CABp-GXvzKU8g3c<&)VP#6roDy1hpt0DC!&ZD@*FEWaC>$mI z_c_eWrt(n+A`(*qV|98ieq;yEHz6=)3Db<9S?#5?S-i|D#~(x_wsg-Df|r^_LCq42 zSCWh~^ZS|UYG*Y|Q4&2&bx*jNvWU=m+xGe!JE=4TS^HF~P8WZoM`tx{P(J_+my+15StFi*L7LR7dGFGS$y&dY>B!%99O;0Q@(&B;|ht z*Iz(Q7T#cuxiy2+7JaM*f?NIRZv$^gIB!FA$@IoWyYy(iCak5%aj6& za#pI|jPI{)cC@|>9KQbM3e*_L?#Yn*T0+JzHc%ON9U1m5%+vv7iT%v7=;&3|9kK21 z)SUBH+|}w0PHo$Bnoo?>bn68czz1v)i8D?f!4e2zTvHg?#m0ry0CM{ zB@uw)aZh!OwcU!Iy;>Oqx8_atg%RRq@$xLe9za(r#@S zUrCsmS^LZO$ZxVxe9h_;3pu+uxrZ!2yVEUtA zt*<}TxXaT$tIYNv6_8f$@y$ZH(PEu*zoZICT{^jQ`}FVm7Z!S`92lD^Zzs@3H7-N0 zE`t<_8TH|v{4~~z5V2A>l?^42w&kNzETKQLsn(#lrqB46TU2A4JC#QC_G`Z&%guYs zes6k_Y=ozV;-jaOhF^TqR@$G8ZC##IZ{0-261XwDk`ai= zGVHgY$~LSFgmESlFC~@AWiVpZ#$_NEgZttdC0Q5rnTQZnE^wqf@yEEU2XfahGBTlW z&C1`)H@si*GpTsLZkn<>gm1{Y9KxqC&nhJVVdCW*GiV{AP zyC)TzD}HZmw{XyO#~h7k89k*$uYgj1L-jQVeW>nqyHnTZuBSnHZm|rpHeon>MPK&A zL3}f?dUx^`@(&L>WGn{f@t5qYq0R`|!boy>`s2R^ud`TYH1gD6LWg(rlW||uV#A-` zIYsc6{Tx^2dWQU#ouyn;0xnu6cY)uJz&E_jsTNu(qzxF>x~p(v-?SEweUnUU`Zn<0 zOnX-o$1#93H+8rb+xx6o0HnuSe55_`7wawW z3)bfh7;i0><0|8VZsUr*vdbXGc`-8HhmE!1;O6*!=3i+hvad7p>Ys1|YSo%rN$4`D z?@>0D_f^JKlqO~Rh(}jDDvkyiZeqVG_*fX5=XQ1{CnV5{S|iJ8T#z$1~b=tmWZ zzPW|QrrUIZnrtq`mEL!IIohyruPVdqQ=z_)<1l+5W^BDjp9s7G$`3*0Q#+F72(s^Z z7GqwYk{qW@Na5Xt`0D*fWIFuGkRH3Hd?^jmFX_SE?=%fFlG^))J`!%fFtNlb4L!k7 zc*0e{z#B-32&T@4xCtl+Qp47j=d;y;cO-p?d%~2 z@&?NBIHS$9skT5bag(@5hGVysjf{+lwqwf8`Ac7zv-tu)CTyacFT!3-gY7#DVaSXPDE|Yat_P4=1|e9K z3~-Es>7(V&O--bstlGdZ*YZKI;=W&7MR27&4g~&n#`1I<7~AuC#mT=bw?#in4)UGRDkD^a&!f4P3Ivg;%b9Xm9+#o5obe&igR8dM#1`(Wq z63h6VzmYW-jA1zYUB=n62UZV<&1{%gq@t7;3XWVQUi54(>^yK$y=(4myXlR+nVaCok6J zGyc$#TZ>-&iq|$Ig|(;+mES}eo-c`cRQ&dV*^2%HkhTvgsfu)!k=AG+gpmv)jtIxj zY2iR^k|2E$e)uI(j_6y3-K-v^Q_z5K?oSF}SrsltV%p?83A19?1(|tefav!S#^(85n1rAvd-l5^bUhRV}e!%#2SXN}4mWxO&U8m~(7$jUWciYy=0k#tpQmf>(^ zv|m`Pb~HaYu7Q9cNXpsUazolTPgVxCq<}6%k!wwSFDq)xZWwm3z6!*DJU*CWDP;Uk z27N+yWowK}rXJEWG55IFY)onD@`IqDiK+>{fFM9X<}iPe++XUt2SZrAJO?txa8npg z+|o{$Cx(7KbRB)6yD~pSYjZd`sKw*q*plLlUNqDl!+5TkaBlQ81(C-;;Q;K?VFDWx z{Ds@Ff#48Y@1SWla^xQ|U=KcON#|9(j-&ib-N3?-(@)}2&lD3MGtt0^X?CSTWUmqcxx7sxKW8^lXa;m;TL0Pgzq^dTbx$>u2whKtb8rH>BS zZz!>Og+HD$$#2CyAtSeJIuUXq-Ynq0od$FzKL;=H^mgqrkMO>U+>R#R^u+wwo5@9v zf4}9qC77*qILSH#ojF%NZa{i@PI8A=Q3!yE-S(?$(%w_CjcUnZtfZHuw&m@wX+%r; zgudiY%-LWvv!Z=g2Mj*Rq=mHX%LA`nT%7!#LTViae=elma9H`G7(w#Rxk?gtb-NBM zOxO523apamhmZ1V_&R>WO63| zxKWBLytP8Oj`~K5LeyqLjW3=p%yl=yqkUeD(XpO>eDi&?jHR_c;{I2VgOy9-%q-@R za`NPzgXHEb(zUz=)%B4#+w?-2UgSR8%iKPX~dm`S#O;5BOHKa*Yu3cD2c~Vd}v2B1DpfT z9EBx(i47@KyvAmCae#(H-+SEBu=HDENj^U;^*%j*jg`9o8)+h~pe|IDeyqDC+5iZQ zPOZBcMEavj`R_tPW|@j#Ljjgx<9TR-%z*6$C143JbPh$~Pzo+TFL`x1(wH_+!EeaO zCtMF2|CVtJ7K{wh#_(&4HW&z8D|{8%l<$?E%#X(& zzmlKyfDReZJN+u&tk91kM%@gdK1k>lVa#z#B!+y%Sx4>doW$a4e<(ZbrEbFg|A8)a z&Q>PZhKY`c8O`Q}Ts62Wc zBN6V`*-gpI?Ql(QfR3zphF%Kht?nFsF4F!PN&X9WJT=lpl12IGKQu^{^)O`y0tF6K zjz(w(^NrKT^mVTp`3Z~8=~E0k5ZrZ~kev+X{W-)MGUgCZG~8oY_0+kPVXr~+P*w?- zX0LSjTrb7GBuEIwY^Nz!d2u#5Rg|FL`GL(t=xMp?xRl6cF~0-TQdZUiOP$DexY@u1 z;Dk94F7MNrih2u$Iz_&#t7%=#rydSvyss6^kX*Y#+#+?yfy>_^i3=CStP(FacmapG!Ja^x)uj zj2Ts+*Z(u`?5QBUvOD~MRW^f+Zn(URc`jlV#`t0h#r2Fzl1#VzO; zjtyVpR@83?#E=de!D1C{_OF#eQ>TB#A`-v)2}dDWVF;+aU!r1aV5=19942?n=J?r+ zxZtEfVT>{XT~CeCv?RF*{%Ijq+TOMz{HnUT-}ZhO`e&n>vg+P$W_hG`>mRq0>U{A% zP_zMevi+bn8|GYi`DyYhfp`Z5dM`R~>CLc1Q0T0V3;ZpYUC<-;^B21hJUlkFEw8y# z$>xjLEu&5{Non0fr;qX-6!33>lL?dZ5ELrEy{OkxayYwm^xR=@{+5+lyf`iwS!hES zkaZz?A+zTfCH=_9h)QeA1Eq*f#fkfKgVy4&o+l9HLl@#6N8!t!ODh(cS-P~CR_M4n zrWULgacN}Grh`xQ;5L#n@*4T)^{O$DZbFqQSQUKX5wR@$jUs4(!uR|Y;*+)W<=J>* z+7N25)IF}874+X;XXq~igKY^`2~WaO$mJ=slpyNSR5M?51V_3hhjyBvmY z(pfS|^6N7oB!-l`|6RfRB4X?ktqr^D6A>CeQxb^gMRLIL3(og4h&KE*(BXj&KsuoF zP73bEY})$UIDW*}vvAG!Z`La;Qn%4-qbT9_jD z-z#7Blnvw3jy0*fa#>tdE3xU>p2^kMv;VXlQ>RwA+(dITyPxvBApMzIX==g7v$Uq% zlrf*hx)wYI`G(0`g9~vhEIg2=amu-EUtwcHS1{##iZ%bE_*a>3d)FXRupVd87o@1k zaPU`}&rLoa5wjZNEYzlD$|`C_yQDlP%Hu%26p@VKS$ncYmw^-V5u+D?u))v_PoO1G zh2S|+JNq9Ns>BM+X~+wXdxr)zN)Ad+j_tBhrn+BZAoYuD{uflN*5_$z#w9*?Iapv_H61P^h@RHZyHd^QzXK9SLgxYZ=}Y$Y0D@N z#KtdT5BRC=oxyCNkG)#qbsqoWM0UJjNqtO-Vs~|?zmRLJHiilupJ4ON9L}Gu>nffgK74hR_f$-|nl3mK6nt321>;7a$p->=+CwPV)+q z4Bbd8whiK&rz@?eW_Pb3M}W7RTKiUf9#d%>>%Dj)<|-yGL82MU1UYkb{i@x5rMJ*~ z(Aqo9Oce6{&SaeCX;W)6qb5>d7XP^{$tn2ug$PYcAQB2ShgLP_%YO;Kw!Zf%9bgs0 zDHsF=3qT+D=vllVO4bADP|_cGZifF%tG?DB7N#mbZ_M#A>{;$j8MazL42j0SY>$r} zBGpyL5?AKFPK??yL`Tz{+NoKNO1nIP;}DDfz43*(OoKj)7D{`pbmZ++izKv1PCR-) zrf)aD8t}4PW0zod8cQnRss(?~@rkB(H)Juhe4|BK7L2uRno63qZ&o~+xId(_TlLeN z3q?EC{Wp-vS7S*W8exXrA=Cf+ZIwL+8e1b6OcTZ`wQfeEo!ky~42g6>Fy2&R2wH95 zWQt|z7z*7x4-l<<4K5j%1sOGo_fs^VHOOqrDg%yM+IvZdy%h6B@=E?|Fn&C~Cb)+6 zbLHF5be%?IprE=cdjD5ZI^g~KAo0UMnS}-lqC!duq^?Pwhnz)wLsbF(-?bJE6g+*GnO6$j-=G(#*3?p^AIt_u#?jml%cWAdYYF(R zL0H_sP&D=%f17+qHwu&tx}&~qm~Q_j$FT3Yh@`8y&STv{>Gfw@-|9rSM1+QGT6o!a_8-xCjyquM^a35pi&kKZe(F22;0RS z^%Z`6zvEyx^jc|&ru=ndFxkw6A(41hg`r6i@d28c5wAQfXODIZ{=YsEt_NSx#!Vsyko7GBnSUjX5W4xK3r{0k zpA88^L6-JAWhjB#?r*y;RA1%|_Q;<`L2LMS>sa`h@c|Zv*XZZUNTx_~h)-0dol?>t z>?flUQj<^MIaYz&-$j0x12Ou!>JF2m1zbftXyGlo6L+{;6>* zEi9>ty53KVLK3e`-@@U=@6=L%bJW+jnqKN&DI1s=UD~~G%MaA4>2wZH^a~UHAuT#i zQDlN}l8Tw!26v=6lC4_uone6zegrhYE3Jkq7L7a!uDXx6NPcPI zc8`tbEyKSwO(>000-MpTxBS55Lt_mZqhFYWea3L_{ZJjJrgF+~#eK{Y&_v9Z=ReyA z&~R0KD=kea@l4Lo{?%-TUP~&m-`T+k5ZB5w*+o&Um)`Z&L+Sqnj*2Iwy966Wa?Dfe zSk@-w3YFo*l+c(@gB!zsY(f=|Wh7u8LYJsoqkbp6y+VK^LIs7=|&M#_XcK5vF zH)wI2wg%;3TJ~0ytGu=@thBVhhaxRmsFwdE1GyR^`rkD9Da=1`G44R-rc5kdnC3$% zLO+<`ZxL5R?GOM+{v(Gy7XX5Tp3>kNmPe6Sn+|Cv`JAW4@L6J{HoYGPGfrvShX0%~ z`>!?m0R&mP_$>UZ>e=W<+$N<4Fj6Va7n7Y;ZQs=pL+z%Z3ftm3Dg@RtHzz@b?dhsin5D$%|6yQq+9a&+pq(LGBTZ0tYj$ z2dh_uVa-lpfd<;52(rdrg4I%w-I+(Wd^nth1kn;O=Fp5^?e&zZD$6$9wa-Qr@iC`c zN}Ix5GhJw|oA4NuafHKfJKi~-p5A4I0^U@;pdm`r`aZyMc1`7G3K&2+(ioU7LId_B z)_^jtRn!W&Gn#23c`uX2VhExNDX z%bB>d&Yy6C`V22y4HdqRW?(#I9#^0DKO z4CPJJ0VFGRJsTLK;Om^>2YVb&0QI?Qy!X#>W)g86r z71?#(qu?cNgc-_;wbPRIAMn;W?Q2S1`os<8q0KO#l?$WhflnRI$*ea36O#2U`RyA_ zIIW=z;Gt=sjY99W6_4r=mru|?)6eP}6jev90rmO%e|C#i;)z1z6lv}J)0bjs5A;dC z^~saFdoc!YMH-|;36U;rm+&*Az|Q1tN=K#T$p(#m`IwV>Ay;r8k=va>y!|t!o088u zw{C=j-i2`TOwxKe41%%QC8iPvbQg_Baq~pB%A|Vmgd&i$IA$ZF`PmN{%2-l~=JzA@ z8TWQwIe{uH+k`$!4Jf>o_@~rqWanP%->{BnWTM40$G7D>j~avpB)h@{Y4!r?>O+CXS9oEC z_~dIbQx}l}Rc-cF+1-b)uj1SG&L*M7+>p&@)Th6`r%t46JSi6Y4`;_k2`r}|GzKl5 zD&JXMp2aN%Dw{2ID1$%TiRSeSdfEMmp!Rf*?-wb|4@r7_=}p?`U|7?sx(X=pY`${+ zgZNxveLhtZQvFc5huqZ$y7FhYLs{LOQ!wnr@v? z&(^TM--&4%18#!9uae~RATFYn3I^Ia+XGjkWJ5ad7$!jZZ9Hw~Q&3YXAEoD59$YND z7%KYSraaGElHeRRgni_e|AVvC-tJpL!s~|*V8$}c{kFot`1Ct@FAGCA86GSfo-ls3 z`Jy})(A4YgRzqGwGl1OD}w-EC$x z3CiqcLJpuA2$30fLCKN8wmb`gjcEQEhgx0jh@+pK8oF1tgTRL-yh>Km43@GCla7vg z;TRtqw?R8Fpnpw%!~!znX8tohTtgfMll`!wT}OlF{)68@a5m~NMz~C_R_qbj7NxO{!?>EQiGfWtl{rpkWB(~RVTKf9a zi7UIvs3{MHk|yD$w1bb=qvo%yvhRNIz7egRW<|pq<WaH>a!V5>*gR3WxK(Fb{u@FxNeL9O+}P zE+~=xcHvo1QKRxvMGzYc_BLT>3pNk`i)L@^g0P_N^>?X~1{q*l%&QljXZ0ddfVASJ zCQX3XZO~$HZ@G&Vm4n#AFb{Q~{gvetA1s1kqKRpHo#bK}GfY8yIbyy&>nWwyTR0_J z84)t%S{eB7X6;b2CxRI%fOAJH+_mt@(oLwY^p*x#-MvE8|)$nYIMXp zKi6WGgEn3X^2+^_HH!62v>gHy*JIlvT&s64bU{d)u9on|N_)^Tu&Qdk**-=$0qrV* zf{eZCWh5)#hgz|sAU(OlaM+gCo>s=wkw8wwJkOjne1Y2*h=ehX`sk48@itunlzC?V zTQcYIn;JfQ-`8y!3k@>>2`Q()i}KgSC54s^{wV)$iAHx-=_qobqytET!x3hU;?AwC zk^xu*D*-Z4#Zc2);1JZ7_{L(8J;r_SLObJ=YR8`?3?xd*lFny#TByTs4nOK%c%Uy> zG2C2rV%)YoyhcuJo$r3*XG2?8uI`d((k40V5wy~qOG614JWbaS)_5P3I+%;gh>f}Q zd)HJh1;CNr$|vyrXUf*X+8T;VrxcOmPSc`r<)^HG<%vc&(?Fp@jGr6>0@GquOV>`a z9QwG(ZOib=DFXoaVKn%aE}J~hnY_{~-@Hw)RM?&Rx$rOKqa{qwtTS(;=hu{iSzY!K z{1a2B&kw?9Lm>YBda`e=yL)4mq1pRIXpb9O0cE=q7;DMB{+5Rp?IeBEZa-E#p_3;D^u=U`NMSx&ooEUeXpy&fxuu9E=bj+54g4u7Oz!7yZx=TkYpwDMlHq<94&fD zVeI*x{>KL1eRyHTIt$&HmfcNlD=+V%^M~*+MJq|9m|ItoWN!={4oN2sffrYF_M|QY z#oeHV*O%GQKhMdd(iO(|F|TSu2Yz?*JSiMiU_n-vbU&dU@VN)=;<-*?dj%YF_CZsO zy86h1PKHagb3Ie|hXauk?Xnt0-82yh>t-Md`gN9aqcrH3#GgRr4*9zimk#nN*R|%` zw_hW7;+R2C$lWVz&K<9^YhJsNzE-2<0rsMXrtgDH^zwanr#|(Oc65V@1yJR4#=D71 z9hnRe#ZQ9M#hjv6$$1+Ov><+>4`m2A;d-l!8Ms6lg8$`A5J(OGE6u(vD}QJA}Y!SptOL&n1&P1i2ql4v>}OEc)g6`2X?LYNvbQu3l)%-x!> zK<}Hi*a~xSh1`r)^Y+(sN>=qDR7YF3w9que^ccO}G9}-nKY=x&<+l615Kteb_OoN0 zSy8&MYVEwT&%-)dmP_4nuw+0DH7DG*GI0G1y*pD4jl3$NOZdmR5RDD$>`xHxkCvB< zkMZtA-|y&|M(Jb52N_HAqOPabpEfiB_(x<3ROicK@}fLZDQPgYY2lVxtGdRSSjs!t zRiss3!&%f28(WN?41-QHXO zTX10gh{I48wQ(bsNY()D--!lClqu=vFs+DBxY^&7$2?`OrAC_HGKLw$wcQA(x@SW7 zmFT4yxp~k0oD3FKK|D@IM-(-rPQyd-LwwBH(n(W$(Du4&sqS^_#MRCOE#KCM&iF?z zEIXoukLPYki7jvno;!Va)-vE6v;v0vytQ{p^bqFK@P z^suj+xC2ySnt-vkZ@rsd%s9gp!qb}I}-rdfr0U>m2A>53``v)QjOb>_` z5e47f-kJGNrLRBtn;j!o2!AIQ-*R;yo4NmF*Ju6Q6IIT6BP5jBv$0{Jy{qqU{z#)f zBWB1A6-lW30n-&Su@nM|AiAuy%W(if`IC+_Y;DT<=cLVdU3|-vD+g{S&s9M%WFA=& zF&(g&t+csIuT=i`^r9~^h+8{gj=>|B_86Qn6!v^1z%xt+#aTVmioS%c z%#_L99LG4+SRdU;ps!hToyMNA{m|C_W!ZswX1gp8@MvVT(9WI-9Q(X6jKfjvr`5WO(?YJDw#%Vrq)M=RW zOTn#UASSwz!T+;DWgUCJe(zD&;>fOGFIdCH0%q1NP`W374kmr>8y7gq|a|jKEhW ztPAmNDg=DqU#u}5<{j2BuY*k)bIYr7VFXC38$m2^@jXp(r_ZU6YrDA(2JG;^>F#S4 zjN(xv0DKzHwkG@IcXBy)VI%Bk+RhBGB%O+|To@xQ?U!9br?WmA98YJt_@4h9GI8~} zAPGu{O}Dp#e5Us>l!>{RN-9twN3j)}`7!)pN1o_(ycEhvR{cni(Y1^N0?}1pSiKt6 z7kSkuc(l!rwwF9VIIApk@p=L&af{_w`zY<@KduQ)mk2jBGdBP&p8B#VTu$m=fu9kh zl86Xc2@Wlsgj8*)lVj8-b-s7T0Z9~evU~UbtCo9VZlL73!!dr|aB;;aYW&jY7(959 zp5d;~Z{;;i*}hl|W3%CJ{&X`b_JKp^u8a(u)|E#;y0?nCyyEOtA3Yh6Ik2e7?b#P3 zL>0}ngs6;fT;)|9YkBt~tSemQAae7BJaaM~@U0PA1=qlsta?36AQ)a4h9w?O9lYY4 zq(pPQ_Ny^zM7zMF7^H}>il_>8S@O7s>DPL&balj^!Lz$Sp%Qvej6q?p{iR>`q~I$U zcb+IS4$9>U4h^Z(`WE8H2#A+}ntcp^zYX8l`c7#~^{(o^z2@vQptVJmuQ49p;7m}T zFY^!>si&?U6vRL^hYOOQI=@?94-BR3au(I+GHUr;%b`!Q`zZd>0_bTEIB&YvXZbTw zSY5Y=)QZ|JtpDa#^d1@I>q_T?!O05Js~{}|%)ak)5z94=m=(TTtF=n_ZR~Pk2V$z_ zHf@D`_|M)LUXYH+cWEyH{hOk`51}=V*=F@`&f=b1k!T)>KFE4H!`Bd=J3;5@7W9PG z+v{F*m8=J7wntGdwis9ufN?oLAc1x7EUEbWk{abn>T^^`cwn(QFMeq8PW>%)6k%4+ z?!v$mS(4!Z(&|AZXz`o*btdN+bIs6i@8;dhnTBw7Tz@+tnR8htyJtP!5DoS@PT^`| zAJFC~D*zBFT{`-BNIqK~A{|YJjcU2Y?1L-}UIRL9`!yD>M7@WgPRKb8EPS6)FaLNf0}I5 zXCJMWB&**|YJ!inkgR(3wtIP)nTtl{;n!t%mtSxB1?E+^Qi72 zjK;y#i;2GXjaTK3O{3BNo%C%67ZlcWGS-^a-OZxMohtaX3(9|t^@r5`lDyH1k*U9# zsZ@Q69S@fN4%2R4&(u^$hk&RR_~n;nec2VqoW2s{F2toHH+3sMx(*%p_ZmVdT>(02 z^ZUEf{iCmj4w__0n5v#0 zt7zIBL_|Vwxq>49edlG4X88!qb&`p9T@UOkG&VIC*Zo>`(Mni#Rb|(!UueG0bRgvR-(VsG1l`#4_8-`UCT+x=}?1`AgMv**2({k#?pJZztQ=#u4#%Sp(ww|!3Wm`eBT$2>Rk0r^##S*3TMM!GY@GTpD$g@4bM^~Ki*ALNf(dyVqM_4$Jg`SmD5m=tFK07CGPYv`iQY+N9( zbLSc@Pd6+`NfGVm7E^pqj8G_`cl*MAb7fYZQs?5v)hCZ%T$gk#6g1iVhBX%IR#9N` zFq2!amoR1K-F(}qG=4p0+`oN$EO%%@X9SDS+@YY%J!7tkRaa8?1E6g=vfLkuYhR~2 zH}n5{zy}mZP-M}50bwq~ZTK9`*giKO{r2W&|0TFTdPC8UHwoRSn1!^QMyhEXjEIIY z9B3*-&1=(?9<@9&bSS^b;pqABIN|uB$?nZBW4~jvoNLrEUGHlHxq7uaLN=iTp1tiM zm))IX`w2UdJ4j}8zu7aB!oCfMOWpZvDKA&jz8%<{l=2N@j<{PJ|xZzp@F1b}b3!x%fCI z%(c|Yzcz_FFaB2W4U8&P5f@*xeRq9C;4LVQ@@W@U(fv(0V9M)?Ku2KD4)N;fuoeUU zB3?Z5L?~x`X~~nrt>IIkZP+Nx79^ANVew$QrnK&roOa(!9;Y4v5j-8il9KgmiX*m6 zbmYEIUybvvLxK(=mZ|G>tA6&j9xKnAQM=?$O70Kxk+=~kEFdhz126Mo^7AxoTlc(= zbLRpm@nBHDEb8*CI3Uo5Fc1gcL#@L7*~9ArkZwzV3)MhTVUY zm}lym`cY|*HGc_R8cX@7>%1Z8Xim0q7)&*f>JA+>0=;>AmY^4M^vQ`SDoykr;)H%# zS*5}^?6mJ$Gq2H^Gg60swz@X35fSxfMSqjGLYsbLVL&8!h;re+zxJH2~KlCgB{gqqk=%~c1*30}aF-BP+2 z@AH@&Y`8OA;kER~8WsYJ9|rFT*G6UF_R~j@{TFoL;UAp|wz?&_4u^Y)npe<&nC>5( z1+Fj|L|vK=vxATdAADV)pf*+@bw$8uGL`JSR1)@aQo-Ti7osG#Hr`Mk#4Gv3m{v?) zmjm&p{4n|dQY5Xo_gg)>wKm|anWNX3Zr}(h>{JntOLZQal}{WLF}NvePLLLljVU&oILZde36XxyjI|D-;U)y!X<%(K!n-ws>+i6Ql~N;KU7pJ0sgFd zyv4@zQpB1*TK^OVQy%7ZjvCqW!mU1UT>%~5oO#0rH5}Gnuzg-t-ZbKsfc9*_Ilnn; zNqfOX(gHeGkG`xBfST`ck;22E!A)2O=!+5Tl(eb;v?P1ZP2C`SIT>l~psD|qz&5h+ zo@eD39>TO=UJ=yZ?rEmHIdt@fXNTG`qb)RHbWr+LdY^)%qkU{MyY}RQ mvmdtWF`M)hz-|Elj|FCys$g@fKTzO5rFz;Bttw61*#7}p#O)aX literal 0 HcmV?d00001 diff --git a/doc/user/discussions/img/start_image_discussion.gif b/doc/user/discussions/img/start_image_discussion.gif new file mode 100644 index 0000000000000000000000000000000000000000..43efbf2fbb2502e38fd47cfff2e7f79f15dda1b7 GIT binary patch literal 146627 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 literal 0 HcmV?d00001 diff --git a/doc/user/discussions/img/swipe_view.png b/doc/user/discussions/img/swipe_view.png new file mode 100755 index 0000000000000000000000000000000000000000..82d6e52173cc829e4bda4ca1b638f281d1e980c1 GIT binary patch literal 16483 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 literal 0 HcmV?d00001 diff --git a/doc/user/discussions/img/two_up_view.png b/doc/user/discussions/img/two_up_view.png new file mode 100755 index 0000000000000000000000000000000000000000..d9e90708e873f211420ff497aa8b0c7f76f30548 GIT binary patch literal 61759 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

m*FPTfV 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 NqM#vPBWoG{zW@TNgA@P& literal 0 HcmV?d00001 diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index 23506261f12..5bd326a426f 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -153,6 +153,43 @@ comments in greater detail. ![Discussion comment](img/discussion_comment.png) +## Image discussions + +> [Introduced][ce-14531] in GitLab 10.1. + +Sometimes a discussion is revolved around an image. With image discussions, +you can easily target a specific coordinate of an image and start a discussion +around it. Image discussions are available in merge requests and commit detail views. + +To start an image discussion, hover your mouse over the image. Your mouse pointer +should convert into an icon, indicating that the image is available for commenting. +Simply click anywhere on the image to create a new discussion. + +![Start image discussion](img/start_image_discussion.gif) + +After you click on the image, a comment form will be displayed that would be the start +of your discussion. Once you save your comment, you will see a new badge displayed on +top of your image. This badge represents your discussion. + +>**Note:** +This discussion badge is typically associated with a number that is only used as a visual +reference for each discussion. In the merge request discussion tab, +this badge will be indicated with a comment icon since each discussion will render a new +image section. + +Image discussions also work on diffs that replace an existing image. In this diff view +mode, you can toggle the different view modes and still see the discussion point badges. + +| 2-up | Swipe | Onion Skin | +| :-----------: | :----------: | :----------: | +| ![2-up view](img/two_up_view.png) | ![swipe view](img/swipe_view.png) | ![onion skin view](img/onion_skin_view.png) | + +Image discussions also work well with resolvable discussions. Resolved discussions +on diffs (not on the merge request discussion tab) will appear collapsed on page +load and will have a corresponding badge counter to match the counter on the image. + +![Image resolved discussion](img/image_resolved_discussion.png) + ## Lock discussions > [Introduced][ce-14531] in GitLab 10.1. @@ -163,8 +200,8 @@ in issues or merge requests in these scenarios: - The project maintainer has already resolved the discussion and it is not helpful for continued feedback. The project maintainer has already directed new conversation to newer issues or merge requests. -- The people participating in the discussion are trolling, abusive, or otherwise -being unproductive. +- The people participating in the discussion are trolling, abusive, or otherwise +being unproductive. In these cases, a user with Master permissions or higher in the project can lock (and unlock) an issue or a merge request, using the "Lock" section in the sidebar: @@ -177,7 +214,7 @@ System notes indicate locking and unlocking. ![Discussion lock system notes](img/discussion_lock_system_notes.png) -In a locked issue or merge request, only team members can add new comments and +In a locked issue or merge request, only team members can add new comments and edit existing comments. Non-team members are restricted from adding or editing comments. | Team member | Non-team member | From 4f47fe6a6cbdfded5e1deca16708194f053d9c08 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 11 Oct 2017 15:49:55 +0900 Subject: [PATCH 070/104] Add - before google_api/auth/callback --- config/routes/google_api.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/routes/google_api.rb b/config/routes/google_api.rb index 3fb236d3d51..a119b47c176 100644 --- a/config/routes/google_api.rb +++ b/config/routes/google_api.rb @@ -1,5 +1,7 @@ -namespace :google_api do - resource :auth, only: [], controller: :authorizations do - match :callback, via: [:get, :post] +scope '-' do + namespace :google_api do + resource :auth, only: [], controller: :authorizations do + match :callback, via: [:get, :post] + end end end From 1f14e9c2bc138a8d791af8d7c2a916c8bb4320ff Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 11 Oct 2017 16:36:59 +0900 Subject: [PATCH 071/104] Remove unnecessary TOLLEVEL routes from path_regex.rb --- lib/gitlab/path_regex.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index e68160c8faf..7c02c9c5c48 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -33,7 +33,6 @@ module Gitlab explore favicon.ico files - google_api groups health_check help From 44d3745e51c7433d2560cdd82c9df9653d3577a3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 10 Oct 2017 09:09:01 +0100 Subject: [PATCH 072/104] Moves form related JS modules out of global --- app/assets/javascripts/dispatcher.js | 20 +- app/assets/javascripts/gl_field_error.js | 5 +- app/assets/javascripts/gl_field_errors.js | 30 ++- app/assets/javascripts/gl_form.js | 185 ++++++++-------- app/assets/javascripts/notes.js | 5 +- .../pipeline_schedule_form_bundle.js | 3 +- .../vue_shared/components/markdown/field.vue | 3 +- spec/javascripts/gl_field_errors_spec.js | 206 +++++++++--------- 8 files changed, 224 insertions(+), 233 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index d43eae79730..1de875adf70 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -79,6 +79,8 @@ import initChangesDropdown from './init_changes_dropdown'; import AbuseReports from './abuse_reports'; import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import AjaxLoadingSpinner from './ajax_loading_spinner'; +import GlFieldErrors from './gl_field_errors'; +import GLForm from './gl_form'; import U2FAuthenticate from './u2f/authenticate'; (function() { @@ -230,7 +232,7 @@ import U2FAuthenticate from './u2f/authenticate'; case 'groups:milestones:update': new ZenMode(); new gl.DueDateSelectors(); - new gl.GLForm($('.milestone-form'), true); + new GLForm($('.milestone-form'), true); break; case 'projects:compare:show': new gl.Diff(); @@ -247,7 +249,7 @@ import U2FAuthenticate from './u2f/authenticate'; case 'projects:issues:new': case 'projects:issues:edit': shortcut_handler = new ShortcutsNavigation(); - new gl.GLForm($('.issue-form'), true); + new GLForm($('.issue-form'), true); new IssuableForm($('.issue-form')); new LabelsSelect(); new MilestoneSelect(); @@ -271,7 +273,7 @@ import U2FAuthenticate from './u2f/authenticate'; case 'projects:merge_requests:edit': new gl.Diff(); shortcut_handler = new ShortcutsNavigation(); - new gl.GLForm($('.merge-request-form'), true); + new GLForm($('.merge-request-form'), true); new IssuableForm($('.merge-request-form')); new LabelsSelect(); new MilestoneSelect(); @@ -280,7 +282,7 @@ import U2FAuthenticate from './u2f/authenticate'; break; case 'projects:tags:new': new ZenMode(); - new gl.GLForm($('.tag-form'), true); + new GLForm($('.tag-form'), true); new RefSelectDropdown($('.js-branch-select')); break; case 'projects:snippets:show': @@ -290,17 +292,17 @@ import U2FAuthenticate from './u2f/authenticate'; case 'projects:snippets:edit': case 'projects:snippets:create': case 'projects:snippets:update': - new gl.GLForm($('.snippet-form'), true); + new GLForm($('.snippet-form'), true); break; case 'snippets:new': case 'snippets:edit': case 'snippets:create': case 'snippets:update': - new gl.GLForm($('.snippet-form'), false); + new GLForm($('.snippet-form'), false); break; case 'projects:releases:edit': new ZenMode(); - new gl.GLForm($('.release-form'), true); + new GLForm($('.release-form'), true); break; case 'projects:merge_requests:show': new gl.Diff(); @@ -606,7 +608,7 @@ import U2FAuthenticate from './u2f/authenticate'; new Wikis(); shortcut_handler = new ShortcutsWiki(); new ZenMode(); - new gl.GLForm($('.wiki-form'), true); + new GLForm($('.wiki-form'), true); break; case 'snippets': shortcut_handler = new ShortcutsNavigation(); @@ -657,7 +659,7 @@ import U2FAuthenticate from './u2f/authenticate'; Dispatcher.prototype.initFieldErrors = function() { $('.gl-show-field-errors').each((i, form) => { - new gl.GlFieldErrors(form); + new GlFieldErrors(form); }); }; diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js index 0add7075254..bd63f6f16f0 100644 --- a/app/assets/javascripts/gl_field_error.js +++ b/app/assets/javascripts/gl_field_error.js @@ -54,7 +54,7 @@ const inputErrorClass = 'gl-field-error-outline'; const errorAnchorSelector = '.gl-field-error-anchor'; const ignoreInputSelector = '.gl-field-error-ignore'; -class GlFieldError { +export default class GlFieldError { constructor({ input, formErrors }) { this.inputElement = $(input); this.inputDomElement = this.inputElement.get(0); @@ -159,6 +159,3 @@ class GlFieldError { this.fieldErrorElement.hide(); } } - -window.gl = window.gl || {}; -window.gl.GlFieldError = GlFieldError; diff --git a/app/assets/javascripts/gl_field_errors.js b/app/assets/javascripts/gl_field_errors.js index 4bef60264bb..cf2e6d26608 100644 --- a/app/assets/javascripts/gl_field_errors.js +++ b/app/assets/javascripts/gl_field_errors.js @@ -1,37 +1,35 @@ -/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */ - -import './gl_field_error'; +import GlFieldError from './gl_field_error'; const customValidationFlag = 'gl-field-error-ignore'; -class GlFieldErrors { +export default class GlFieldErrors { constructor(form) { this.form = $(form); this.state = { inputs: [], - valid: false + valid: false, }; this.initValidators(); } - initValidators () { + initValidators() { // register selectors here as needed const validateSelectors = [':text', ':password', '[type=email]'] - .map((selector) => `input${selector}`).join(','); + .map(selector => `input${selector}`).join(','); this.state.inputs = this.form.find(validateSelectors).toArray() - .filter((input) => !input.classList.contains(customValidationFlag)) - .map((input) => new window.gl.GlFieldError({ input, formErrors: this })); + .filter(input => !input.classList.contains(customValidationFlag)) + .map(input => new GlFieldError({ input, formErrors: this })); - this.form.on('submit', this.catchInvalidFormSubmit); + this.form.on('submit', GlFieldErrors.catchInvalidFormSubmit); } /* Neccessary to prevent intercept and override invalid form submit * because Safari & iOS quietly allow form submission when form is invalid * and prevents disabling of invalid submit button by application.js */ - catchInvalidFormSubmit (event) { - const $form = $(event.currentTarget); + static catchInvalidFormSubmit(e) { + const $form = $(e.currentTarget); if (!$form.attr('novalidate')) { if (!event.currentTarget.checkValidity()) { @@ -50,11 +48,9 @@ class GlFieldErrors { }); } - focusOnFirstInvalid () { - const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0]; + focusOnFirstInvalid() { + const firstInvalid = this.state.inputs + .filter(input => !input.inputDomElement.validity.valid)[0]; firstInvalid.inputElement.focus(); } } - -window.gl = window.gl || {}; -window.gl.GlFieldErrors = GlFieldErrors; diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index 4e8141b2956..48d0c12143a 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -1,104 +1,99 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */ -/* global GitLab */ /* global DropzoneInput */ /* global autosize */ import GfmAutoComplete from './gfm_auto_complete'; -window.gl = window.gl || {}; - -function GLForm(form, enableGFM = false) { - this.form = form; - this.textarea = this.form.find('textarea.js-gfm-input'); - this.enableGFM = enableGFM; - // Before we start, we should clean up any previous data for this form - this.destroy(); - // Setup the form - this.setupForm(); - this.form.data('gl-form', this); -} - -GLForm.prototype.destroy = function() { - // Clean form listeners - this.clearEventListeners(); - if (this.autoComplete) { - this.autoComplete.destroy(); +export default class GLForm { + constructor(form, enableGFM = false) { + this.form = form; + this.textarea = this.form.find('textarea.js-gfm-input'); + this.enableGFM = enableGFM; + // Before we start, we should clean up any previous data for this form + this.destroy(); + // Setup the form + this.setupForm(); + this.form.data('gl-form', this); } - return this.form.data('gl-form', null); -}; -GLForm.prototype.setupForm = function() { - var isNewForm; - isNewForm = this.form.is(':not(.gfm-form)'); - this.form.removeClass('js-new-note-form'); - if (isNewForm) { - this.form.find('.div-dropzone').remove(); - this.form.addClass('gfm-form'); - // remove notify commit author checkbox for non-commit notes - gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion')); - this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); - this.autoComplete.setup(this.form.find('.js-gfm-input'), { - emojis: true, - members: this.enableGFM, - issues: this.enableGFM, - milestones: this.enableGFM, - mergeRequests: this.enableGFM, - labels: this.enableGFM, + destroy() { + // Clean form listeners + this.clearEventListeners(); + if (this.autoComplete) { + this.autoComplete.destroy(); + } + this.form.data('gl-form', null); + } + + setupForm() { + const isNewForm = this.form.is(':not(.gfm-form)'); + this.form.removeClass('js-new-note-form'); + if (isNewForm) { + this.form.find('.div-dropzone').remove(); + this.form.addClass('gfm-form'); + // remove notify commit author checkbox for non-commit notes + gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion')); + this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); + this.autoComplete.setup(this.form.find('.js-gfm-input'), { + emojis: true, + members: this.enableGFM, + issues: this.enableGFM, + milestones: this.enableGFM, + mergeRequests: this.enableGFM, + labels: this.enableGFM, + }); + new DropzoneInput(this.form); // eslint-disable-line no-new + autosize(this.textarea); + } + // form and textarea event listeners + this.addEventListeners(); + gl.text.init(this.form); + // hide discard button + this.form.find('.js-note-discard').hide(); + this.form.show(); + if (this.isAutosizeable) this.setupAutosize(); + } + + setupAutosize() { + this.textarea.off('autosize:resized') + .on('autosize:resized', this.setHeightData.bind(this)); + + this.textarea.off('mouseup.autosize') + .on('mouseup.autosize', this.destroyAutosize.bind(this)); + + setTimeout(() => { + autosize(this.textarea); + this.textarea.css('resize', 'vertical'); + }, 0); + } + + setHeightData() { + this.textarea.data('height', this.textarea.outerHeight()); + } + + destroyAutosize() { + const outerHeight = this.textarea.outerHeight(); + + if (this.textarea.data('height') === outerHeight) return; + + autosize.destroy(this.textarea); + + this.textarea.data('height', outerHeight); + this.textarea.outerHeight(outerHeight); + this.textarea.css('max-height', window.outerHeight); + } + + clearEventListeners() { + this.textarea.off('focus'); + this.textarea.off('blur'); + gl.text.removeListeners(this.form); + } + + addEventListeners() { + this.textarea.on('focus', function focusTextArea() { + $(this).closest('.md-area').addClass('is-focused'); + }); + this.textarea.on('blur', function blurTextArea() { + $(this).closest('.md-area').removeClass('is-focused'); }); - new DropzoneInput(this.form); - autosize(this.textarea); } - // form and textarea event listeners - this.addEventListeners(); - gl.text.init(this.form); - // hide discard button - this.form.find('.js-note-discard').hide(); - this.form.show(); - if (this.isAutosizeable) this.setupAutosize(); -}; - -GLForm.prototype.setupAutosize = function () { - this.textarea.off('autosize:resized') - .on('autosize:resized', this.setHeightData.bind(this)); - - this.textarea.off('mouseup.autosize') - .on('mouseup.autosize', this.destroyAutosize.bind(this)); - - setTimeout(() => { - autosize(this.textarea); - this.textarea.css('resize', 'vertical'); - }, 0); -}; - -GLForm.prototype.setHeightData = function () { - this.textarea.data('height', this.textarea.outerHeight()); -}; - -GLForm.prototype.destroyAutosize = function () { - const outerHeight = this.textarea.outerHeight(); - - if (this.textarea.data('height') === outerHeight) return; - - autosize.destroy(this.textarea); - - this.textarea.data('height', outerHeight); - this.textarea.outerHeight(outerHeight); - this.textarea.css('max-height', window.outerHeight); -}; - -GLForm.prototype.clearEventListeners = function() { - this.textarea.off('focus'); - this.textarea.off('blur'); - return gl.text.removeListeners(this.form); -}; - -GLForm.prototype.addEventListeners = function() { - this.textarea.on('focus', function() { - return $(this).closest('.md-area').addClass('is-focused'); - }); - return this.textarea.on('blur', function() { - return $(this).closest('.md-area').removeClass('is-focused'); - }); -}; - -window.gl.GLForm = GLForm; +} diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index cf7322ba1da..2934cfe013c 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -19,6 +19,7 @@ import 'vendor/jquery.atwho'; import AjaxCache from '~/lib/utils/ajax_cache'; import Flash from './flash'; import CommentTypeToggle from './comment_type_toggle'; +import GLForm from './gl_form'; import loadAwardsHandler from './awards_handler'; import './autosave'; import './dropzone_input'; @@ -557,7 +558,7 @@ export default class Notes { */ setupNoteForm(form) { var textarea, key; - new gl.GLForm(form, this.enableGFM); + new GLForm(form, this.enableGFM); textarea = form.find('.js-note-text'); key = [ 'Note', @@ -1152,7 +1153,7 @@ export default class Notes { var targetId = $originalContentEl.data('target-id'); var targetType = $originalContentEl.data('target-type'); - new gl.GLForm($editForm.find('form'), this.enableGFM); + new GLForm($editForm.find('form'), this.enableGFM); $editForm.find('form') .attr('action', postUrl) diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js index 50c725aa3d5..f1cf6e92ef5 100644 --- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js +++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import Translate from '../vue_shared/translate'; +import GlFieldErrors from '../gl_field_errors'; import intervalPatternInput from './components/interval_pattern_input.vue'; import TimezoneDropdown from './components/timezone_dropdown'; import TargetBranchDropdown from './components/target_branch_dropdown'; @@ -39,7 +40,7 @@ document.addEventListener('DOMContentLoaded', () => { gl.timezoneDropdown = new TimezoneDropdown(); gl.targetBranchDropdown = new TargetBranchDropdown(); - gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement); + gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement); setupPipelineVariableList($('.js-pipeline-variable-list')); }); diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index af4187fab46..8c0d9b9cda8 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -1,5 +1,6 @@