From b742ff96fc9e844c9dc17b6b7a7f62b3497594ba Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Fri, 7 Apr 2017 14:26:56 +0200 Subject: [PATCH 001/168] Improve gitaly_address error message Closes gitaly#174 --- lib/gitlab/gitaly_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index bcdf1b1faa8..c69676a1dac 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -15,7 +15,7 @@ module Gitlab end unless URI(address).scheme.in?(%w(tcp unix)) - raise "Unsupported Gitaly address: #{address.inspect}" + raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'" end @addresses[name] = address From 400c328fb3e63ae19f8822a2f02744060f73b7ad Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Mon, 10 Apr 2017 19:20:14 +0000 Subject: [PATCH 002/168] high-priority-features-in-patch-releases --- PROCESS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PROCESS.md b/PROCESS.md index cfa841dc13d..fa9e375b226 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -136,9 +136,11 @@ against the potential negative impact (things breaking without enough time to comfortably find and fix them before the release on the 22nd). When in doubt, we err on the side of _not_ cherry-picking. -For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement +For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement, (e.g. adding a database index or adding `includes` to a query), but not for a new feature, no matter how relatively small or thoroughly tested. +Another exception example is a medium-risk feature such as a navigation change in response to significant negative community feedback from a previous release. + During the feature freeze all merge requests that are meant to go into the upcoming release should have the correct milestone assigned _and_ have the label ~"Pick into Stable" set, so that release managers can find and pick them. From 530d17f1e207fe60d0e854fa57fb02f7c0ea4ca6 Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Mon, 10 Apr 2017 19:23:35 +0000 Subject: [PATCH 003/168] remove stray comma [ci skip] --- PROCESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PROCESS.md b/PROCESS.md index fa9e375b226..3efbb8f83ab 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -136,7 +136,7 @@ against the potential negative impact (things breaking without enough time to comfortably find and fix them before the release on the 22nd). When in doubt, we err on the side of _not_ cherry-picking. -For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement, +For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement (e.g. adding a database index or adding `includes` to a query), but not for a new feature, no matter how relatively small or thoroughly tested. Another exception example is a medium-risk feature such as a navigation change in response to significant negative community feedback from a previous release. From 0c3a0b6560912ad044a214dcfd74bca960a1fd24 Mon Sep 17 00:00:00 2001 From: George Andrinopoulos Date: Sun, 26 Mar 2017 23:16:55 +0300 Subject: [PATCH 004/168] Add keyboard edit shortcut for wiki --- app/assets/javascripts/dispatcher.js | 3 +- app/assets/javascripts/main.js | 1 + app/assets/javascripts/shortcuts_wiki.js | 32 +++++++++++++++++++ app/views/help/_shortcuts.html.haml | 17 ++++++++++ .../projects/wikis/_main_links.html.haml | 2 +- ...eyboard-shortcut-for-editing-wiki-page.yml | 4 +++ doc/workflow/shortcuts.md | 6 ++++ spec/features/projects/wiki/shortcuts_spec.rb | 20 ++++++++++++ 8 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/shortcuts_wiki.js create mode 100644 changelogs/unreleased/29816-create-keyboard-shortcut-for-editing-wiki-page.yml create mode 100644 spec/features/projects/wiki/shortcuts_spec.rb diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f277e1dddc7..d6a019333b7 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -33,6 +33,7 @@ /* global Labels */ /* global Shortcuts */ /* global Sidebar */ +/* global ShortcutsWiki */ import Issue from './issue'; @@ -416,7 +417,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); break; case 'wikis': new gl.Wikis(); - shortcut_handler = new ShortcutsNavigation(); + shortcut_handler = new ShortcutsWiki(); new ZenMode(); new gl.GLForm($('.wiki-form')); break; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index c50ec24c818..4ca4bde3c19 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -35,6 +35,7 @@ import './shortcuts_navigation'; import './shortcuts_find_file'; import './shortcuts_issuable'; import './shortcuts_network'; +import './shortcuts_wiki'; // behaviors import './behaviors/'; diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js new file mode 100644 index 00000000000..f09215fdd6d --- /dev/null +++ b/app/assets/javascripts/shortcuts_wiki.js @@ -0,0 +1,32 @@ +/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, one-var-declaration-per-line, quotes, prefer-arrow-callback, consistent-return, prefer-template, no-mixed-operators */ +/* global Mousetrap */ +/* global ShortcutsNavigation */ + +require('mousetrap'); +require('./shortcuts_navigation'); + +(function() { + var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + this.ShortcutsWiki = (function(superClass) { + extend(ShortcutsWiki, superClass); + + function ShortcutsWiki() { + ShortcutsWiki.__super__.constructor.call(this); + Mousetrap.bind('e', (function(_this) { + return function() { + _this.editWiki(); + return false; + }; + })(this)); + } + + ShortcutsWiki.prototype.editWiki = function() { + var $editBtn; + $editBtn = $('.wiki-edit'); + return gl.utils.visitUrl($editBtn.attr('href')); + }; + return ShortcutsWiki; + })(ShortcutsNavigation); +}).call(window); diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 700c5e61a14..83d03a82a9b 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -105,6 +105,23 @@ %td.shortcut .key esc %td Go back + %tbody + %tr + %th + %th Project File + %tr + %td.shortcut + .key y + %td Go to file permalink + %tbody.hidden-shortcut.merge_requests{ style: 'display:none' } + %tr + %th + %th Wiki pages + %tr + %td.shortcut + .key e + %td Edit wiki page + %tr .col-lg-4 %table.shortcut-mappings %tbody diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 86178257af8..71bc33df463 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -5,5 +5,5 @@ = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do Page history - if can?(current_user, :create_wiki, @project) && @page.latest? - = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do + = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn wiki-edit" do Edit diff --git a/changelogs/unreleased/29816-create-keyboard-shortcut-for-editing-wiki-page.yml b/changelogs/unreleased/29816-create-keyboard-shortcut-for-editing-wiki-page.yml new file mode 100644 index 00000000000..a165c70a6d3 --- /dev/null +++ b/changelogs/unreleased/29816-create-keyboard-shortcut-for-editing-wiki-page.yml @@ -0,0 +1,4 @@ +--- +title: Add keyboard edit shotcut for wiki +merge_request: 10245 +author: George Andrinopoulos diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index f94357abec9..c5b7488be69 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -75,3 +75,9 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | r | Reply (quoting selected text) | | e | Edit issue/merge request | | l | Change label | + +## Wiki pages + +| Keyboard Shortcut | Description | +| ----------------- | ----------- | +| e | Edit wiki page| diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb new file mode 100644 index 00000000000..add4b7f6190 --- /dev/null +++ b/spec/features/projects/wiki/shortcuts_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +feature 'Wiki shortcuts', :feature, :js do + let(:user) { create(:user) } + let(:project) { create(:empty_project, namespace: user.namespace) } + let(:wiki_page) do + WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute + end + + before do + login_as(user) + visit namespace_project_wiki_path(project.namespace, project, wiki_page) + end + + scenario 'Visit edit wiki page using "e" heyboard shortcut' do + find('body').native.send_key('e') + + expect(find('.wiki-page-title')).to have_content('Edit Page') + end +end From b6b83f3c50384c956950dedb6f55739c1605579e Mon Sep 17 00:00:00 2001 From: geoandri Date: Wed, 29 Mar 2017 14:47:34 +0300 Subject: [PATCH 005/168] Refactor shortcuts_wiki.js --- app/assets/javascripts/dispatcher.js | 1 + app/assets/javascripts/shortcuts_wiki.js | 37 +++++-------------- app/views/help/_shortcuts.html.haml | 2 +- spec/features/projects/wiki/shortcuts_spec.rb | 2 +- 4 files changed, 13 insertions(+), 29 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index d6a019333b7..d88b7cd0e17 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -47,6 +47,7 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; import BlobForkSuggestion from './blob/blob_fork_suggestion'; import UserCallout from './user_callout'; import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags'; +import ShortcutsWiki from './shortcuts_wiki'; const ShortcutsBlob = require('./shortcuts_blob'); diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js index f09215fdd6d..00d0d8f3a62 100644 --- a/app/assets/javascripts/shortcuts_wiki.js +++ b/app/assets/javascripts/shortcuts_wiki.js @@ -1,32 +1,15 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, one-var-declaration-per-line, quotes, prefer-arrow-callback, consistent-return, prefer-template, no-mixed-operators */ /* global Mousetrap */ /* global ShortcutsNavigation */ -require('mousetrap'); -require('./shortcuts_navigation'); +class ShortcutsWiki extends ShortcutsNavigation { + constructor() { + super(); + Mousetrap.bind('e', this.editWiki); + } -(function() { - var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; + editWiki() { + this.gl.utils.visitUrl($('.wiki-edit').attr('href')); + } +} - this.ShortcutsWiki = (function(superClass) { - extend(ShortcutsWiki, superClass); - - function ShortcutsWiki() { - ShortcutsWiki.__super__.constructor.call(this); - Mousetrap.bind('e', (function(_this) { - return function() { - _this.editWiki(); - return false; - }; - })(this)); - } - - ShortcutsWiki.prototype.editWiki = function() { - var $editBtn; - $editBtn = $('.wiki-edit'); - return gl.utils.visitUrl($editBtn.attr('href')); - }; - return ShortcutsWiki; - })(ShortcutsNavigation); -}).call(window); +module.exports = ShortcutsWiki; diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 83d03a82a9b..c6b4ba20b63 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -113,7 +113,7 @@ %td.shortcut .key y %td Go to file permalink - %tbody.hidden-shortcut.merge_requests{ style: 'display:none' } + %tbody.hidden-shortcut.wiki{ style: 'display:none' } %tr %th %th Wiki pages diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb index add4b7f6190..c1f6b0cce3b 100644 --- a/spec/features/projects/wiki/shortcuts_spec.rb +++ b/spec/features/projects/wiki/shortcuts_spec.rb @@ -12,7 +12,7 @@ feature 'Wiki shortcuts', :feature, :js do visit namespace_project_wiki_path(project.namespace, project, wiki_page) end - scenario 'Visit edit wiki page using "e" heyboard shortcut' do + scenario 'Visit edit wiki page using "e" keyboard shortcut' do find('body').native.send_key('e') expect(find('.wiki-page-title')).to have_content('Edit Page') From b96abc7d26b5d3b888881644b4fe1595bb39e0a1 Mon Sep 17 00:00:00 2001 From: geoandri Date: Thu, 30 Mar 2017 14:52:36 +0300 Subject: [PATCH 006/168] Minor refactoring in shortcuts_wiki.js --- app/assets/javascripts/shortcuts_wiki.js | 7 +++---- app/views/help/_shortcuts.html.haml | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js index 00d0d8f3a62..bf4479f29c8 100644 --- a/app/assets/javascripts/shortcuts_wiki.js +++ b/app/assets/javascripts/shortcuts_wiki.js @@ -1,15 +1,14 @@ +/* eslint-disable class-methods-use-this*/ /* global Mousetrap */ /* global ShortcutsNavigation */ -class ShortcutsWiki extends ShortcutsNavigation { +export default class ShortcutsWiki extends ShortcutsNavigation { constructor() { super(); Mousetrap.bind('e', this.editWiki); } editWiki() { - this.gl.utils.visitUrl($('.wiki-edit').attr('href')); + gl.utils.visitUrl($('.wiki-edit').attr('href')); } } - -module.exports = ShortcutsWiki; diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index c6b4ba20b63..1aee47b1a71 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -113,7 +113,7 @@ %td.shortcut .key y %td Go to file permalink - %tbody.hidden-shortcut.wiki{ style: 'display:none' } + %tbody.hidden-shortcut.wiki %tr %th %th Wiki pages @@ -121,7 +121,6 @@ %td.shortcut .key e %td Edit wiki page - %tr .col-lg-4 %table.shortcut-mappings %tbody From 07442bf84141cf766d05f014385a2ada4d5b6442 Mon Sep 17 00:00:00 2001 From: geoandri Date: Fri, 31 Mar 2017 12:32:55 +0300 Subject: [PATCH 007/168] Add wikiEdit to constructor --- app/assets/javascripts/shortcuts_wiki.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js index bf4479f29c8..d6d78e70f6d 100644 --- a/app/assets/javascripts/shortcuts_wiki.js +++ b/app/assets/javascripts/shortcuts_wiki.js @@ -1,14 +1,14 @@ -/* eslint-disable class-methods-use-this*/ /* global Mousetrap */ /* global ShortcutsNavigation */ export default class ShortcutsWiki extends ShortcutsNavigation { constructor() { super(); - Mousetrap.bind('e', this.editWiki); + this.$wikiEdit = $('.wiki-edit'); + Mousetrap.bind('e', this.editWiki.bind(this)); } editWiki() { - gl.utils.visitUrl($('.wiki-edit').attr('href')); + gl.utils.visitUrl(this.$wikiEdit.attr('href')); } } From 61355eb72cd1078c4686b2047a4f0e8712e45bb7 Mon Sep 17 00:00:00 2001 From: George Andrinopoulos Date: Sun, 26 Mar 2017 23:16:55 +0300 Subject: [PATCH 008/168] Add keyboard edit shortcut for wiki --- app/views/help/_shortcuts.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 1aee47b1a71..76ea84ff342 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -121,6 +121,7 @@ %td.shortcut .key e %td Edit wiki page + .col-lg-4 %table.shortcut-mappings %tbody From ffd7814b69f8088d2da956cc6aa230b29850de20 Mon Sep 17 00:00:00 2001 From: geoandri Date: Thu, 30 Mar 2017 14:52:36 +0300 Subject: [PATCH 009/168] Minor refactoring in shortcuts_wiki.js --- app/assets/javascripts/shortcuts_wiki.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js index d6d78e70f6d..73aa4160f6f 100644 --- a/app/assets/javascripts/shortcuts_wiki.js +++ b/app/assets/javascripts/shortcuts_wiki.js @@ -1,3 +1,4 @@ +/* eslint-disable class-methods-use-this*/ /* global Mousetrap */ /* global ShortcutsNavigation */ From 3c2596f7f36e660bcd0a21e7c899a76bb133acea Mon Sep 17 00:00:00 2001 From: geoandri Date: Fri, 31 Mar 2017 12:32:55 +0300 Subject: [PATCH 010/168] Add wikiEdit to constructor --- app/assets/javascripts/shortcuts_wiki.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js index 73aa4160f6f..d6d78e70f6d 100644 --- a/app/assets/javascripts/shortcuts_wiki.js +++ b/app/assets/javascripts/shortcuts_wiki.js @@ -1,4 +1,3 @@ -/* eslint-disable class-methods-use-this*/ /* global Mousetrap */ /* global ShortcutsNavigation */ From 4306c0b41a75a0924c03c1c6ed6fac01a992a234 Mon Sep 17 00:00:00 2001 From: geoandri Date: Mon, 10 Apr 2017 11:40:26 +0300 Subject: [PATCH 011/168] Refactor shortcuts_wiki.js --- app/assets/javascripts/main.js | 1 - app/assets/javascripts/shortcuts_wiki.js | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 4ca4bde3c19..c50ec24c818 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -35,7 +35,6 @@ import './shortcuts_navigation'; import './shortcuts_find_file'; import './shortcuts_issuable'; import './shortcuts_network'; -import './shortcuts_wiki'; // behaviors import './behaviors/'; diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js index d6d78e70f6d..425f35f22fb 100644 --- a/app/assets/javascripts/shortcuts_wiki.js +++ b/app/assets/javascripts/shortcuts_wiki.js @@ -1,14 +1,16 @@ +/* eslint-disable class-methods-use-this */ /* global Mousetrap */ /* global ShortcutsNavigation */ +import findAndFollowLink from './shortcuts_dashboard_navigation'; + export default class ShortcutsWiki extends ShortcutsNavigation { constructor() { super(); - this.$wikiEdit = $('.wiki-edit'); - Mousetrap.bind('e', this.editWiki.bind(this)); + Mousetrap.bind('e', this.editWiki); } editWiki() { - gl.utils.visitUrl(this.$wikiEdit.attr('href')); + findAndFollowLink('.wiki-edit'); } } From 8d0c7e8451e76fd7ef55b9e7a2bda3e4eada2dda Mon Sep 17 00:00:00 2001 From: George Andrinopoulos Date: Tue, 11 Apr 2017 20:26:01 +0300 Subject: [PATCH 012/168] Rename wiki-edit class to js-wiki-edit --- app/assets/javascripts/shortcuts_wiki.js | 2 +- app/views/projects/wikis/_main_links.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/shortcuts_wiki.js index 425f35f22fb..8a075062a48 100644 --- a/app/assets/javascripts/shortcuts_wiki.js +++ b/app/assets/javascripts/shortcuts_wiki.js @@ -11,6 +11,6 @@ export default class ShortcutsWiki extends ShortcutsNavigation { } editWiki() { - findAndFollowLink('.wiki-edit'); + findAndFollowLink('.js-wiki-edit'); } } diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 71bc33df463..6a578dbf640 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -5,5 +5,5 @@ = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do Page history - if can?(current_user, :create_wiki, @project) && @page.latest? - = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn wiki-edit" do + = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn js-wiki-edit" do Edit From e809aafdb8da3067f6c4e481378b8404ae700264 Mon Sep 17 00:00:00 2001 From: "Sean Packham (GitLab)" Date: Wed, 12 Apr 2017 13:34:03 +0000 Subject: [PATCH 013/168] Remove link to topics --- doc/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/README.md b/doc/README.md index b6790b4d008..7703c64e152 100644 --- a/doc/README.md +++ b/doc/README.md @@ -6,10 +6,6 @@ All technical content published by GitLab lives in the documentation, including: - [User docs](#user-documentation): general documentation dedicated to regular users of GitLab - [Admin docs](#administrator-documentation): general documentation dedicated to administrators of GitLab instances - [Contributor docs](#contributor-documentation): general documentation on how to develop and contribute to GitLab -- [Topics](topics/index.md): pages organized per topic, gathering all the - resources already published by GitLab related to a specific subject, including - general docs, [technical articles](development/writing_documentation.md#technical-articles), - blog posts and video tutorials. - [GitLab University](university/README.md): guides to learn Git and GitLab through courses and videos. From ffca1cc2ea0f3aeaee5c7aa3afa883489a15af12 Mon Sep 17 00:00:00 2001 From: George Andrinopoulos Date: Wed, 12 Apr 2017 17:58:12 +0300 Subject: [PATCH 014/168] Fix ._shortcuts.html.haml --- app/views/help/_shortcuts.html.haml | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 76ea84ff342..ea8bbe92d86 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -105,23 +105,6 @@ %td.shortcut .key esc %td Go back - %tbody - %tr - %th - %th Project File - %tr - %td.shortcut - .key y - %td Go to file permalink - %tbody.hidden-shortcut.wiki - %tr - %th - %th Wiki pages - %tr - %td.shortcut - .key e - %td Edit wiki page - .col-lg-4 %table.shortcut-mappings %tbody @@ -335,3 +318,11 @@ %td.shortcut .key l %td Change Label + %tbody.hidden-shortcut.wiki{ style: 'display:none' } + %tr + %th + %th Wiki pages + %tr + %td.shortcut + .key e + %td Edit wiki page From 4dda6fcdf3432fd32b83e55f3d99c46068c39904 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 12 Apr 2017 14:56:54 -0500 Subject: [PATCH 015/168] Add docs for recent searches/search history See - https://gitlab.com/gitlab-org/gitlab-ce/issues/27262 - https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10324 --- doc/user/search/img/search_history.gif | Bin 0 -> 265970 bytes doc/user/search/index.md | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 doc/user/search/img/search_history.gif diff --git a/doc/user/search/img/search_history.gif b/doc/user/search/img/search_history.gif new file mode 100644 index 0000000000000000000000000000000000000000..4cfa48ee0ab0be2d348d30d64ad577271306ee10 GIT binary patch literal 265970 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

Tq=t+mIzW=4!UcZV- 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

!l?;8eY;EMZ!6`g|XNTaKS(_7KE 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

KeQgxvN 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%0D--vJbyu*)&F+1du}JF+2(R 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 Date: Wed, 12 Apr 2017 20:14:44 +0000 Subject: [PATCH 016/168] Update index.md --- doc/user/search/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 6a5a19d5548..7f15c177138 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -42,7 +42,7 @@ milestone, and label. ### Search History -You can view past recent searches by clicking on the icon with little clock with a counter-clockwise arrow surrounding it (left of the main search input). Clicking on a search entry, will submit and search that query. +You can view past recent searches by clicking on the icon with little clock with a counter-clockwise arrow surrounding it (left of the main search input). Clicking on a search entry, will submit and search that query. This is available in issues, merge requests, and issue boards. The searches are stored locally in your browser. ![search history](img/search_history.gif) From e114d114e254fe74081011cd63b96909b76864ce Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Wed, 12 Apr 2017 20:17:57 +0000 Subject: [PATCH 017/168] Change heading --- doc/user/search/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 7f15c177138..990640a313f 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -40,7 +40,7 @@ The same process is valid for merge requests. Navigate to your project's **Merge and click **Search or filter results...**. Merge requests can be filtered by author, assignee, milestone, and label. -### Search History +## Search History You can view past recent searches by clicking on the icon with little clock with a counter-clockwise arrow surrounding it (left of the main search input). Clicking on a search entry, will submit and search that query. This is available in issues, merge requests, and issue boards. The searches are stored locally in your browser. From 92bc8a9a74f7dc44d1e45455dc4bc5e2250f4b16 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 12 Apr 2017 16:23:28 -0500 Subject: [PATCH 018/168] Update phrasing via @jschatz1 comments --- doc/user/search/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 990640a313f..61850e38fce 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -42,7 +42,7 @@ milestone, and label. ## Search History -You can view past recent searches by clicking on the icon with little clock with a counter-clockwise arrow surrounding it (left of the main search input). Clicking on a search entry, will submit and search that query. This is available in issues, merge requests, and issue boards. The searches are stored locally in your browser. +You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues, merge requests, and issue boards. Searches are stored locally in your browser. ![search history](img/search_history.gif) From 98559541e89ddcf780c681b38c35c477bce6ffb7 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 12 Apr 2017 19:34:22 -0500 Subject: [PATCH 019/168] Fix award button smiley jumping out of place Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/30863 --- app/assets/stylesheets/pages/notes.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index ad0f2f6efbb..8d6f212ccf5 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -123,6 +123,9 @@ ul.notes { } .note-emoji-button { + position: relative; + line-height: 1; + .fa-spinner { display: none; } @@ -428,7 +431,8 @@ ul.notes { .award-control-icon-positive, .award-control-icon-super-positive { position: absolute; - margin-left: -20px; + top: 0; + left: 0; opacity: 0; } From 0370c7c8389c43138b11c3c5167993d5b025933a Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 7 Apr 2017 00:28:30 -0500 Subject: [PATCH 020/168] Clear emoji search in awards menu after picking emoji Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/27655 --- app/assets/javascripts/awards_handler.js | 53 +++++++++++++------ ...655-clear-emoji-search-after-selection.yml | 4 ++ features/steps/project/issues/award_emoji.rb | 6 +-- spec/javascripts/awards_handler_spec.js | 28 ++++++++-- 4 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 changelogs/unreleased/27655-clear-emoji-search-after-selection.yml diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index ce426741637..424453436a1 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -6,6 +6,7 @@ import { glEmojiTag } from './behaviors/gl_emoji'; import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; +const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || @@ -103,8 +104,9 @@ function AwardsHandler() { const $glEmojiElement = $target.find('gl-emoji'); const $spriteIconElement = $target.find('.icon'); const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name'); + $target.closest('.js-awards-block').addClass('current'); - return this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); + this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); }); } @@ -128,12 +130,12 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { if ($menu.is('.is-visible')) { $addBtn.removeClass('is-active'); $menu.removeClass('is-visible'); - $('#emoji_search').blur(); + $('.js-emoji-menu-search').blur(); } else { $addBtn.addClass('is-active'); this.positionMenu($menu, $addBtn); $menu.addClass('is-visible'); - $('#emoji_search').focus(); + $('.js-emoji-menu-search').focus(); } } else { $addBtn.addClass('is-loading is-active'); @@ -143,7 +145,7 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { this.positionMenu($createdMenu, $addBtn); return setTimeout(() => { $createdMenu.addClass('is-visible'); - $('#emoji_search').focus(); + $('.js-emoji-menu-search').focus(); }, 200); }); } @@ -174,7 +176,7 @@ AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) { const emojiMenuMarkup = `

- +
${frequentlyUsedCatgegory} @@ -474,24 +476,41 @@ AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmoj }; AwardsHandler.prototype.setupSearch = function setupSearch() { - this.registerEventListener('on', $('input.emoji-search'), 'input', (e) => { + const $search = $('.js-emoji-menu-search'); + + this.registerEventListener('on', $search, 'input', (e) => { const term = $(e.target).val().trim(); - // Clean previous search results - $('ul.emoji-menu-search, h5.emoji-search-title').remove(); - if (term.length > 0) { - // Generate a search result block - const h5 = $('
').text('Search results'); - const foundEmojis = this.searchEmojis(term).show(); - const ul = $('
    ').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis); - $('.emoji-menu-content ul, .emoji-menu-content h5').hide(); - $('.emoji-menu-content').append(h5).append(ul); - } else { - $('.emoji-menu-content').children().show(); + this.searchEmojis(term); + }); + + const $menu = $('.emoji-menu'); + this.registerEventListener('on', $menu, transitionEndEventString, (e) => { + if (e.target === e.currentTarget) { + // Clear the search + this.searchEmojis(''); } }); }; AwardsHandler.prototype.searchEmojis = function searchEmojis(term) { + const $search = $('.js-emoji-menu-search'); + $search.val(term); + + // Clean previous search results + $('ul.emoji-menu-search, h5.emoji-search-title').remove(); + if (term.length > 0) { + // Generate a search result block + const h5 = $('
    ').text('Search results'); + const foundEmojis = this.findMatchingEmojiElements(term).show(); + const ul = $('
      ').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis); + $('.emoji-menu-content ul, .emoji-menu-content h5').hide(); + $('.emoji-menu-content').append(h5).append(ul); + } else { + $('.emoji-menu-content').children().show(); + } +}; + +AwardsHandler.prototype.findMatchingEmojiElements = function findMatchingEmojiElements(term) { const safeTerm = term.toLowerCase(); const namesMatchingAlias = []; diff --git a/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml b/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml new file mode 100644 index 00000000000..5fd02696323 --- /dev/null +++ b/changelogs/unreleased/27655-clear-emoji-search-after-selection.yml @@ -0,0 +1,4 @@ +--- +title: Clear emoji search in awards menu after picking emoji +merge_request: +author: diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index a4cfc1fb8c8..dfd0bc13305 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -87,7 +87,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I search "hand"' do - fill_in 'emoji_search', with: 'hand' + fill_in 'emoji-menu-search', with: 'hand' end step 'I see search result for "hand"' do @@ -101,7 +101,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'The search field is focused' do - expect(page).to have_selector('#emoji_search') - expect(page.evaluate_script('document.activeElement.id')).to eq('emoji_search') + expect(page).to have_selector('.js-emoji-menu-search') + expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) end end diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index ea7753c7a1d..abb6884fd24 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -63,7 +63,7 @@ import AwardsHandler from '~/awards_handler'; $emojiMenu = $('.emoji-menu'); expect($emojiMenu.length).toBe(1); expect($emojiMenu.hasClass('is-visible')).toBe(true); - expect($emojiMenu.find('#emoji_search').length).toBe(1); + expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1); return expect($('.js-awards-block.current').length).toBe(1); }); }); @@ -194,16 +194,35 @@ import AwardsHandler from '~/awards_handler'; return expect($thumbsUpEmoji.data("original-title")).toBe('sam'); }); }); - describe('search', function() { - return it('should filter the emoji', function(done) { + describe('::searchEmojis', () => { + it('should filter the emoji', function(done) { return openAndWaitForEmojiMenu() .then(() => { expect($('[data-name=angel]').is(':visible')).toBe(true); expect($('[data-name=anger]').is(':visible')).toBe(true); - $('#emoji_search').val('ali').trigger('input'); + awardsHandler.searchEmojis('ali'); expect($('[data-name=angel]').is(':visible')).toBe(false); expect($('[data-name=anger]').is(':visible')).toBe(false); expect($('[data-name=alien]').is(':visible')).toBe(true); + expect($('.js-emoji-menu-search').val()).toBe('ali'); + }) + .then(done) + .catch((err) => { + done.fail(`Failed to open and build emoji menu: ${err.message}`); + }); + }); + it('should clear the search when searching for nothing', function(done) { + return openAndWaitForEmojiMenu() + .then(() => { + awardsHandler.searchEmojis('ali'); + expect($('[data-name=angel]').is(':visible')).toBe(false); + expect($('[data-name=anger]').is(':visible')).toBe(false); + expect($('[data-name=alien]').is(':visible')).toBe(true); + awardsHandler.searchEmojis(''); + expect($('[data-name=angel]').is(':visible')).toBe(true); + expect($('[data-name=anger]').is(':visible')).toBe(true); + expect($('[data-name=alien]').is(':visible')).toBe(true); + expect($('.js-emoji-menu-search').val()).toBe(''); }) .then(done) .catch((err) => { @@ -211,6 +230,7 @@ import AwardsHandler from '~/awards_handler'; }); }); }); + describe('emoji menu', function() { const emojiSelector = '[data-name="sunglasses"]'; const openEmojiMenuAndAddEmoji = function() { From deb56f21cb5fc7ef9292d4307bc1618af0c1cb29 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Thu, 13 Apr 2017 11:09:20 +0100 Subject: [PATCH 021/168] [ci skip] Use favicon full path --- ...tus_canceled.ico => favicon_status_canceled.ico} | Bin ...tatus_created.ico => favicon_status_created.ico} | Bin ..._status_failed.ico => favicon_status_failed.ico} | Bin ..._status_manual.ico => favicon_status_manual.ico} | Bin ...s_not_found.ico => favicon_status_not_found.ico} | Bin ...tatus_pending.ico => favicon_status_pending.ico} | Bin ...tatus_running.ico => favicon_status_running.ico} | Bin ...tatus_skipped.ico => favicon_status_skipped.ico} | Bin ...tatus_success.ico => favicon_status_success.ico} | Bin ...tatus_warning.ico => favicon_status_warning.ico} | Bin app/assets/javascripts/lib/utils/common_utils.js | 8 ++++---- app/helpers/ci_status_helper.rb | 4 ++++ app/serializers/status_entity.rb | 9 ++++++++- 13 files changed, 16 insertions(+), 5 deletions(-) rename app/assets/images/ci_favicons/{icon_status_canceled.ico => favicon_status_canceled.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_created.ico => favicon_status_created.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_failed.ico => favicon_status_failed.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_manual.ico => favicon_status_manual.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_not_found.ico => favicon_status_not_found.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_pending.ico => favicon_status_pending.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_running.ico => favicon_status_running.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_skipped.ico => favicon_status_skipped.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_success.ico => favicon_status_success.ico} (100%) rename app/assets/images/ci_favicons/{icon_status_warning.ico => favicon_status_warning.ico} (100%) diff --git a/app/assets/images/ci_favicons/icon_status_canceled.ico b/app/assets/images/ci_favicons/favicon_status_canceled.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_canceled.ico rename to app/assets/images/ci_favicons/favicon_status_canceled.ico diff --git a/app/assets/images/ci_favicons/icon_status_created.ico b/app/assets/images/ci_favicons/favicon_status_created.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_created.ico rename to app/assets/images/ci_favicons/favicon_status_created.ico diff --git a/app/assets/images/ci_favicons/icon_status_failed.ico b/app/assets/images/ci_favicons/favicon_status_failed.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_failed.ico rename to app/assets/images/ci_favicons/favicon_status_failed.ico diff --git a/app/assets/images/ci_favicons/icon_status_manual.ico b/app/assets/images/ci_favicons/favicon_status_manual.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_manual.ico rename to app/assets/images/ci_favicons/favicon_status_manual.ico diff --git a/app/assets/images/ci_favicons/icon_status_not_found.ico b/app/assets/images/ci_favicons/favicon_status_not_found.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_not_found.ico rename to app/assets/images/ci_favicons/favicon_status_not_found.ico diff --git a/app/assets/images/ci_favicons/icon_status_pending.ico b/app/assets/images/ci_favicons/favicon_status_pending.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_pending.ico rename to app/assets/images/ci_favicons/favicon_status_pending.ico diff --git a/app/assets/images/ci_favicons/icon_status_running.ico b/app/assets/images/ci_favicons/favicon_status_running.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_running.ico rename to app/assets/images/ci_favicons/favicon_status_running.ico diff --git a/app/assets/images/ci_favicons/icon_status_skipped.ico b/app/assets/images/ci_favicons/favicon_status_skipped.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_skipped.ico rename to app/assets/images/ci_favicons/favicon_status_skipped.ico diff --git a/app/assets/images/ci_favicons/icon_status_success.ico b/app/assets/images/ci_favicons/favicon_status_success.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_success.ico rename to app/assets/images/ci_favicons/favicon_status_success.ico diff --git a/app/assets/images/ci_favicons/icon_status_warning.ico b/app/assets/images/ci_favicons/favicon_status_warning.ico similarity index 100% rename from app/assets/images/ci_favicons/icon_status_warning.ico rename to app/assets/images/ci_favicons/favicon_status_warning.ico diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index e1e6ca25446..5c7baf0a69b 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -364,9 +364,9 @@ }); }; - w.gl.utils.setFavicon = (iconName) => { - if (faviconEl && iconName) { - faviconEl.setAttribute('href', `/assets/${iconName}.ico`); + w.gl.utils.setFavicon = (faviconPath) => { + if (faviconEl && faviconPath) { + faviconEl.setAttribute('href', faviconPath); } }; @@ -382,7 +382,7 @@ dataType: 'json', success: function(data) { if (data && data.icon) { - gl.utils.setFavicon(`ci_favicons/${data.icon}`); + gl.utils.setFavicon(data.favicon); } else { gl.utils.resetFavicon(); } diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 2de9e0de310..b05662bc33d 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -121,4 +121,8 @@ module CiStatusHelper status.respond_to?(:label) && status.respond_to?(:icon) end + + def ci_status_favicon_path(favicon_name) + ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{favicon_name}.ico")) + end end diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb index dfd9d1584a1..8aa894f9fee 100644 --- a/app/serializers/status_entity.rb +++ b/app/serializers/status_entity.rb @@ -1,8 +1,15 @@ class StatusEntity < Grape::Entity include RequestAwareEntity + include CiStatusHelper - expose :icon, :favicon, :text, :label, :group + format_with(:status_favicon_path) do |favicon_name| + ci_status_favicon_path(favicon_name) + end + + expose :icon, :text, :label, :group expose :has_details?, as: :has_details expose :details_path + + expose :favicon, format_with: :status_favicon_path end From c0af1fc4be9df47656d1859de18e18a85c1080a5 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Thu, 13 Apr 2017 12:14:31 +0100 Subject: [PATCH 022/168] Remove helper --- app/helpers/ci_status_helper.rb | 4 ---- app/serializers/status_entity.rb | 5 +++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index b05662bc33d..2de9e0de310 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -121,8 +121,4 @@ module CiStatusHelper status.respond_to?(:label) && status.respond_to?(:icon) end - - def ci_status_favicon_path(favicon_name) - ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{favicon_name}.ico")) - end end diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb index 8aa894f9fee..6862730d9d2 100644 --- a/app/serializers/status_entity.rb +++ b/app/serializers/status_entity.rb @@ -1,6 +1,5 @@ class StatusEntity < Grape::Entity include RequestAwareEntity - include CiStatusHelper format_with(:status_favicon_path) do |favicon_name| ci_status_favicon_path(favicon_name) @@ -11,5 +10,7 @@ class StatusEntity < Grape::Entity expose :has_details?, as: :has_details expose :details_path - expose :favicon, format_with: :status_favicon_path + expose :favicon do |status| + ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{status.favicon}.ico")) + end end From b24cb9985682bc52f299ba2fe2bc5cfbf41fafaf Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Thu, 13 Apr 2017 12:50:03 +0100 Subject: [PATCH 023/168] Updated specs --- app/assets/javascripts/lib/utils/common_utils.js | 2 +- spec/controllers/projects/builds_controller_spec.rb | 2 +- .../projects/merge_requests_controller_spec.rb | 2 +- .../projects/pipelines_controller_spec.rb | 2 +- spec/javascripts/lib/utils/common_utils_spec.js | 13 ++++++------- spec/serializers/build_serializer_spec.rb | 2 +- spec/serializers/pipeline_serializer_spec.rb | 2 +- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 5c7baf0a69b..e3d14f716a3 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -381,7 +381,7 @@ url: pageUrl, dataType: 'json', success: function(data) { - if (data && data.icon) { + if (data && data.favicon) { gl.utils.setFavicon(data.favicon); } else { gl.utils.resetFavicon(); diff --git a/spec/controllers/projects/builds_controller_spec.rb b/spec/controllers/projects/builds_controller_spec.rb index 13208d21918..07f3a3d0062 100644 --- a/spec/controllers/projects/builds_controller_spec.rb +++ b/spec/controllers/projects/builds_controller_spec.rb @@ -60,7 +60,7 @@ describe Projects::BuildsController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq status.favicon + expect(json_response['favicon']).to match status.favicon end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 1739d40ab88..49e94574f57 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -1208,7 +1208,7 @@ describe Projects::MergeRequestsController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq status.favicon + expect(json_response['favicon']).to match status.favicon end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index d8f9bfd0d37..f64daff42ec 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -86,7 +86,7 @@ describe Projects::PipelinesController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq status.favicon + expect(json_response['favicon']).to match status.favicon end end end diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 03f3c206f44..56aabc16382 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -313,7 +313,7 @@ require('~/lib/utils/common_utils'); describe('gl.utils.setFavicon', () => { it('should set page favicon to provided favicon', () => { - const faviconName = 'custom_favicon'; + const faviconPath = '//custom_favicon'; const fakeLink = { setAttribute() {}, }; @@ -321,9 +321,9 @@ require('~/lib/utils/common_utils'); spyOn(window.document, 'getElementById').and.callFake(() => fakeLink); spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => { expect(attr).toEqual('href'); - expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true); + expect(val.indexOf(faviconPath) > -1).toBe(true); }); - gl.utils.setFavicon(faviconName); + gl.utils.setFavicon(faviconPath); }); }); @@ -345,13 +345,12 @@ require('~/lib/utils/common_utils'); describe('gl.utils.setCiStatusFavicon', () => { it('should set page favicon to CI status favicon based on provided status', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`; - const FAVICON_PATH = 'ci_favicons/'; - const FAVICON = 'icon_status_success'; + const FAVICON_PATH = '//icon_status_success'; const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub(); const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub(); spyOn($, 'ajax').and.callFake(function (options) { - options.success({ icon: FAVICON }); - expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON); + options.success({ favicon: FAVICON_PATH }); + expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH); options.success(); expect(spyResetFavicon).toHaveBeenCalled(); options.error(); diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb index 3cc791bca50..ebb70466a73 100644 --- a/spec/serializers/build_serializer_spec.rb +++ b/spec/serializers/build_serializer_spec.rb @@ -38,7 +38,7 @@ describe BuildSerializer do expect(subject[:text]).to eq(status.text) expect(subject[:label]).to eq(status.label) expect(subject[:icon]).to eq(status.icon) - expect(subject[:favicon]).to eq(status.favicon) + expect(subject[:favicon]).to match status.favicon end end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index f6249ab4664..238dfa50013 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -144,7 +144,7 @@ describe PipelineSerializer do expect(subject[:text]).to eq(status.text) expect(subject[:label]).to eq(status.label) expect(subject[:icon]).to eq(status.icon) - expect(subject[:favicon]).to eq(status.favicon) + expect(subject[:favicon]).to match status.favicon end end end From cce27d3aef6bb6a6a41260745fa1a14b82d97fbb Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Thu, 13 Apr 2017 15:27:28 +0000 Subject: [PATCH 024/168] Remove issue boards from recent searches --- doc/user/search/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 61850e38fce..45f443819ec 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -42,7 +42,7 @@ milestone, and label. ## Search History -You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues, merge requests, and issue boards. Searches are stored locally in your browser. +You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues and merge requests. Searches are stored locally in your browser. ![search history](img/search_history.gif) From c922b846fee750dae855b1811d83e27f95b6dcec Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Apr 2017 16:33:19 +0100 Subject: [PATCH 025/168] Only increase the page number for boards when we need to Closes #30902 --- app/assets/javascripts/boards/models/list.js | 9 ++--- spec/javascripts/boards/list_spec.js | 40 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 91e5fb2a666..d83006ebe1a 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -58,7 +58,9 @@ class List { nextPage () { if (this.issuesSize > this.issues.length) { - this.page += 1; + if (this.issues.length / 20 >= 1) { + this.page += 1; + } return this.getIssues(false); } @@ -145,10 +147,7 @@ class List { } updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) { - gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid) - .then(() => { - listFrom.getIssues(false); - }); + gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid); } findIssue (id) { diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index a9d4c6ef76f..7c88a362fd2 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -107,4 +107,44 @@ describe('List model', () => { expect(gl.boardService.moveIssue) .toHaveBeenCalledWith(issue.id, list.id, listDup.id, undefined, undefined); }); + + describe('page number', () => { + beforeEach(() => { + spyOn(list, 'getIssues'); + }); + + it('increase page number if current issue count is more than the page size', () => { + for (let i = 0; i < 30; i+=1) { + list.issues.push(new ListIssue({ + title: 'Testing', + iid: _.random(10000) + i, + confidential: false, + labels: [list.label] + })); + } + list.issuesSize = 50; + + expect(list.issues.length).toBe(30); + + list.nextPage(); + + expect(list.page).toBe(2); + expect(list.getIssues).toHaveBeenCalled(); + }); + + it('does not increase page number if issue count is less than the page size', () => { + list.issues.push(new ListIssue({ + title: 'Testing', + iid: _.random(10000), + confidential: false, + labels: [list.label] + })); + list.issuesSize = 2; + + list.nextPage(); + + expect(list.page).toBe(1); + expect(list.getIssues).toHaveBeenCalled(); + }); + }); }); From 5a1e00c0d01ec540cc422c503f38ad203bd8fb6d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Apr 2017 17:16:41 +0100 Subject: [PATCH 026/168] Disable recent search on issue boards Closes #30919 --- app/views/shared/issuable/_search_bar.html.haml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index b447996a8ab..f1350169bbe 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -12,13 +12,14 @@ class: "check_all_issues left" .issues-other-filters.filtered-search-wrapper .filtered-search-box - = dropdown_tag(content_tag(:i, '', class: 'fa fa-history'), - options: { wrapper_class: "filtered-search-history-dropdown-wrapper", - toggle_class: "filtered-search-history-dropdown-toggle-button", - dropdown_class: "filtered-search-history-dropdown", - content_class: "filtered-search-history-dropdown-content", - title: "Recent searches" }) do - .js-filtered-search-history-dropdown + - if type != :boards_modal && type != :boards + = dropdown_tag(content_tag(:i, '', class: 'fa fa-history'), + options: { wrapper_class: "filtered-search-history-dropdown-wrapper", + toggle_class: "filtered-search-history-dropdown-toggle-button", + dropdown_class: "filtered-search-history-dropdown", + content_class: "filtered-search-history-dropdown-content", + title: "Recent searches" }) do + .js-filtered-search-history-dropdown .filtered-search-box-input-container .scroll-container %ul.tokens-container.list-unstyled From 32a387a72c3d0085890001571e1a3bfd74ef07eb Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Thu, 16 Mar 2017 15:46:56 -0700 Subject: [PATCH 027/168] 29595 Customize experience callout design --- app/assets/stylesheets/pages/profile.scss | 59 +++++++++++++++---- app/views/shared/_user_callout.html.haml | 19 +++--- .../29595-customize-experience-callout.yml | 4 ++ 3 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 changelogs/unreleased/29595-customize-experience-callout.yml diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 8c6dd392865..a8dad58b1d3 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -289,8 +289,12 @@ table.u2f-registrations { margin: 0 auto; .bordered-box { - border: 1px solid $border-color; + border: 1px solid $blue-300; border-radius: $border-radius-default; + background-color: lighten($blue-50, 3%); + position: relative; + display: flex; + justify-content: center; } .landing { @@ -298,28 +302,59 @@ table.u2f-registrations { margin-bottom: $gl-padding; .close { - margin-right: 20px; - } + position: absolute; + right: 20px; + opacity: 1; - .dismiss-icon { - float: right; - cursor: pointer; - color: $cycle-analytics-dismiss-icon-color; + .dismiss-icon { + float: right; + cursor: pointer; + color: $blue-300; + } + + &:hover { + background-color: transparent; + border: 0; + + .dismiss-icon { + color: $blue-400; + } + } } .svg-container { - text-align: center; + margin-right: 30px; + display: inline-block; svg { - width: 136px; - height: 136px; + height: 110px; + vertical-align: top; } } + + .user-callout-copy { + display: inline-block; + vertical-align: top; + } } @media(max-width: $screen-xs-max) { - .inner-content { - padding-left: 30px; + text-align: center; + + .bordered-box { + display: block; + } + + .landing { + .svg-container, + .user-callout-copy { + margin: 0; + display: block; + + svg { + height: 75px; + } + } } } } diff --git a/app/views/shared/_user_callout.html.haml b/app/views/shared/_user_callout.html.haml index 8f1293adcb1..108643f2f6f 100644 --- a/app/views/shared/_user_callout.html.haml +++ b/app/views/shared/_user_callout.html.haml @@ -1,14 +1,13 @@ .user-callout .bordered-box.landing.content-block - %button.btn.btn-default.close.js-close-callout{ type: 'button', + %button.btn.btn-default.close.close-user-callout{ type: 'button', 'aria-label' => 'Dismiss customize experience box' } = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') - .row - .col-sm-3.col-xs-12.svg-container - = custom_icon('icon_customization') - .col-sm-8.col-xs-12.inner-content - %h4 - Customize your experience - %p - Change syntax themes, default project pages, and more in preferences. - = link_to 'Check it out', profile_preferences_path, class: 'btn btn-default js-close-callout' + .svg-container + = custom_icon('icon_customization') + .user-callout-copy + %h4 + Customize your experience + %p + Change syntax themes, default project pages, and more in preferences. + = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary user-callout-btn' diff --git a/changelogs/unreleased/29595-customize-experience-callout.yml b/changelogs/unreleased/29595-customize-experience-callout.yml new file mode 100644 index 00000000000..ec8393142c6 --- /dev/null +++ b/changelogs/unreleased/29595-customize-experience-callout.yml @@ -0,0 +1,4 @@ +--- +title: 29595 Update callout design +merge_request: +author: From 0cb6370a2de01e76ab666ab253492a25b23d2687 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 12 Apr 2017 01:46:51 -0500 Subject: [PATCH 028/168] Fix note header timeago and action overlap Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/30810 --- app/assets/stylesheets/pages/notes.scss | 25 ++++++++++++++++----- app/views/projects/notes/_note.html.haml | 28 +++++++++++++----------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 94ea4c5c8c6..6d92a6c15ca 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -344,6 +344,15 @@ ul.notes { font-size: 14px; } +.note-header { + display: flex; + justify-content: space-between; +} + +.note-header-info { + min-width: 0; +} + .note-headline-light { display: inline; @@ -363,21 +372,27 @@ ul.notes { } } +.note-headline-meta { + display: inline-block; + white-space: nowrap; +} + /** * Actions for Discussions/Notes */ -.discussion-actions, -.note-actions { +.discussion-actions { float: right; margin-left: 10px; color: $gray-darkest; } .note-actions { - position: absolute; - right: 0; - top: 0; + flex-shrink: 0; + // For PhantomJS that does not support flex + float: right; + margin-left: 10px; + color: $gray-darkest; .note-action-button { margin-left: 8px; diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index c12c05eeb73..c4b8ee8d859 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -12,19 +12,21 @@ = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' .timeline-content .note-header - %a.visible-xs{ href: user_path(note.author) } - = note.author.to_reference - = link_to_member(note.project, note.author, avatar: false, extra_class: 'hidden-xs') - .note-headline-light - %span.hidden-xs - = note.author.to_reference - - unless note.system - commented - - if note.system - %span.system-note-message - = note.redacted_note_html - %a{ href: "##{dom_id(note)}" } - = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') + .note-header-info + %a{ href: user_path(note.author) } + %span.hidden-xs + = sanitize(note.author.name) + %span.note-headline-light + = note.author.to_reference + %span.note-headline-light + %span.note-headline-meta + - unless note.system + commented + - if note.system + %span.system-note-message + = note.redacted_note_html + %a{ href: "##{dom_id(note)}" } + = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') - unless note.system? .note-actions - access = note_max_access_for_user(note) From 6e21728d9549ee100ce6c5d869ba190477d4cb8d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Apr 2017 20:03:08 +0100 Subject: [PATCH 029/168] ESLint fix --- spec/javascripts/boards/list_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index 7c88a362fd2..24a2da9f6b6 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -114,7 +114,7 @@ describe('List model', () => { }); it('increase page number if current issue count is more than the page size', () => { - for (let i = 0; i < 30; i+=1) { + for (let i = 0; i < 30; i += 1) { list.issues.push(new ListIssue({ title: 'Testing', iid: _.random(10000) + i, From ef443a99eea066c644922ea66e5d01ab1ca66743 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 12 Apr 2017 08:31:56 -0400 Subject: [PATCH 030/168] Update ResolveBtn when CommentStore changes state --- .../javascripts/diff_notes/components/resolve_btn.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js index 312f38ce241..dcfc40c1013 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js @@ -20,8 +20,7 @@ import Vue from 'vue'; data: function () { return { discussions: CommentsStore.state, - loading: false, - note: {}, + loading: false }; }, watch: { @@ -34,6 +33,9 @@ import Vue from 'vue'; discussion: function () { return this.discussions[this.discussionId]; }, + note: function () { + return this.discussion ? this.discussion.getNote(this.noteId) : {}; + }, buttonText: function () { if (this.isResolved) { return `Resolved by ${this.resolvedByName}`; @@ -112,8 +114,6 @@ import Vue from 'vue'; authorAvatar: this.authorAvatar, noteTruncated: this.noteTruncated, }); - - this.note = this.discussion.getNote(this.noteId); } }); From d04a017ad63660543c3890a49e80547abac0332a Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Thu, 13 Apr 2017 16:43:07 -0500 Subject: [PATCH 031/168] Fixed tests --- app/views/shared/_user_callout.html.haml | 4 ++-- spec/javascripts/user_callout_spec.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/shared/_user_callout.html.haml b/app/views/shared/_user_callout.html.haml index 108643f2f6f..8308baa7829 100644 --- a/app/views/shared/_user_callout.html.haml +++ b/app/views/shared/_user_callout.html.haml @@ -1,6 +1,6 @@ .user-callout .bordered-box.landing.content-block - %button.btn.btn-default.close.close-user-callout{ type: 'button', + %button.btn.btn-default.close.js-close-callout{ type: 'button', 'aria-label' => 'Dismiss customize experience box' } = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') .svg-container @@ -10,4 +10,4 @@ Customize your experience %p Change syntax themes, default project pages, and more in preferences. - = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary user-callout-btn' + = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout' diff --git a/spec/javascripts/user_callout_spec.js b/spec/javascripts/user_callout_spec.js index c0375ebc61c..28d0c7dcd99 100644 --- a/spec/javascripts/user_callout_spec.js +++ b/spec/javascripts/user_callout_spec.js @@ -14,7 +14,6 @@ describe('UserCallout', function () { this.userCallout = new UserCallout(); this.closeButton = $('.js-close-callout.close'); this.userCalloutBtn = $('.js-close-callout:not(.close)'); - this.userCalloutContainer = $('.user-callout'); }); it('hides when user clicks on the dismiss-icon', (done) => { From 34b0197778271dd2f65d16763d0b6456766f0175 Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Thu, 13 Apr 2017 22:16:54 +0000 Subject: [PATCH 032/168] Remove unnecessary example. --- PROCESS.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 3efbb8f83ab..cfa841dc13d 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -139,8 +139,6 @@ When in doubt, we err on the side of _not_ cherry-picking. For example, it is likely that an exception will be made for a trivial 1-5 line performance improvement (e.g. adding a database index or adding `includes` to a query), but not for a new feature, no matter how relatively small or thoroughly tested. -Another exception example is a medium-risk feature such as a navigation change in response to significant negative community feedback from a previous release. - During the feature freeze all merge requests that are meant to go into the upcoming release should have the correct milestone assigned _and_ have the label ~"Pick into Stable" set, so that release managers can find and pick them. From 04e2dde538b7a91de0ffdd09d355e9c990b4c7b1 Mon Sep 17 00:00:00 2001 From: barthc Date: Thu, 8 Sep 2016 11:08:09 +0100 Subject: [PATCH 033/168] frontend prevent authored votes --- app/assets/javascripts/awards_handler.js | 44 +++++++++++++++---- .../javascripts/lib/utils/common_utils.js | 4 ++ app/assets/stylesheets/framework/awards.scss | 20 +++++++++ app/helpers/issues_helper.rb | 8 ++++ app/views/award_emoji/_awards_block.html.haml | 4 +- app/views/projects/notes/_note.html.haml | 3 +- .../6260-frontend-prevent-authored-votes.yml | 4 ++ spec/javascripts/awards_handler_spec.js | 25 ++++++++++- 8 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/6260-frontend-prevent-authored-votes.yml diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index ce426741637..d8a73b0f5e7 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,3 +1,5 @@ +/* global Flash */ + import Cookies from 'js-cookie'; import emojiMap from 'emojis/digests.json'; @@ -124,6 +126,8 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { } const $menu = $('.emoji-menu'); + const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent(); + const $userAuthored = this.isUserAuthored($addBtn); if ($menu.length) { if ($menu.is('.is-visible')) { $addBtn.removeClass('is-active'); @@ -147,6 +151,8 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { }, 200); }); } + + $thumbsBtn.toggleClass('disabled', $userAuthored); }; // Create the emoji menu with the first category of emojis. @@ -259,7 +265,8 @@ AwardsHandler.prototype.addAward = function addAward( callback, ) { const normalizedEmoji = this.normalizeEmojiName(emoji); - this.postEmoji(awardUrl, normalizedEmoji, () => { + const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); + this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => { this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); return typeof callback === 'function' ? callback() : undefined; }); @@ -324,6 +331,10 @@ AwardsHandler.prototype.isActive = function isActive($emojiButton) { return $emojiButton.hasClass('active'); }; +AwardsHandler.prototype.isUserAuthored = function isUserAuthored($button) { + return $button.hasClass('js-user-authored'); +}; + AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) { const counter = $('.js-counter', $emojiButton); const counterNumber = parseInt(counter.text(), 10); @@ -428,20 +439,35 @@ AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) { }); }; -AwardsHandler.prototype.postEmoji = function postEmoji(awardUrl, emoji, callback) { - return $.post(awardUrl, { - name: emoji, - }, (data) => { - if (data.ok) { - callback(); - } - }); +AwardsHandler.prototype.postEmoji = function postEmoji($emojiButton, awardUrl, emoji, callback) { + if (this.isUserAuthored($emojiButton)) { + this.userAuthored($emojiButton); + } else { + $.post(awardUrl, { + name: emoji, + }, (data) => { + if (data.ok) { + callback(); + } + }).fail(() => new Flash('Something went wrong on our end.')); + } }; AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) { return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`); }; +AwardsHandler.prototype.userAuthored = function userAuthored($emojiButton) { + const oldTitle = this.getAwardTooltip($emojiButton); + const newTitle = 'You cannot vote on your own issue, MR and note'; + gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show'); + // Restore tooltip back to award list + return setTimeout(() => { + $emojiButton.tooltip('hide'); + gl.utils.updateTooltipTitle($emojiButton, oldTitle); + }, 2800); +}; + AwardsHandler.prototype.scrollToAwards = function scrollToAwards() { const options = { scrollTop: $('.awards').offset().top - 110, diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index e1e6ca25446..33c900e264b 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -47,6 +47,10 @@ } }; + gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) { + return $tooltipEl.attr('title', newTitle).tooltip('fixTitle'); + }; + w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) { event_name = event_name || 'input'; var closest_submit, field, that; diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss index b849cc2d853..f614f262316 100644 --- a/app/assets/stylesheets/framework/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -38,6 +38,15 @@ height: 300px; overflow-y: scroll; } + + .disabled { + cursor: default; + opacity: 0.5; + + &:hover { + transform: none; + } + } } .emoji-search { @@ -154,6 +163,17 @@ } } + &.user-authored { + cursor: default; + opacity: 0.65; + + &:hover, + &:active { + background-color: $white-light; + border-color: $border-color; + } + } + &.btn { &:focus { outline: 0; diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 6978b0c89fd..82288f1da35 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -110,6 +110,14 @@ module IssuesHelper end end + def award_user_authored_class(award) + if award == 'thumbsdown' || award == 'thumbsup' + 'user-authored js-user-authored' + else + '' + end + end + def awards_sort(awards) awards.sort_by do |award, notes| if award == "thumbsup" diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml index 3ca45fbf751..9aabfb49a29 100644 --- a/app/views/award_emoji/_awards_block.html.haml +++ b/app/views/award_emoji/_awards_block.html.haml @@ -1,8 +1,9 @@ - grouped_emojis = awardable.grouped_awards(with_thumbs: inline) +- user_authored = awardable.user_authored?(current_user) .awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } - awards_sort(grouped_emojis).each do |emoji, awards| %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", - class: (award_state_class(awards, current_user)), + class: [(award_state_class(awards, current_user)), (award_user_authored_class(emoji) if user_authored)], data: { placement: "bottom", title: award_user_list(awards, current_user) } } = emoji_icon(emoji) %span.award-control-text.js-counter @@ -12,6 +13,7 @@ .award-menu-holder.js-award-holder %button.btn.award-control.has-tooltip.js-add-award{ type: 'button', 'aria-label': 'Add emoji', + class: ("js-user-authored" if user_authored), data: { title: 'Add emoji', placement: "bottom" } } %span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face') %span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley') diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 1f021ad77e5..a6fbe2c441f 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -59,7 +59,8 @@ - if current_user - if note.emoji_awardable? - = link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do + - user_authored = note.user_authored?(current_user) + = link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do = icon('spinner spin') %span{ class: "link-highlight award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face') %span{ class: "link-highlight award-control-icon-positive" }= custom_icon('emoji_smiley') diff --git a/changelogs/unreleased/6260-frontend-prevent-authored-votes.yml b/changelogs/unreleased/6260-frontend-prevent-authored-votes.yml new file mode 100644 index 00000000000..82e852fa197 --- /dev/null +++ b/changelogs/unreleased/6260-frontend-prevent-authored-votes.yml @@ -0,0 +1,4 @@ +--- +title: 'Frontend prevent authored votes' +merge_request: 6260 +author: Barthc diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index ea7753c7a1d..d6d50304086 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -3,6 +3,8 @@ import Cookies from 'js-cookie'; import AwardsHandler from '~/awards_handler'; +require('~/lib/utils/common_utils'); + (function() { var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu; @@ -28,7 +30,7 @@ import AwardsHandler from '~/awards_handler'; loadFixtures('issues/issue_with_comment.html.raw'); awardsHandler = new AwardsHandler; spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) { - return function(url, emoji, cb) { + return function(button, url, emoji, cb) { return cb(); }; })(this)); @@ -115,6 +117,27 @@ import AwardsHandler from '~/awards_handler'; return expect($emojiButton.next('.js-counter').text()).toBe('4'); }); }); + describe('::userAuthored', function() { + it('should update tooltip to user authored title', function() { + var $thumbsUpEmoji, $votesBlock; + $votesBlock = $('.js-awards-block').eq(0); + $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); + $thumbsUpEmoji.attr('data-title', 'sam'); + awardsHandler.userAuthored($thumbsUpEmoji); + return expect($thumbsUpEmoji.data("original-title")).toBe("You cannot vote on your own issue, MR and note"); + }); + it('should restore tooltip back to initial vote list', function() { + var $thumbsUpEmoji, $votesBlock; + jasmine.clock().install(); + $votesBlock = $('.js-awards-block').eq(0); + $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); + $thumbsUpEmoji.attr('data-title', 'sam'); + awardsHandler.userAuthored($thumbsUpEmoji); + jasmine.clock().tick(2801); + jasmine.clock().uninstall(); + return expect($thumbsUpEmoji.data("original-title")).toBe("sam"); + }); + }); describe('::getAwardUrl', function() { return it('returns the url for request', function() { return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji'); From 2756e612873d6dcd0a6e290cfbe556234da8f061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 12 Apr 2017 17:02:53 +0200 Subject: [PATCH 034/168] Reorganize feature specs for MR notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../merge_requests/diff_notes_spec.rb | 238 -------------- .../user_posts_diff_notes_spec.rb | 294 ++++++++++++++++++ .../merge_requests/user_posts_notes.rb | 145 +++++++++ .../user_sees_system_notes_spec.rb | 31 ++ spec/features/notes_on_merge_requests_spec.rb | 285 ----------------- 5 files changed, 470 insertions(+), 523 deletions(-) delete mode 100644 spec/features/merge_requests/diff_notes_spec.rb create mode 100644 spec/features/merge_requests/user_posts_diff_notes_spec.rb create mode 100644 spec/features/merge_requests/user_posts_notes.rb create mode 100644 spec/features/merge_requests/user_sees_system_notes_spec.rb delete mode 100644 spec/features/notes_on_merge_requests_spec.rb diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb deleted file mode 100644 index 06fad1007e8..00000000000 --- a/spec/features/merge_requests/diff_notes_spec.rb +++ /dev/null @@ -1,238 +0,0 @@ -require 'spec_helper' - -feature 'Diff notes', js: true, feature: true do - include WaitForAjax - - before do - login_as :admin - @merge_request = create(:merge_request) - @project = @merge_request.source_project - end - - context 'merge request diffs' do - let(:comment_button_class) { '.add-diff-note' } - let(:notes_holder_input_class) { 'js-temp-notes-holder' } - let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' } - let(:test_note_comment) { 'this is a test note!' } - - context 'when hovering over a parallel view diff file' do - before(:each) do - visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'parallel') - end - - context 'with an old line on the left and no line on the right' do - it 'should allow commenting on the left side' do - should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'left') - end - - it 'should not allow commenting on the right side' do - should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right') - end - end - - context 'with no line on the left and a new line on the right' do - it 'should not allow commenting on the left side' do - should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left') - end - - it 'should allow commenting on the right side' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right') - end - end - - context 'with an old line on the left and a new line on the right' do - it 'should allow commenting on the left side' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left') - end - - it 'should allow commenting on the right side' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right') - end - end - - context 'with an unchanged line on the left and an unchanged line on the right' do - it 'should allow commenting on the left side' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left') - end - - it 'should allow commenting on the right side' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right') - end - end - - context 'with a match line' do - it 'should not allow commenting on the left side' do - should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left') - end - - it 'should not allow commenting on the right side' do - should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right') - end - end - - context 'with an unfolded line' do - before(:each) do - find('.js-unfold', match: :first).click - wait_for_ajax - end - - # The first `.js-unfold` unfolds upwards, therefore the first - # `.line_holder` will be an unfolded line. - let(:line_holder) { first('.line_holder[id="1"]') } - - it 'should not allow commenting on the left side' do - should_not_allow_commenting(line_holder, 'left') - end - - it 'should not allow commenting on the right side' do - should_not_allow_commenting(line_holder, 'right') - end - end - end - - context 'when hovering over an inline view diff file' do - before do - visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') - end - - context 'with a new line' do - it 'should allow commenting' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) - end - end - - context 'with an old line' do - it 'should allow commenting' do - should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) - end - end - - context 'with an unchanged line' do - it 'should allow commenting' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) - end - end - - context 'with a match line' do - it 'should not allow commenting' do - should_not_allow_commenting(find('.match', match: :first)) - end - end - - context 'with an unfolded line' do - before(:each) do - find('.js-unfold', match: :first).click - wait_for_ajax - end - - # The first `.js-unfold` unfolds upwards, therefore the first - # `.line_holder` will be an unfolded line. - let(:line_holder) { first('.line_holder[id="1"]') } - - it 'should not allow commenting' do - should_not_allow_commenting line_holder - end - end - - context 'when hovering over a diff discussion' do - before do - visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) - visit namespace_project_merge_request_path(@project.namespace, @project, @merge_request) - end - - it 'should not allow commenting' do - should_not_allow_commenting(find('.line_holder', match: :first)) - end - end - end - - context 'when the MR only supports legacy diff notes' do - before do - @merge_request.merge_request_diff.update_attributes(start_commit_sha: nil) - visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') - end - - context 'with a new line' do - it 'should allow commenting' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) - end - end - - context 'with an old line' do - it 'should allow commenting' do - should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) - end - end - - context 'with an unchanged line' do - it 'should allow commenting' do - should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) - end - end - - context 'with a match line' do - it 'should not allow commenting' do - should_not_allow_commenting(find('.match', match: :first)) - end - end - end - - def should_allow_commenting(line_holder, diff_side = nil) - line = get_line_components(line_holder, diff_side) - line[:content].hover - expect(line[:num]).to have_css comment_button_class - - comment_on_line(line_holder, line) - - assert_comment_persistence(line_holder) - end - - def should_not_allow_commenting(line_holder, diff_side = nil) - line = get_line_components(line_holder, diff_side) - line[:content].hover - expect(line[:num]).not_to have_css comment_button_class - end - - def get_line_components(line_holder, diff_side = nil) - if diff_side.nil? - get_inline_line_components(line_holder) - else - get_parallel_line_components(line_holder, diff_side) - end - end - - def get_inline_line_components(line_holder) - { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) } - end - - def get_parallel_line_components(line_holder, diff_side = nil) - side_index = diff_side == 'left' ? 0 : 1 - # Wait for `.line_content` - line_holder.find('.line_content', match: :first) - # Wait for `.diff-line-num` - line_holder.find('.diff-line-num', match: :first) - { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] } - end - - def comment_on_line(line_holder, line) - line[:num].find(comment_button_class).trigger 'click' - line_holder.find(:xpath, notes_holder_input_xpath) - - notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath) - expect(notes_holder_input[:class]).to include(notes_holder_input_class) - - notes_holder_input.fill_in 'note[note]', with: test_note_comment - click_button 'Comment' - wait_for_ajax - end - - def assert_comment_persistence(line_holder) - expect(line_holder).to have_xpath notes_holder_input_xpath - - notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath) - expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class) - expect(notes_holder_saved).to have_content test_note_comment - end - end -end diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb new file mode 100644 index 00000000000..7756202e3f5 --- /dev/null +++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb @@ -0,0 +1,294 @@ +require 'spec_helper' + +feature 'Merge requests > User posts diff notes', :js do + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.source_project } + + before do + project.add_developer(user) + login_as(user) + end + + let(:comment_button_class) { '.add-diff-note' } + let(:notes_holder_input_class) { 'js-temp-notes-holder' } + let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' } + let(:test_note_comment) { 'this is a test note!' } + + context 'when hovering over a parallel view diff file' do + before do + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'parallel') + end + + context 'with an old line on the left and no line on the right' do + it 'allows commenting on the left side' do + should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'left') + end + + it 'does not allow commenting on the right side' do + should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right') + end + end + + context 'with no line on the left and a new line on the right' do + it 'does not allow commenting on the left side' do + should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left') + end + + it 'allows commenting on the right side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right') + end + end + + context 'with an old line on the left and a new line on the right' do + it 'allows commenting on the left side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left') + end + + it 'allows commenting on the right side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right') + end + end + + context 'with an unchanged line on the left and an unchanged line on the right' do + it 'allows commenting on the left side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left') + end + + it 'allows commenting on the right side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right') + end + end + + context 'with a match line' do + it 'does not allow commenting on the left side' do + should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left') + end + + it 'does not allow commenting on the right side' do + should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right') + end + end + + context 'with an unfolded line' do + before(:each) do + find('.js-unfold', match: :first).click + wait_for_ajax + end + + # The first `.js-unfold` unfolds upwards, therefore the first + # `.line_holder` will be an unfolded line. + let(:line_holder) { first('.line_holder[id="1"]') } + + it 'does not allow commenting on the left side' do + should_not_allow_commenting(line_holder, 'left') + end + + it 'does not allow commenting on the right side' do + should_not_allow_commenting(line_holder, 'right') + end + end + end + + context 'when hovering over an inline view diff file' do + before do + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline') + end + + context 'with a new line' do + it 'allows commenting' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) + end + end + + context 'with an old line' do + it 'allows commenting' do + should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) + end + end + + context 'with an unchanged line' do + it 'allows commenting' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) + end + end + + context 'with a match line' do + it 'does not allow commenting' do + should_not_allow_commenting(find('.match', match: :first)) + end + end + + context 'with an unfolded line' do + before(:each) do + find('.js-unfold', match: :first).click + wait_for_ajax + end + + # The first `.js-unfold` unfolds upwards, therefore the first + # `.line_holder` will be an unfolded line. + let(:line_holder) { first('.line_holder[id="1"]') } + + it 'does not allow commenting' do + should_not_allow_commenting line_holder + end + end + + context 'when hovering over a diff discussion' do + before do + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline') + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'does not allow commenting' do + should_not_allow_commenting(find('.line_holder', match: :first)) + end + end + end + + context 'when cancelling the comment addition' do + before do + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline') + end + + context 'with a new line' do + it 'allows dismissing a comment' do + should_allow_dismissing_a_comment(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) + end + end + end + + describe 'with muliple note forms' do + before do + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline') + click_diff_line(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) + click_diff_line(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) + end + + describe 'posting a note' do + it 'adds as discussion' do + expect(page).to have_css('.js-temp-notes-holder', count: 2) + + should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'), asset_form_reset: false) + expect(page).to have_css('.notes_holder .note', count: 1) + expect(page).to have_css('.js-temp-notes-holder', count: 1) + expect(page).to have_button('Reply...') + end + end + end + + context 'when the MR only supports legacy diff notes' do + before do + merge_request.merge_request_diff.update_attributes(start_commit_sha: nil) + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline') + end + + context 'with a new line' do + it 'allows commenting' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) + end + end + + context 'with an old line' do + it 'allows commenting' do + should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) + end + end + + context 'with an unchanged line' do + it 'allows commenting' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) + end + end + + context 'with a match line' do + it 'does not allow commenting' do + should_not_allow_commenting(find('.match', match: :first)) + end + end + end + + def should_allow_commenting(line_holder, diff_side = nil, asset_form_reset: true) + write_comment_on_line(line_holder, diff_side) + + click_button 'Comment' + wait_for_ajax + + assert_comment_persistence(line_holder, asset_form_reset: asset_form_reset) + end + + def should_allow_dismissing_a_comment(line_holder, diff_side = nil) + write_comment_on_line(line_holder, diff_side) + + find('.js-close-discussion-note-form').trigger('click') + + assert_comment_dismissal(line_holder) + end + + def should_not_allow_commenting(line_holder, diff_side = nil) + line = get_line_components(line_holder, diff_side) + line[:content].hover + expect(line[:num]).not_to have_css comment_button_class + end + + def get_line_components(line_holder, diff_side = nil) + if diff_side.nil? + get_inline_line_components(line_holder) + else + get_parallel_line_components(line_holder, diff_side) + end + end + + def get_inline_line_components(line_holder) + { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) } + end + + def get_parallel_line_components(line_holder, diff_side = nil) + side_index = diff_side == 'left' ? 0 : 1 + # Wait for `.line_content` + line_holder.find('.line_content', match: :first) + # Wait for `.diff-line-num` + line_holder.find('.diff-line-num', match: :first) + { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] } + end + + def click_diff_line(line_holder, diff_side = nil) + line = get_line_components(line_holder, diff_side) + line[:content].hover + + expect(line[:num]).to have_css comment_button_class + + line[:num].find(comment_button_class).trigger 'click' + end + + def write_comment_on_line(line_holder, diff_side) + click_diff_line(line_holder, diff_side) + + notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath) + + expect(notes_holder_input[:class]).to include(notes_holder_input_class) + + notes_holder_input.fill_in 'note[note]', with: test_note_comment + end + + def assert_comment_persistence(line_holder, asset_form_reset:) + notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath) + + expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class) + expect(notes_holder_saved).to have_content test_note_comment + + assert_form_is_reset if asset_form_reset + end + + def assert_comment_dismissal(line_holder) + expect(line_holder).not_to have_xpath notes_holder_input_xpath + expect(page).not_to have_content test_note_comment + + assert_form_is_reset + end + + def assert_form_is_reset + expect(page).to have_no_css('.js-temp-notes-holder') + end +end diff --git a/spec/features/merge_requests/user_posts_notes.rb b/spec/features/merge_requests/user_posts_notes.rb new file mode 100644 index 00000000000..c7cc4d6bc72 --- /dev/null +++ b/spec/features/merge_requests/user_posts_notes.rb @@ -0,0 +1,145 @@ +require 'spec_helper' + +describe 'Merge requests > User posts notes', :js do + let(:project) { create(:project) } + let(:merge_request) do + create(:merge_request, source_project: project, target_project: project) + end + let!(:note) do + create(:note_on_merge_request, :with_attachment, noteable: merge_request, + project: project) + end + + before do + login_as :admin + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + subject { page } + + describe 'the note form' do + it 'is valid' do + is_expected.to have_css('.js-main-target-form', visible: true, count: 1) + expect(find('.js-main-target-form .js-comment-button').value). + to eq('Comment') + page.within('.js-main-target-form') do + expect(page).not_to have_link('Cancel') + end + end + + describe 'with text' do + before do + page.within('.js-main-target-form') do + fill_in 'note[note]', with: 'This is awesome' + end + end + + it 'has enable submit button and preview button' do + page.within('.js-main-target-form') do + expect(page).not_to have_css('.js-comment-button[disabled]') + expect(page).to have_css('.js-md-preview-button', visible: true) + end + end + end + end + + describe 'when posting a note' do + before do + page.within('.js-main-target-form') do + fill_in 'note[note]', with: 'This is awesome!' + find('.js-md-preview-button').click + click_button 'Comment' + end + end + + it 'is added and form reset' do + is_expected.to have_content('This is awesome!') + page.within('.js-main-target-form') do + expect(page).to have_no_field('note[note]', with: 'This is awesome!') + expect(page).to have_css('.js-md-preview', visible: :hidden) + end + page.within('.js-main-target-form') do + is_expected.to have_css('.js-note-text', visible: true) + end + end + end + + describe 'when editing a note' do + it 'there should be a hidden edit form' do + is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1) + is_expected.to have_css('.note-edit-form.mr-note-edit-form', visible: false, count: 1) + end + + describe 'editing the note' do + before do + find('.note').hover + find('.js-note-edit').click + end + + it 'shows the note edit form and hide the note body' do + page.within("#note_#{note.id}") do + expect(find('.current-note-edit-form', visible: true)).to be_visible + expect(find('.note-edit-form', visible: true)).to be_visible + expect(find(:css, '.note-body > .note-text', visible: false)).not_to be_visible + end + end + + it 'resets the edit note form textarea with the original content of the note if cancelled' do + within('.current-note-edit-form') do + fill_in 'note[note]', with: 'Some new content' + find('.btn-cancel').click + expect(find('.js-note-text', visible: false).text).to eq '' + end + end + + it 'allows using markdown buttons after saving a note and then trying to edit it again' do + page.within('.current-note-edit-form') do + fill_in 'note[note]', with: 'This is the new content' + find('.btn-save').click + end + + find('.note').hover + find('.js-note-edit').click + + page.within('.current-note-edit-form') do + expect(find('#note_note').value).to eq('This is the new content') + find('.js-md:first-child').click + expect(find('#note_note').value).to eq('This is the new content****') + end + end + + it 'appends the edited at time to the note' do + page.within('.current-note-edit-form') do + fill_in 'note[note]', with: 'Some new content' + find('.btn-save').click + end + + page.within("#note_#{note.id}") do + is_expected.to have_css('.note_edited_ago') + expect(find('.note_edited_ago').text). + to match(/less than a minute ago/) + end + end + end + + describe 'deleting an attachment' do + before do + find('.note').hover + find('.js-note-edit').click + end + + it 'shows the delete link' do + page.within('.note-attachment') do + is_expected.to have_css('.js-note-attachment-delete') + end + end + + it 'removes the attachment div and resets the edit form' do + find('.js-note-attachment-delete').click + is_expected.not_to have_css('.note-attachment') + is_expected.not_to have_css('.current-note-edit-form') + wait_for_ajax + end + end + end +end diff --git a/spec/features/merge_requests/user_sees_system_notes_spec.rb b/spec/features/merge_requests/user_sees_system_notes_spec.rb new file mode 100644 index 00000000000..55d0f9d728c --- /dev/null +++ b/spec/features/merge_requests/user_sees_system_notes_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +feature 'Merge requests > User sees system notes' do + let(:public_project) { create(:project, :public) } + let(:private_project) { create(:project, :private) } + let(:issue) { create(:issue, project: private_project) } + let(:merge_request) { create(:merge_request, source_project: public_project, source_branch: 'markdown') } + let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: public_project, note: "mentioned in #{issue.to_reference(public_project)}") } + + context 'when logged-in as a member of the private project' do + before do + user = create(:user) + private_project.add_developer(user) + login_as(user) + end + + it 'shows the system note' do + visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request) + + expect(page).to have_css('.system-note') + end + end + + context 'when not logged-in' do + it 'hides the system note' do + visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request) + + expect(page).not_to have_css('.system-note') + end + end +end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb deleted file mode 100644 index 783f2e93909..00000000000 --- a/spec/features/notes_on_merge_requests_spec.rb +++ /dev/null @@ -1,285 +0,0 @@ -require 'spec_helper' - -describe 'Comments', feature: true do - include RepoHelpers - include WaitForAjax - - describe 'On a merge request', js: true, feature: true do - let!(:project) { create(:project) } - let!(:merge_request) do - create(:merge_request, source_project: project, target_project: project) - end - - let!(:note) do - create(:note_on_merge_request, :with_attachment, noteable: merge_request, - project: project) - end - - before do - login_as :admin - visit namespace_project_merge_request_path(project.namespace, project, merge_request) - end - - subject { page } - - describe 'the note form' do - it 'is valid' do - is_expected.to have_css('.js-main-target-form', visible: true, count: 1) - expect(find('.js-main-target-form .js-comment-button').value). - to eq('Comment') - page.within('.js-main-target-form') do - expect(page).not_to have_link('Cancel') - end - end - - describe 'with text' do - before do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: 'This is awesome' - end - end - - it 'has enable submit button and preview button' do - page.within('.js-main-target-form') do - expect(page).not_to have_css('.js-comment-button[disabled]') - expect(page).to have_css('.js-md-preview-button', visible: true) - end - end - end - end - - describe 'when posting a note' do - before do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: 'This is awsome!' - find('.js-md-preview-button').click - click_button 'Comment' - end - end - - it 'is added and form reset' do - is_expected.to have_content('This is awsome!') - page.within('.js-main-target-form') do - expect(page).to have_no_field('note[note]', with: 'This is awesome!') - expect(page).to have_css('.js-md-preview', visible: :hidden) - end - page.within('.js-main-target-form') do - is_expected.to have_css('.js-note-text', visible: true) - end - end - end - - describe 'when editing a note', js: true do - it 'there should be a hidden edit form' do - is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1) - is_expected.to have_css('.note-edit-form.mr-note-edit-form', visible: false, count: 1) - end - - describe 'editing the note' do - before do - find('.note').hover - find('.js-note-edit').click - end - - it 'shows the note edit form and hide the note body' do - page.within("#note_#{note.id}") do - expect(find('.current-note-edit-form', visible: true)).to be_visible - expect(find('.note-edit-form', visible: true)).to be_visible - expect(find(:css, '.note-body > .note-text', visible: false)).not_to be_visible - end - end - - it 'resets the edit note form textarea with the original content of the note if cancelled' do - within('.current-note-edit-form') do - fill_in 'note[note]', with: 'Some new content' - find('.btn-cancel').click - expect(find('.js-note-text', visible: false).text).to eq '' - end - end - - it 'allows using markdown buttons after saving a note and then trying to edit it again' do - page.within('.current-note-edit-form') do - fill_in 'note[note]', with: 'This is the new content' - find('.btn-save').click - end - - find('.note').hover - find('.js-note-edit').click - - page.within('.current-note-edit-form') do - expect(find('#note_note').value).to eq('This is the new content') - find('.js-md:first-child').click - expect(find('#note_note').value).to eq('This is the new content****') - end - end - - it 'appends the edited at time to the note' do - page.within('.current-note-edit-form') do - fill_in 'note[note]', with: 'Some new content' - find('.btn-save').click - end - - page.within("#note_#{note.id}") do - is_expected.to have_css('.note_edited_ago') - expect(find('.note_edited_ago').text). - to match(/less than a minute ago/) - end - end - end - - describe 'deleting an attachment' do - before do - find('.note').hover - find('.js-note-edit').click - end - - it 'shows the delete link' do - page.within('.note-attachment') do - is_expected.to have_css('.js-note-attachment-delete') - end - end - - it 'removes the attachment div and resets the edit form' do - find('.js-note-attachment-delete').click - is_expected.not_to have_css('.note-attachment') - is_expected.not_to have_css('.current-note-edit-form') - wait_for_ajax - end - end - end - end - - describe 'Handles cross-project system notes', js: true, feature: true do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - let(:project2) { create(:project, :private) } - let(:issue) { create(:issue, project: project2) } - let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') } - let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") } - - it 'shows the system note' do - login_as :admin - visit namespace_project_merge_request_path(project.namespace, project, merge_request) - - expect(page).to have_css('.system-note') - end - - it 'hides redacted system note' do - visit namespace_project_merge_request_path(project.namespace, project, merge_request) - - expect(page).not_to have_css('.system-note') - end - end - - describe 'On a merge request diff', js: true, feature: true do - let(:merge_request) { create(:merge_request) } - let(:project) { merge_request.source_project } - - before do - login_as :admin - visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) - end - - subject { page } - - describe 'when adding a note' do - before do - click_diff_line - end - - describe 'the notes holder' do - it { is_expected.to have_css('.js-temp-notes-holder') } - - it 'has .new_note css class' do - page.within('.js-temp-notes-holder') do - expect(subject).to have_css('.new-note') - end - end - end - - describe 'the note form' do - it "does not add a second form for same row" do - click_diff_line - - is_expected. - to have_css("form[data-line-code='#{line_code}']", - count: 1) - end - - it 'is removed when canceled' do - is_expected.to have_css('.js-temp-notes-holder') - - page.within("form[data-line-code='#{line_code}']") do - find('.js-close-discussion-note-form').trigger('click') - end - - is_expected.to have_no_css('.js-temp-notes-holder') - end - end - end - - describe 'with muliple note forms' do - before do - click_diff_line - click_diff_line(line_code_2) - end - - it { is_expected.to have_css('.js-temp-notes-holder', count: 2) } - - describe 'previewing them separately' do - before do - # add two separate texts and trigger previews on both - page.within("tr[id='#{line_code}'] + .js-temp-notes-holder") do - fill_in 'note[note]', with: 'One comment on line 7' - find('.js-md-preview-button').click - end - page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do - fill_in 'note[note]', with: 'Another comment on line 10' - find('.js-md-preview-button').click - end - end - end - - describe 'posting a note' do - before do - page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do - fill_in 'note[note]', with: 'Another comment on line 10' - click_button('Comment') - end - end - - it 'adds as discussion' do - is_expected.to have_content('Another comment on line 10') - is_expected.to have_css('.notes_holder') - is_expected.to have_css('.notes_holder .note', count: 1) - is_expected.to have_button('Reply...') - end - - it 'adds code to discussion' do - click_button 'Reply...' - - page.within(first('.js-discussion-note-form')) do - fill_in 'note[note]', with: '```{{ test }}```' - - click_button('Comment') - end - - expect(page).to have_content('{{ test }}') - end - end - end - end - - def line_code - sample_compare.changes.first[:line_code] - end - - def line_code_2 - sample_compare.changes.last[:line_code] - end - - def click_diff_line(data = line_code) - find(".line_holder[id='#{data}'] td.line_content").hover - find(".line_holder[id='#{data}'] button").trigger('click') - end -end From fb20dcf8e67992cada346ff3718e078cdf5359e8 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 14 Apr 2017 11:34:50 +0100 Subject: [PATCH 035/168] Remove unneeded format block --- app/serializers/status_entity.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb index 6862730d9d2..944472f3e51 100644 --- a/app/serializers/status_entity.rb +++ b/app/serializers/status_entity.rb @@ -1,10 +1,6 @@ class StatusEntity < Grape::Entity include RequestAwareEntity - format_with(:status_favicon_path) do |favicon_name| - ci_status_favicon_path(favicon_name) - end - expose :icon, :text, :label, :group expose :has_details?, as: :has_details From ebd5e9b4549ebc80155a5a8f139efdb40b6f8b12 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 5 Apr 2017 13:19:59 +0100 Subject: [PATCH 036/168] Port 'Add EE usage ping' to CE CE port of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/557 --- .../admin/application_settings_controller.rb | 1 + app/models/application_setting.rb | 3 +- .../application_settings/_form.html.haml | 9 +++++ app/workers/gitlab_usage_ping_worker.rb | 40 +++++++++++++++++++ config/initializers/1_settings.rb | 11 +++++ ..._add_usage_ping_to_application_settings.rb | 7 ++++ db/schema.rb | 1 + spec/workers/gitlab_usage_ping_worker_spec.rb | 29 ++++++++++++++ 8 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 app/workers/gitlab_usage_ping_worker.rb create mode 100644 db/migrate/20160713222618_add_usage_ping_to_application_settings.rb create mode 100644 spec/workers/gitlab_usage_ping_worker_spec.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 515d8e1523b..82b01be5a11 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -135,6 +135,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :version_check_enabled, :terminal_max_session_time, :polling_interval_multiplier, + :usage_ping_enabled, disabled_oauth_sign_in_sources: [], import_sources: [], diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 2961e16f5e0..dd1a6922968 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -238,7 +238,8 @@ class ApplicationSetting < ActiveRecord::Base terminal_max_session_time: 0, two_factor_grace_period: 48, user_default_external: false, - polling_interval_multiplier: 1 + polling_interval_multiplier: 1, + usage_ping_enabled: true } end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index f4ba44096d3..d8b6ce13ca4 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -486,6 +486,15 @@ Version check enabled .help-block Let GitLab inform you when an update is available. + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :usage_ping_enabled do + = f.check_box :usage_ping_enabled + Usage ping enabled + .help-block + Every week GitLab will report license usage back to GitLab, Inc. + Disable this option if you do not want this to occur. %fieldset %legend Email diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb new file mode 100644 index 00000000000..2e039b7f3c5 --- /dev/null +++ b/app/workers/gitlab_usage_ping_worker.rb @@ -0,0 +1,40 @@ +class GitlabUsagePingWorker + LEASE_TIMEOUT = 86400 + + include Sidekiq::Worker + include HTTParty + + # This is not guaranteed to succeed, so don't retry on failure + sidekiq_options queue: :default, retry: false + + def perform + return unless current_application_settings.usage_ping_enabled + + # Multiple Sidekiq workers could run this. We should only do this at most once a day. + return unless try_obtain_lease + + begin + HTTParty.post(url, + body: data.to_json, + headers: { 'Content-type' => 'application/json' } + ) + rescue HTTParty::Error => e + Rails.logger.info "Unable to contact GitLab, Inc.: #{e}" + end + end + + def try_obtain_lease + Gitlab::ExclusiveLease.new('gitlab_usage_ping_worker:ping', timeout: LEASE_TIMEOUT).try_obtain + end + + def data + usage_data = { version: Gitlab::VERSION, + active_user_count: User.active.acount } + + usage_data + end + + def url + 'https://version.gitlab.com/usage_data' + end +end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 4c9d829aa9f..c19ccbb8fd8 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -110,6 +110,14 @@ class Settings < Settingslogic URI.parse(url_without_path).host end + + # Random cron time every Sunday to load balance usage pings + def cron_random_weekly_time + hour = rand(24) + minute = rand(60) + + "#{minute} #{hour} * * 0" + end end end @@ -355,6 +363,9 @@ Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['job_class'] = 'Rem Settings.cron_jobs['stuck_import_jobs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_import_jobs_worker']['cron'] ||= '15 * * * *' Settings.cron_jobs['stuck_import_jobs_worker']['job_class'] = 'StuckImportJobsWorker' +Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.send(:cron_random_weekly_time) +Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker' # # GitLab Shell diff --git a/db/migrate/20160713222618_add_usage_ping_to_application_settings.rb b/db/migrate/20160713222618_add_usage_ping_to_application_settings.rb new file mode 100644 index 00000000000..c7c5cdf7a56 --- /dev/null +++ b/db/migrate/20160713222618_add_usage_ping_to_application_settings.rb @@ -0,0 +1,7 @@ +class AddUsagePingToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + add_column :application_settings, :usage_ping_enabled, :boolean, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 5689f7331dc..7c5bb94dfb0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -116,6 +116,7 @@ ActiveRecord::Schema.define(version: 20170408033905) do t.integer "unique_ips_limit_time_window" t.boolean "unique_ips_limit_enabled", default: false, null: false t.decimal "polling_interval_multiplier", default: 1.0, null: false + t.boolean "usage_ping_enabled", default: true, null: false end create_table "audit_events", force: :cascade do |t| diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb new file mode 100644 index 00000000000..8c5c027ac29 --- /dev/null +++ b/spec/workers/gitlab_usage_ping_worker_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe GitlabUsagePingWorker do + subject { GitlabUsagePingWorker.new } + + it "gathers license data" do + data = subject.data + + expect(data[:version]).to eq(Gitlab::VERSION) + expect(data[:active_user_count]).to eq(User.active.count) + end + + it "sends POST request" do + stub_application_setting(usage_ping_enabled: true) + + stub_request(:post, "https://version.gitlab.com/usage_data"). + to_return(status: 200, body: '', headers: {}) + expect(subject).to receive(:try_obtain_lease).and_return(true) + + expect(subject.perform.response.code.to_i).to eq(200) + end + + it "does not run if usage ping is disabled" do + stub_application_setting(usage_ping_enabled: false) + + expect(subject).not_to receive(:try_obtain_lease) + expect(subject).not_to receive(:perform) + end +end From 0483019e9800dc1b4ef4493890f815f047b7c04e Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 5 Apr 2017 13:29:48 +0100 Subject: [PATCH 037/168] Port 'Add more usage data to EE ping' to CE CE port of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/735 --- .../javascripts/application_settings.js.es6 | 16 +++++ app/assets/javascripts/dispatcher.js | 3 + .../admin/application_settings_controller.rb | 7 ++ .../application_settings/_form.html.haml | 8 ++- app/workers/gitlab_usage_ping_worker.rb | 9 +-- config/routes/admin.rb | 1 + lib/gitlab/usage_data.rb | 57 ++++++++++++++++ .../application_settings_controller_spec.rb | 37 +++++++++++ spec/lib/gitlab/usage_data_spec.rb | 65 +++++++++++++++++++ spec/workers/gitlab_usage_ping_worker_spec.rb | 7 -- 10 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 app/assets/javascripts/application_settings.js.es6 create mode 100644 lib/gitlab/usage_data.rb create mode 100644 spec/lib/gitlab/usage_data_spec.rb diff --git a/app/assets/javascripts/application_settings.js.es6 b/app/assets/javascripts/application_settings.js.es6 new file mode 100644 index 00000000000..ce7d5129d8d --- /dev/null +++ b/app/assets/javascripts/application_settings.js.es6 @@ -0,0 +1,16 @@ +(global => { + global.gl = global.gl || {}; + + gl.ApplicationSettings = function() { + var usage_data_url = $('.usage-data').data('endpoint'); + + $.ajax({ + type: "GET", + url: usage_data_url, + dataType: "html", + success: function (html) { + $(".usage-data").html(html); + } + }); + }; +})(window); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f277e1dddc7..9d8f965dee0 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -365,6 +365,9 @@ const ShortcutsBlob = require('./shortcuts_blob'); case 'admin': new Admin(); switch (path[1]) { + case 'application_settings': + new gl.ApplicationSettings(); + break; case 'groups': new UsersSelect(); break; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 82b01be5a11..73b03b41594 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -17,6 +17,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end end + def usage_data + respond_to do |format| + format.html { render html: Gitlab::Highlight.highlight('payload.json', Gitlab::UsageData.to_json) } + format.json { render json: Gitlab::UsageData.to_json } + end + end + def reset_runners_token @application_setting.reset_runners_registration_token! flash[:notice] = 'New runners registration token has been generated!' diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index d8b6ce13ca4..f671af477ad 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -492,9 +492,11 @@ = f.label :usage_ping_enabled do = f.check_box :usage_ping_enabled Usage ping enabled - .help-block - Every week GitLab will report license usage back to GitLab, Inc. - Disable this option if you do not want this to occur. + .container + .help-block + Every week GitLab will report license usage back to GitLab, Inc. + Disable this option if you do not want this to occur. This is the JSON payload that will be sent: + %pre.usage-data.js-syntax-highlight.code.highlight{ "data-endpoint" => usage_data_admin_application_settings_path(format: :html) } %fieldset %legend Email diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb index 2e039b7f3c5..866f5d03d8b 100644 --- a/app/workers/gitlab_usage_ping_worker.rb +++ b/app/workers/gitlab_usage_ping_worker.rb @@ -15,7 +15,7 @@ class GitlabUsagePingWorker begin HTTParty.post(url, - body: data.to_json, + body: Gitlab::UsageData.to_json, headers: { 'Content-type' => 'application/json' } ) rescue HTTParty::Error => e @@ -27,13 +27,6 @@ class GitlabUsagePingWorker Gitlab::ExclusiveLease.new('gitlab_usage_ping_worker:ping', timeout: LEASE_TIMEOUT).try_obtain end - def data - usage_data = { version: Gitlab::VERSION, - active_user_count: User.active.acount } - - usage_data - end - def url 'https://version.gitlab.com/usage_data' end diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 486ce3c5c87..3c1c2ce2582 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -91,6 +91,7 @@ namespace :admin do resource :application_settings, only: [:show, :update] do resources :services, only: [:index, :edit, :update] + get :usage_data put :reset_runners_token put :reset_health_check_token put :clear_repository_check_states diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb new file mode 100644 index 00000000000..234d8680545 --- /dev/null +++ b/lib/gitlab/usage_data.rb @@ -0,0 +1,57 @@ +module Gitlab + class UsageData + class << self + def data + Rails.cache.fetch('usage_data', expires_in: 1.hour) { uncached_data } + end + + def uncached_data + license_usage_data.merge(system_usage_data) + end + + def to_json + data.to_json + end + + def system_usage_data + { + counts: { + boards: Board.count, + ci_builds: ::Ci::Build.count, + ci_pipelines: ::Ci::Pipeline.count, + ci_runners: ::Ci::Runner.count, + ci_triggers: ::Ci::Trigger.count, + deploy_keys: DeployKey.count, + deployments: Deployment.count, + environments: Environment.count, + groups: Group.count, + issues: Issue.count, + keys: Key.count, + labels: Label.count, + lfs_objects: LfsObject.count, + merge_requests: MergeRequest.count, + milestones: Milestone.count, + notes: Note.count, + pushes: Event.code_push.count, + pages_domains: PagesDomain.count, + projects: Project.count, + protected_branches: ProtectedBranch.count, + releases: Release.count, + services: Service.where(active: true).count, + snippets: Snippet.count, + todos: Todo.count, + web_hooks: WebHook.count + } + } + end + + def license_usage_data + usage_data = { version: Gitlab::VERSION, + active_user_count: User.active.count, + recorded_at: Time.now } + + usage_data + end + end + end +end diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 5dd8f66343f..2565622f8df 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -3,12 +3,49 @@ require 'spec_helper' describe Admin::ApplicationSettingsController do include StubENV + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:admin) { create(:admin) } + let(:user) { create(:user)} before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end + describe 'GET #usage_data with no access' do + before do + sign_in(user) + end + + it 'returns 404' do + get :usage_data, format: :html + + expect(response.status).to eq(404) + end + end + + describe 'GET #usage_data' do + before do + sign_in(admin) + end + + it 'returns HTML data' do + get :usage_data, format: :html + + expect(response.body).to start_with(' Date: Wed, 15 Mar 2017 16:24:45 -0500 Subject: [PATCH 038/168] Remove pushes count from usage ping payload This query is constantly generating timeout errors on large installations and we don't have a simple solution for now and also we don't think having this counter is really critical. --- lib/gitlab/usage_data.rb | 1 - spec/lib/gitlab/usage_data_spec.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 234d8680545..e5c1407d8fe 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -32,7 +32,6 @@ module Gitlab merge_requests: MergeRequest.count, milestones: Milestone.count, notes: Note.count, - pushes: Event.code_push.count, pages_domains: PagesDomain.count, projects: Project.count, protected_branches: ProtectedBranch.count, diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 8d2f086f0f5..badb8e3c7ae 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -41,7 +41,6 @@ describe Gitlab::UsageData do milestones notes projects - pushes pages_domains protected_branches releases From a19dd9ad25d460e87c3640096efe59f3680712ab Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 21 Sep 2016 20:24:23 +0300 Subject: [PATCH 039/168] Add documentation on Usage statistics and link from the admin area --- .../application_settings/_form.html.haml | 1 + .../admin_area/settings/usage_statistics.md | 87 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 doc/user/admin_area/settings/usage_statistics.md diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index f671af477ad..f4e4bac62d7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -492,6 +492,7 @@ = f.label :usage_ping_enabled do = f.check_box :usage_ping_enabled Usage ping enabled + = link_to icon('question-circle'), help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-data") .container .help-block Every week GitLab will report license usage back to GitLab, Inc. diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md new file mode 100644 index 00000000000..70dea71d3c7 --- /dev/null +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -0,0 +1,87 @@ +# Usage statistics + +GitLab Inc. will periodically collect information about your instance in order +to perform various actions. + +All statistics are opt-in and you can always disable them from the admin panel. + +## Version check + +GitLab can inform you when an update is available and the importance of it. + +No information other than the GitLab version and the instance's domain name +are collected. + +In the **Overview** tab you can see if your GitLab version is up to date. There +are three cases: 1) you are up to date (green), 2) there is an update available +(yellow) and 3) your version is vulnerable and a security fix is released (red). + +In any case, you will see a message informing you of the state and the +importance of the update. + +If enabled, the version status will also be shown in the help page (`/help`) +for all signed in users. + +## Usage data + +> [Introduced][ee-557] in GitLab Enterprise Edition 8.10. More statistics +[were added][ee-735] in GitLab Enterprise Edition 8.12. + +GitLab Inc. can collect non-sensitive information about how Enterprise Edition +customers use their GitLab instance upon the activation of a ping feature +located in the admin panel (`/admin/application_settings`). + +You can see the **exact** JSON payload that your instance sends to GitLab Inc. +in the "Usage statistics" section of the admin panel. + +Nothing qualitative is collected. Only quantitative. Meaning, no project name, +author name, nature of comments, name of labels, etc. + +This is done mainly for the following reasons: + +- to have a better understanding on how our users use our product +- to provide more tools for the customer success team to help customers onboard + better. + +The total number of the following is sent back to GitLab Inc.: + +- Comments +- Groups +- Users +- Projects +- Issues +- Labels +- CI builds +- Snippets +- Milestones +- Todos +- Pushes +- Merge requests +- Environments +- Triggers +- Deploy keys +- Pages +- Project Services +- Issue Boards +- CI Runners +- Deployments +- Geo Nodes +- LDAP Groups +- LDAP Keys +- LDAP Users +- LFS objects +- Protected branches +- Releases +- Remote mirrors +- Web hooks + +## Privacy policy + +GitLab Inc. does **not** collect any sensitive information, like project names +or the content of the comments. GitLab Inc. does not disclose or otherwise make +available any of the data collected on a customer specific basis. + +Read more in about the [Privacy policy](https://about.gitlab.com/privacy). + +[ee-557]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/557 +[ee-735]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/735 From f5b42881c88678cd85ea7743fdffa400105b4b8d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 18 Jan 2017 19:33:33 +0100 Subject: [PATCH 040/168] Track Mattermost usage --- doc/user/admin_area/settings/usage_statistics.md | 3 +++ lib/gitlab/usage_data.rb | 3 ++- spec/lib/gitlab/usage_data_spec.rb | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index 70dea71d3c7..4ad4a174640 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -75,6 +75,9 @@ The total number of the following is sent back to GitLab Inc.: - Remote mirrors - Web hooks +Also, we track if you've installed Mattermost with GitLab. +For example: `"mattermost_enabled":true"`. + ## Privacy policy GitLab Inc. does **not** collect any sensitive information, like project names diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index e5c1407d8fe..ae1ae82ab4c 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -47,7 +47,8 @@ module Gitlab def license_usage_data usage_data = { version: Gitlab::VERSION, active_user_count: User.active.count, - recorded_at: Time.now } + recorded_at: Time.now, + mattermost_enabled: Gitlab.config.mattermost.enabled } usage_data end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index badb8e3c7ae..2cb5d49c4c1 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -14,6 +14,7 @@ describe Gitlab::UsageData do counts version recorded_at + mattermost_enabled )) end From c53afeda0c2ee0cda89c235c9e8799baa1cfdc1a Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 5 Apr 2017 13:49:22 +0100 Subject: [PATCH 041/168] Port 'Add uuid to usage ping' to CE CE port of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1521 --- ...328010804_add_uuid_to_application_settings.rb | 16 ++++++++++++++++ db/schema.rb | 1 + lib/gitlab/usage_data.rb | 5 ++++- spec/lib/gitlab/usage_data_spec.rb | 4 +++- 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20170328010804_add_uuid_to_application_settings.rb diff --git a/db/migrate/20170328010804_add_uuid_to_application_settings.rb b/db/migrate/20170328010804_add_uuid_to_application_settings.rb new file mode 100644 index 00000000000..5dfcc751c7b --- /dev/null +++ b/db/migrate/20170328010804_add_uuid_to_application_settings.rb @@ -0,0 +1,16 @@ +class AddUuidToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column :application_settings, :uuid, :string + execute("UPDATE application_settings SET uuid = #{quote(SecureRandom.uuid)}") + end + + def down + remove_column :application_settings, :uuid + end +end diff --git a/db/schema.rb b/db/schema.rb index 7c5bb94dfb0..1c592dd5d6d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -117,6 +117,7 @@ ActiveRecord::Schema.define(version: 20170408033905) do t.boolean "unique_ips_limit_enabled", default: false, null: false t.decimal "polling_interval_multiplier", default: 1.0, null: false t.boolean "usage_ping_enabled", default: true, null: false + t.string "uuid" end create_table "audit_events", force: :cascade do |t| diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index ae1ae82ab4c..46875908fa3 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -1,5 +1,7 @@ module Gitlab class UsageData + include Gitlab::CurrentSettings + class << self def data Rails.cache.fetch('usage_data', expires_in: 1.hour) { uncached_data } @@ -45,7 +47,8 @@ module Gitlab end def license_usage_data - usage_data = { version: Gitlab::VERSION, + usage_data = { uuid: current_application_settings.uuid, + version: Gitlab::VERSION, active_user_count: User.active.count, recorded_at: Time.now, mattermost_enabled: Gitlab.config.mattermost.enabled } diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 2cb5d49c4c1..920135f79b4 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -12,9 +12,10 @@ describe Gitlab::UsageData do expect(subject.keys).to match_array(%i( active_user_count counts - version recorded_at mattermost_enabled + version + uuid )) end @@ -57,6 +58,7 @@ describe Gitlab::UsageData do subject { Gitlab::UsageData.license_usage_data } it "gathers license data" do + expect(subject[:uuid]).to eq(current_application_settings.uuid) expect(subject[:version]).to eq(Gitlab::VERSION) expect(subject[:active_user_count]).to eq(User.active.count) expect(subject[:recorded_at]).to be_a(Time) From bca368990acdb86acf251382c191fa9b7627837a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 4 Oct 2016 15:32:02 -0700 Subject: [PATCH 042/168] Cache the last usage data to avoid unicorn timeouts --- app/workers/gitlab_usage_ping_worker.rb | 2 +- lib/gitlab/usage_data.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb index 866f5d03d8b..f26a3f47dad 100644 --- a/app/workers/gitlab_usage_ping_worker.rb +++ b/app/workers/gitlab_usage_ping_worker.rb @@ -15,7 +15,7 @@ class GitlabUsagePingWorker begin HTTParty.post(url, - body: Gitlab::UsageData.to_json, + body: Gitlab::UsageData.to_json(true), headers: { 'Content-type' => 'application/json' } ) rescue HTTParty::Error => e diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 46875908fa3..02750fa02e6 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -3,16 +3,16 @@ module Gitlab include Gitlab::CurrentSettings class << self - def data - Rails.cache.fetch('usage_data', expires_in: 1.hour) { uncached_data } + def data(force_refresh = false) + Rails.cache.fetch('usage_data', force: force_refresh) { uncached_data } end def uncached_data license_usage_data.merge(system_usage_data) end - def to_json - data.to_json + def to_json(force_refresh = false) + data(force_refresh).to_json end def system_usage_data From f30a46b401f283f2dc868da17de413eee05198a5 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 19 Oct 2016 14:29:01 -0700 Subject: [PATCH 043/168] Used named parameter for refreshing usage data --- app/workers/gitlab_usage_ping_worker.rb | 2 +- lib/gitlab/usage_data.rb | 8 ++++---- spec/workers/gitlab_usage_ping_worker_spec.rb | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb index f26a3f47dad..4a9d626caec 100644 --- a/app/workers/gitlab_usage_ping_worker.rb +++ b/app/workers/gitlab_usage_ping_worker.rb @@ -15,7 +15,7 @@ class GitlabUsagePingWorker begin HTTParty.post(url, - body: Gitlab::UsageData.to_json(true), + body: Gitlab::UsageData.to_json(force_refresh: true), headers: { 'Content-type' => 'application/json' } ) rescue HTTParty::Error => e diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 02750fa02e6..26812ed7164 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -3,16 +3,16 @@ module Gitlab include Gitlab::CurrentSettings class << self - def data(force_refresh = false) - Rails.cache.fetch('usage_data', force: force_refresh) { uncached_data } + def data(force_refresh: false) + Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) { uncached_data } end def uncached_data license_usage_data.merge(system_usage_data) end - def to_json(force_refresh = false) - data(force_refresh).to_json + def to_json(force_refresh: false) + data(force_refresh: force_refresh).to_json end def system_usage_data diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb index 8821df3d81e..b6c080f36f4 100644 --- a/spec/workers/gitlab_usage_ping_worker_spec.rb +++ b/spec/workers/gitlab_usage_ping_worker_spec.rb @@ -8,6 +8,7 @@ describe GitlabUsagePingWorker do stub_request(:post, "https://version.gitlab.com/usage_data"). to_return(status: 200, body: '', headers: {}) + expect(Gitlab::UsageData).to receive(:to_json).with({ force_refresh: true }).and_call_original expect(subject).to receive(:try_obtain_lease).and_return(true) expect(subject.perform.response.code.to_i).to eq(200) From e21ed5fe776a6b571eaeecd243d020f53584abdc Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 9 Mar 2017 11:00:25 -0500 Subject: [PATCH 044/168] Add Upload count to usage data --- lib/gitlab/usage_data.rb | 1 + spec/lib/gitlab/usage_data_spec.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 26812ed7164..10170dd8271 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -41,6 +41,7 @@ module Gitlab services: Service.where(active: true).count, snippets: Snippet.count, todos: Todo.count, + uploads: Upload.count, web_hooks: WebHook.count } } diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 920135f79b4..bfaf6154e36 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -49,6 +49,7 @@ describe Gitlab::UsageData do services snippets todos + uploads web_hooks )) end From 945bf66d7a7e80d6be422e7c14cbce1bdd35523f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 5 Apr 2017 15:18:35 +0100 Subject: [PATCH 045/168] Add changelog for usage ping in CE --- changelogs/unreleased/usage-ping-port.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/usage-ping-port.yml diff --git a/changelogs/unreleased/usage-ping-port.yml b/changelogs/unreleased/usage-ping-port.yml new file mode 100644 index 00000000000..4f135100fce --- /dev/null +++ b/changelogs/unreleased/usage-ping-port.yml @@ -0,0 +1,4 @@ +--- +title: Add usage ping to CE +merge_request: +author: From 856dcd1832a9351cebcabbf3fc2149e13145a699 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 6 Apr 2017 20:13:52 +0100 Subject: [PATCH 046/168] Fix usage ping worker queue --- app/workers/gitlab_usage_ping_worker.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb index 4a9d626caec..2f02235b0ac 100644 --- a/app/workers/gitlab_usage_ping_worker.rb +++ b/app/workers/gitlab_usage_ping_worker.rb @@ -2,11 +2,9 @@ class GitlabUsagePingWorker LEASE_TIMEOUT = 86400 include Sidekiq::Worker + include CronjobQueue include HTTParty - # This is not guaranteed to succeed, so don't retry on failure - sidekiq_options queue: :default, retry: false - def perform return unless current_application_settings.usage_ping_enabled From 092258e92490f177c6262287d1eca4e33e908614 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 6 Apr 2017 20:16:57 +0100 Subject: [PATCH 047/168] Add edition to usage ping --- lib/gitlab/usage_data.rb | 13 ++++++++----- spec/lib/gitlab/usage_data_spec.rb | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 10170dd8271..008c3d994db 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -48,11 +48,14 @@ module Gitlab end def license_usage_data - usage_data = { uuid: current_application_settings.uuid, - version: Gitlab::VERSION, - active_user_count: User.active.count, - recorded_at: Time.now, - mattermost_enabled: Gitlab.config.mattermost.enabled } + usage_data = { + uuid: current_application_settings.uuid, + version: Gitlab::VERSION, + active_user_count: User.active.count, + recorded_at: Time.now, + mattermost_enabled: Gitlab.config.mattermost.enabled, + edition: 'CE' + } usage_data end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index bfaf6154e36..7ae26081022 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -14,6 +14,7 @@ describe Gitlab::UsageData do counts recorded_at mattermost_enabled + edition version uuid )) From b61199ce0ccdfcd11a338778ce300cd15e5f2a43 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 6 Apr 2017 20:52:08 +0100 Subject: [PATCH 048/168] Add prometheus services to usage ping --- doc/user/admin_area/settings/usage_statistics.md | 3 ++- lib/gitlab/usage_data.rb | 1 + spec/lib/gitlab/usage_data_spec.rb | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index 4ad4a174640..6c9352332c2 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -62,6 +62,7 @@ The total number of the following is sent back to GitLab Inc.: - Deploy keys - Pages - Project Services +- Projects using the Prometheus service - Issue Boards - CI Runners - Deployments @@ -75,7 +76,7 @@ The total number of the following is sent back to GitLab Inc.: - Remote mirrors - Web hooks -Also, we track if you've installed Mattermost with GitLab. +Also, we track if you've installed Mattermost with GitLab. For example: `"mattermost_enabled":true"`. ## Privacy policy diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 008c3d994db..6aca6db3123 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -36,6 +36,7 @@ module Gitlab notes: Note.count, pages_domains: PagesDomain.count, projects: Project.count, + projects_prometheus_active: PrometheusService.active.count, protected_branches: ProtectedBranch.count, releases: Release.count, services: Service.where(active: true).count, diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 7ae26081022..7f21288cf88 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -44,6 +44,7 @@ describe Gitlab::UsageData do milestones notes projects + projects_prometheus_active pages_domains protected_branches releases From 2951a8543ef97ceb1bcaca5f5140d822729c950b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 5 Oct 2016 16:41:32 +0200 Subject: [PATCH 049/168] Add user activity service and spec. Also added relevant - NOT offline - migration It uses a user activity table instead of a column in users. Tested with mySQL and postgreSQL --- .../projects/git_http_controller.rb | 6 +++++ app/controllers/sessions_controller.rb | 5 ++++ app/models/user.rb | 2 ++ app/models/user_activity.rb | 19 +++++++++++++ app/services/event_create_service.rb | 2 ++ app/services/users/activity_service.rb | 26 ++++++++++++++++++ .../20161007073613_create_user_activities.rb | 27 +++++++++++++++++++ db/schema.rb | 8 ++++++ lib/api/internal.rb | 12 +++++++++ spec/controllers/sessions_controller_spec.rb | 6 +++++ spec/factories/user_activities.rb | 6 +++++ spec/requests/api/internal_spec.rb | 13 +++++++++ spec/requests/git_http_spec.rb | 6 +++++ spec/services/event_create_service_spec.rb | 13 +++++++++ spec/services/users/activity_service_spec.rb | 26 ++++++++++++++++++ 15 files changed, 177 insertions(+) create mode 100644 app/models/user_activity.rb create mode 100644 app/services/users/activity_service.rb create mode 100644 db/migrate/20161007073613_create_user_activities.rb create mode 100644 spec/factories/user_activities.rb create mode 100644 spec/services/users/activity_service_spec.rb diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 37f6f637ff0..10adddb4636 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -5,6 +5,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) def info_refs if upload_pack? && upload_pack_allowed? + log_user_activity + render_ok elsif receive_pack? && receive_pack_allowed? render_ok @@ -106,4 +108,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController def access_klass @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess end + + def log_user_activity + Users::ActivityService.new(user, 'pull').execute + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index d3091a4f8e9..8c6ba4915cd 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -35,6 +35,7 @@ class SessionsController < Devise::SessionsController # hide the signed-in notification flash[:notice] = nil log_audit_event(current_user, with: authentication_method) + log_user_activity(current_user) end end @@ -123,6 +124,10 @@ class SessionsController < Devise::SessionsController for_authentication.security_event end + def log_user_activity(user) + Users::ActivityService.new(user, 'login').execute + end + def load_recaptcha Gitlab::Recaptcha.load_configurations! end diff --git a/app/models/user.rb b/app/models/user.rb index 457ba05fb04..1dde5c89699 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -101,6 +101,7 @@ class User < ActiveRecord::Base has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" + has_one :user_activity, dependent: :destroy # Issues that a user owns are expected to be moved to the "ghost" user before # the user is destroyed. If the user owns any issues during deletion, this @@ -158,6 +159,7 @@ class User < ActiveRecord::Base alias_attribute :private_token, :authentication_token delegate :path, to: :namespace, allow_nil: true, prefix: true + delegate :last_activity_at, to: :user_activity, allow_nil: true state_machine :state, initial: :active do event :block do diff --git a/app/models/user_activity.rb b/app/models/user_activity.rb new file mode 100644 index 00000000000..c5fdbff0feb --- /dev/null +++ b/app/models/user_activity.rb @@ -0,0 +1,19 @@ +class UserActivity < ActiveRecord::Base + belongs_to :user, inverse_of: :user_activity + + validates :user, uniqueness: true, presence: true + validates :last_activity_at, presence: true + + # Updated version of http://apidock.com/rails/ActiveRecord/Timestamp/touch + # That accepts a new record. + def touch + current_time = current_time_from_proper_timezone + + if persisted? + update_column(:last_activity_at, current_time) + else + self.last_activity_at = current_time + save!(validate: false) + end + end +end diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index e24cc66e0fe..0f3a485a3fd 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -72,6 +72,8 @@ class EventCreateService def push(project, current_user, push_data) create_event(project, current_user, Event::PUSHED, data: push_data) + + Users::ActivityService.new(current_user, 'push').execute end private diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb new file mode 100644 index 00000000000..b81f947cd01 --- /dev/null +++ b/app/services/users/activity_service.rb @@ -0,0 +1,26 @@ +module Users + class ActivityService + def initialize(author, activity) + @author = author.respond_to?(:user) ? author.user : author + @activity = activity + end + + def execute + return unless @author && @author.is_a?(User) + + record_activity + end + + private + + def record_activity + user_activity.touch + + Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username}") + end + + def user_activity + UserActivity.find_or_initialize_by(user: @author) + end + end +end diff --git a/db/migrate/20161007073613_create_user_activities.rb b/db/migrate/20161007073613_create_user_activities.rb new file mode 100644 index 00000000000..4239cebd8f6 --- /dev/null +++ b/db/migrate/20161007073613_create_user_activities.rb @@ -0,0 +1,27 @@ +class CreateUserActivities < ActiveRecord::Migration + # Set this constant to true if this migration requires downtime. + DOWNTIME = true + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + DOWNTIME_REASON = 'Adding foreign key' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + create_table :user_activities do |t| + t.belongs_to :user, index: { unique: true }, foreign_key: { on_delete: :cascade } + t.datetime :last_activity_at, null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 1c592dd5d6d..3ed28a02de2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1230,6 +1230,13 @@ ActiveRecord::Schema.define(version: 20170408033905) do add_index "uploads", ["model_id", "model_type"], name: "index_uploads_on_model_id_and_model_type", using: :btree add_index "uploads", ["path"], name: "index_uploads_on_path", using: :btree + create_table "user_activities", force: :cascade do |t| + t.integer "user_id" + t.datetime "last_activity_at", null: false + end + + add_index "user_activities", ["user_id"], name: "index_user_activities_on_user_id", unique: true, using: :btree + create_table "user_agent_details", force: :cascade do |t| t.string "user_agent", null: false t.string "ip_address", null: false @@ -1389,4 +1396,5 @@ ActiveRecord::Schema.define(version: 20170408033905) do add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" + add_foreign_key "user_activities", "users", on_delete: :cascade end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 215bc03d0e9..d374d183dcd 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -15,6 +15,16 @@ module API # project - project path with namespace # action - git action (git-upload-pack or git-receive-pack) # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList + helpers do + def log_user_activity(actor) + commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS + + Gitlab::GitAccess::PUSH_COMMANDS + + Gitlab::GitAccess::GIT_ANNEX_COMMANDS + + ::Users::ActivityService.new(actor, 'Git SSH').execute if commands.include?(params[:action]) + end + end + post "/allowed" do status 200 @@ -40,6 +50,8 @@ module API response = { status: access_status.status, message: access_status.message } if access_status.status + log_user_activity(actor) + # Return the repository full path so that gitlab-shell has it when # handling ssh commands response[:repository_path] = diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 9c16a7bc08b..3459f30ef42 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -37,6 +37,12 @@ describe SessionsController do subject.sign_out user end end + + it 'updates the user activity' do + expect do + post(:create, user: { login: user.username, password: user.password }) + end.to change { user.reload.last_activity_at }.from(nil) + end end end diff --git a/spec/factories/user_activities.rb b/spec/factories/user_activities.rb new file mode 100644 index 00000000000..32ad8c6a3b2 --- /dev/null +++ b/spec/factories/user_activities.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :user_activity do + last_activity_at { Time.now } + user + end +end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 4be67df5a00..63f566da7a8 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -151,6 +151,11 @@ describe API::Internal, api: true do context "access granted" do before do project.team << [user, :developer] + Timecop.freeze + end + + after do + Timecop.return end context 'with env passed as a JSON' do @@ -176,6 +181,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) + expect(key.user.reload.last_activity_at.to_i).to eq(Time.now.to_i) end end @@ -186,6 +192,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) + expect(key.user.reload.last_activity_at.to_i).to eq(Time.now.to_i) end end @@ -196,6 +203,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(key.user.reload.last_activity_at.to_i).to eq(Time.now.to_i) end end @@ -206,6 +214,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(key.user.reload.last_activity_at.to_i).to eq(Time.now.to_i) end context 'project as /namespace/project' do @@ -241,6 +250,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey + expect(key.user.reload.last_activity_at).to be_nil end end @@ -250,6 +260,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey + expect(key.user.reload.last_activity_at).to be_nil end end end @@ -267,6 +278,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey + expect(key.user.reload.last_activity_at).to be_nil end end @@ -276,6 +288,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey + expect(key.user.reload.last_activity_at).to be_nil end end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 006d6a6af1c..9f2857ce2e7 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -157,6 +157,12 @@ describe 'Git HTTP requests', lib: true do expect(response).to have_http_status(:ok) end end + + it 'updates the user last activity' do + download(path, env) do |response| + expect(user.reload.last_activity_at).not_to be_nil + end + end end context 'but only project members are allowed' do diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index f2c2009bcbf..54e5c0b236b 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -129,4 +129,17 @@ describe EventCreateService, services: true do it { expect { subject }.to change { Event.count }.from(0).to(1) } end end + + describe '#push' do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + it 'creates a new event' do + expect { service.push(project, user, {}) }.to change { Event.count } + end + + it 'updates user last activity' do + expect { service.push(project, user, {}) }.to change { user.last_activity_at } + end + end end diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb new file mode 100644 index 00000000000..68399118579 --- /dev/null +++ b/spec/services/users/activity_service_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Users::ActivityService, services: true do + let(:user) { create(:user) } + subject(:service) { described_class.new(user, 'type') } + + describe '#execute' do + context 'when last activity is nil' do + it 'sets the last activity timestamp' do + service.execute + + expect(user.last_activity_at).not_to be_nil + end + end + + context 'when activity_at is not nil' do + it 'updates the activity multiple times' do + activity = create(:user_activity, user: user) + + Timecop.travel(activity.last_activity_at + 1.minute) do + expect { service.execute }.to change { user.reload.last_activity_at } + end + end + end + end +end From 3cb84e06b7a118fb46b4e1e0d4885026c9d4a4d1 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 25 Nov 2016 17:10:25 +0100 Subject: [PATCH 050/168] Remove user activities table and use redis instead of PG for recording activities Refactored specs and added a post deployment migration to remove the activity users table. --- app/models/user.rb | 10 ++++-- app/models/user_activity.rb | 19 ----------- app/services/users/activity_service.rb | 6 +--- ...161128170531_drop_user_activities_table.rb | 23 +++++++++++++ db/schema.rb | 8 ----- lib/api/helpers/internal_helpers.rb | 7 ++++ spec/controllers/sessions_controller_spec.rb | 6 ++-- spec/factories/user_activities.rb | 6 ---- spec/requests/api/internal_spec.rb | 20 ++++++----- spec/services/event_create_service_spec.rb | 28 ++++++++------- spec/services/users/activity_service_spec.rb | 34 ++++++++++++++----- spec/support/user_activities_helpers.rb | 17 ++++++++++ 12 files changed, 111 insertions(+), 73 deletions(-) delete mode 100644 app/models/user_activity.rb create mode 100644 db/post_migrate/20161128170531_drop_user_activities_table.rb delete mode 100644 spec/factories/user_activities.rb create mode 100644 spec/support/user_activities_helpers.rb diff --git a/app/models/user.rb b/app/models/user.rb index 1dde5c89699..83e17bcb04e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -101,7 +101,6 @@ class User < ActiveRecord::Base has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" - has_one :user_activity, dependent: :destroy # Issues that a user owns are expected to be moved to the "ghost" user before # the user is destroyed. If the user owns any issues during deletion, this @@ -159,7 +158,6 @@ class User < ActiveRecord::Base alias_attribute :private_token, :authentication_token delegate :path, to: :namespace, allow_nil: true, prefix: true - delegate :last_activity_at, to: :user_activity, allow_nil: true state_machine :state, initial: :active do event :block do @@ -951,6 +949,14 @@ class User < ActiveRecord::Base end end + def record_activity + Gitlab::Redis.with do |redis| + redis.zadd('user/activities', Time.now.to_i, self.username) + end + end + + private + def access_level=(new_level) new_level = new_level.to_s return unless %w(admin regular).include?(new_level) diff --git a/app/models/user_activity.rb b/app/models/user_activity.rb deleted file mode 100644 index c5fdbff0feb..00000000000 --- a/app/models/user_activity.rb +++ /dev/null @@ -1,19 +0,0 @@ -class UserActivity < ActiveRecord::Base - belongs_to :user, inverse_of: :user_activity - - validates :user, uniqueness: true, presence: true - validates :last_activity_at, presence: true - - # Updated version of http://apidock.com/rails/ActiveRecord/Timestamp/touch - # That accepts a new record. - def touch - current_time = current_time_from_proper_timezone - - if persisted? - update_column(:last_activity_at, current_time) - else - self.last_activity_at = current_time - save!(validate: false) - end - end -end diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb index b81f947cd01..483821b7f01 100644 --- a/app/services/users/activity_service.rb +++ b/app/services/users/activity_service.rb @@ -14,13 +14,9 @@ module Users private def record_activity - user_activity.touch + @author.record_activity Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username}") end - - def user_activity - UserActivity.find_or_initialize_by(user: @author) - end end end diff --git a/db/post_migrate/20161128170531_drop_user_activities_table.rb b/db/post_migrate/20161128170531_drop_user_activities_table.rb new file mode 100644 index 00000000000..3ece0722821 --- /dev/null +++ b/db/post_migrate/20161128170531_drop_user_activities_table.rb @@ -0,0 +1,23 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class DropUserActivitiesTable < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + drop_table :user_activities + end +end diff --git a/db/schema.rb b/db/schema.rb index 3ed28a02de2..1c592dd5d6d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1230,13 +1230,6 @@ ActiveRecord::Schema.define(version: 20170408033905) do add_index "uploads", ["model_id", "model_type"], name: "index_uploads_on_model_id_and_model_type", using: :btree add_index "uploads", ["path"], name: "index_uploads_on_path", using: :btree - create_table "user_activities", force: :cascade do |t| - t.integer "user_id" - t.datetime "last_activity_at", null: false - end - - add_index "user_activities", ["user_id"], name: "index_user_activities_on_user_id", unique: true, using: :btree - create_table "user_agent_details", force: :cascade do |t| t.string "user_agent", null: false t.string "ip_address", null: false @@ -1396,5 +1389,4 @@ ActiveRecord::Schema.define(version: 20170408033905) do add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" - add_foreign_key "user_activities", "users", on_delete: :cascade end diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 810e5063996..8f25bcf2f54 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -60,6 +60,13 @@ module API rescue JSON::ParserError {} end + + def log_user_activity(actor) + commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS + + Gitlab::GitAccess::GIT_ANNEX_COMMANDS + + ::Users::ActivityService.new(actor, 'Git SSH').execute if commands.include?(params[:action]) + end end end end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 3459f30ef42..d817099394a 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -16,7 +16,9 @@ describe SessionsController do end end - context 'when using valid password' do + context 'when using valid password', :redis do + include UserActivitiesHelpers + let(:user) { create(:user) } it 'authenticates user correctly' do @@ -41,7 +43,7 @@ describe SessionsController do it 'updates the user activity' do expect do post(:create, user: { login: user.username, password: user.password }) - end.to change { user.reload.last_activity_at }.from(nil) + end.to change { user_score }.from(0) end end end diff --git a/spec/factories/user_activities.rb b/spec/factories/user_activities.rb deleted file mode 100644 index 32ad8c6a3b2..00000000000 --- a/spec/factories/user_activities.rb +++ /dev/null @@ -1,6 +0,0 @@ -FactoryGirl.define do - factory :user_activity do - last_activity_at { Time.now } - user - end -end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 63f566da7a8..eee8bd51bff 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -147,7 +147,9 @@ describe API::Internal, api: true do end end - describe "POST /internal/allowed" do + describe "POST /internal/allowed", :redis do + include UserActivitiesHelpers + context "access granted" do before do project.team << [user, :developer] @@ -181,7 +183,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) - expect(key.user.reload.last_activity_at.to_i).to eq(Time.now.to_i) + expect(user_score).to be_zero end end @@ -192,7 +194,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) - expect(key.user.reload.last_activity_at.to_i).to eq(Time.now.to_i) + expect(user_score).not_to be_zero end end @@ -203,7 +205,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(key.user.reload.last_activity_at.to_i).to eq(Time.now.to_i) + expect(user_score).not_to be_zero end end @@ -214,7 +216,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(key.user.reload.last_activity_at.to_i).to eq(Time.now.to_i) + expect(user_score).to be_zero end context 'project as /namespace/project' do @@ -250,7 +252,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey - expect(key.user.reload.last_activity_at).to be_nil + expect(user_score).to be_zero end end @@ -260,7 +262,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey - expect(key.user.reload.last_activity_at).to be_nil + expect(user_score).to be_zero end end end @@ -278,7 +280,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey - expect(key.user.reload.last_activity_at).to be_nil + expect(user_score).to be_zero end end @@ -288,7 +290,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey - expect(key.user.reload.last_activity_at).to be_nil + expect(user_score).to be_zero end end end diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 54e5c0b236b..13c0aac2363 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe EventCreateService, services: true do + include UserActivitiesHelpers + let(:service) { EventCreateService.new } describe 'Issues' do @@ -111,6 +113,19 @@ describe EventCreateService, services: true do end end + describe '#push', :redis do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + it 'creates a new event' do + expect { service.push(project, user, {}) }.to change { Event.count } + end + + it 'updates user last activity' do + expect { service.push(project, user, {}) }.to change { user_score } + end + end + describe 'Project' do let(:user) { create :user } let(:project) { create(:empty_project) } @@ -129,17 +144,4 @@ describe EventCreateService, services: true do it { expect { subject }.to change { Event.count }.from(0).to(1) } end end - - describe '#push' do - let(:project) { create(:empty_project) } - let(:user) { create(:user) } - - it 'creates a new event' do - expect { service.push(project, user, {}) }.to change { Event.count } - end - - it 'updates user last activity' do - expect { service.push(project, user, {}) }.to change { user.last_activity_at } - end - end end diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb index 68399118579..07715ad4ca0 100644 --- a/spec/services/users/activity_service_spec.rb +++ b/spec/services/users/activity_service_spec.rb @@ -1,24 +1,40 @@ require 'spec_helper' describe Users::ActivityService, services: true do + include UserActivitiesHelpers + let(:user) { create(:user) } + subject(:service) { described_class.new(user, 'type') } - describe '#execute' do + describe '#execute', :redis do context 'when last activity is nil' do - it 'sets the last activity timestamp' do + before do + service.execute + end + + it 'sets the last activity timestamp for the user' do + expect(last_hour_members).to eq([user.username]) + end + + it 'updates the same user' do service.execute - expect(user.last_activity_at).not_to be_nil + expect(last_hour_members).to eq([user.username]) end - end - context 'when activity_at is not nil' do - it 'updates the activity multiple times' do - activity = create(:user_activity, user: user) + it 'updates the timestamp of an existing user' do + Timecop.freeze(Date.tomorrow) do + expect { service.execute }.to change { user_score }.to(Time.now.to_i) + end + end - Timecop.travel(activity.last_activity_at + 1.minute) do - expect { service.execute }.to change { user.reload.last_activity_at } + describe 'other user' do + it 'updates other user' do + other_user = create(:user) + described_class.new(other_user, 'type').execute + + expect(last_hour_members).to match_array([user.username, other_user.username]) end end end diff --git a/spec/support/user_activities_helpers.rb b/spec/support/user_activities_helpers.rb new file mode 100644 index 00000000000..ef88dab6c91 --- /dev/null +++ b/spec/support/user_activities_helpers.rb @@ -0,0 +1,17 @@ +module UserActivitiesHelpers + def last_hour_members + Gitlab::Redis.with do |redis| + redis.zrangebyscore(user_activities_key, 1.hour.ago.to_i, Time.now.to_i) + end + end + + def user_score + Gitlab::Redis.with do |redis| + redis.zscore(user_activities_key, user.username).to_i + end + end + + def user_activities_key + 'user/activities' + end +end From 91ac0e038ab51dd2f30f2bb7c91837fa588ca250 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 12 Apr 2017 12:19:45 +0100 Subject: [PATCH 051/168] Port 'Add user activities API' to CE CE port of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/962 --- app/models/user.rb | 4 +- doc/api/users.md | 53 ++++++ lib/api/entities.rb | 5 + lib/api/issues.rb | 2 +- lib/api/users.rb | 18 ++ lib/gitlab/pagination_delegate.rb | 65 ++++++++ lib/gitlab/user_activities/activity.rb | 16 ++ lib/gitlab/user_activities/activity_set.rb | 67 ++++++++ spec/lib/gitlab/pagination_delegate_spec.rb | 155 ++++++++++++++++++ .../user_activities/activity_set_spec.rb | 77 +++++++++ .../gitlab/user_activities/activity_spec.rb | 14 ++ spec/requests/api/users_spec.rb | 76 ++++----- spec/requests/api/users_spec.rb.rej | 124 ++++++++++++++ 13 files changed, 634 insertions(+), 42 deletions(-) create mode 100644 lib/gitlab/pagination_delegate.rb create mode 100644 lib/gitlab/user_activities/activity.rb create mode 100644 lib/gitlab/user_activities/activity_set.rb create mode 100644 spec/lib/gitlab/pagination_delegate_spec.rb create mode 100644 spec/lib/gitlab/user_activities/activity_set_spec.rb create mode 100644 spec/lib/gitlab/user_activities/activity_spec.rb create mode 100644 spec/requests/api/users_spec.rb.rej diff --git a/app/models/user.rb b/app/models/user.rb index 83e17bcb04e..2a351b679ea 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -950,9 +950,7 @@ class User < ActiveRecord::Base end def record_activity - Gitlab::Redis.with do |redis| - redis.zadd('user/activities', Time.now.to_i, self.username) - end + Gitlab::UserActivities::ActivitySet.record(self) end private diff --git a/doc/api/users.md b/doc/api/users.md index 2ada4d09c84..84dbc350e6b 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -986,3 +986,56 @@ Parameters: | --------- | ---- | -------- | ----------- | | `user_id` | integer | yes | The ID of the user | | `impersonation_token_id` | integer | yes | The ID of the impersonation token | + +### Get user activities (admin only) + +>**Note:** This API endpoint is only available on 8.15 (EE) and 9.1 (CE) and above. + +Get the last activity date for all users, sorted from oldest to newest. + +The activities that update the timestamp are: + + - Git HTTP/SSH activities (such as clone, push) + - User logging in into GitLab + +The data is stored in Redis and it depends on it for being recorded and displayed +over time. This means that we will lose the data if Redis gets flushed, or a custom +TTL is reached. + +By default, it shows the activity for all users in the last 6 months, but this can be +amended by using the `from` parameter. + +This function takes pagination parameters `page` and `per_page` to restrict the list of users. + + +``` +GET /user/activities +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `from` | string | no | Date string in the format YEAR-MONTH-DAY, e.g. `2016-03-11`. Defaults to 6 months ago. | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/user/activities +``` + +Example response: + +```json +[ + { + "username": "user1", + "last_activity_at": "2015-12-14 01:00:00" + }, + { + "username": "user2", + "last_activity_at": "2015-12-15 01:00:00" + }, + { + "username": "user3", + "last_activity_at": "2015-12-16 01:00:00" + } +] diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9919762cd82..939cedc1b27 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -18,6 +18,11 @@ module API expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization end + class UserActivity < Grape::Entity + expose :username + expose :last_activity_at + end + class Identity < Grape::Entity expose :provider, :extern_uid end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 05423c17449..244725bb292 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -35,7 +35,7 @@ module API optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue' optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue' optional :labels, type: String, desc: 'Comma-separated list of label names' - optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY' + optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY' optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' end diff --git a/lib/api/users.rb b/lib/api/users.rb index eedc59f8636..16fa1ef6836 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -534,6 +534,24 @@ module API email.destroy current_user.update_secondary_emails! end + + + desc 'Get a list of user activities' + params do + optional :from, type: String, desc: 'Date string in the format YEAR-MONTH-DAY' + use :pagination + end + get ":activities" do + authenticated_as_admin! + + activity_set = Gitlab::UserActivities::ActivitySet.new(from: params[:from], + page: params[:page], + per_page: params[:per_page]) + + add_pagination_headers(activity_set) + + present activity_set.activities, with: Entities::UserActivity + end end end end diff --git a/lib/gitlab/pagination_delegate.rb b/lib/gitlab/pagination_delegate.rb new file mode 100644 index 00000000000..d4913e908b2 --- /dev/null +++ b/lib/gitlab/pagination_delegate.rb @@ -0,0 +1,65 @@ +module Gitlab + class PaginationDelegate + DEFAULT_PER_PAGE = Kaminari.config.default_per_page + MAX_PER_PAGE = Kaminari.config.max_per_page + + def initialize(page:, per_page:, count:, options: {}) + @count = count + @options = { default_per_page: DEFAULT_PER_PAGE, + max_per_page: MAX_PER_PAGE }.merge(options) + + @per_page = sanitize_per_page(per_page) + @page = sanitize_page(page) + end + + def total_count + @count + end + + def total_pages + (total_count.to_f / @per_page).ceil + end + + def next_page + current_page + 1 unless last_page? + end + + def prev_page + current_page - 1 unless first_page? + end + + def current_page + @page + end + + def limit_value + @per_page + end + + def first_page? + current_page == 1 + end + + def last_page? + current_page >= total_pages + end + + def offset + (current_page - 1) * limit_value + end + + private + + def sanitize_per_page(per_page) + return @options[:default_per_page] unless per_page && per_page > 0 + + [@options[:max_per_page], per_page].min + end + + def sanitize_page(page) + return 1 unless page && page > 1 + + [total_pages, page].min + end + end +end diff --git a/lib/gitlab/user_activities/activity.rb b/lib/gitlab/user_activities/activity.rb new file mode 100644 index 00000000000..ec052870ee3 --- /dev/null +++ b/lib/gitlab/user_activities/activity.rb @@ -0,0 +1,16 @@ +module Gitlab + module UserActivities + class Activity + attr_reader :username + + def initialize(username, time) + @username = username + @time = time + end + + def last_activity_at + @last_activity_at ||= Time.at(@time).to_s(:db) + end + end + end +end diff --git a/lib/gitlab/user_activities/activity_set.rb b/lib/gitlab/user_activities/activity_set.rb new file mode 100644 index 00000000000..6b8e540e99b --- /dev/null +++ b/lib/gitlab/user_activities/activity_set.rb @@ -0,0 +1,67 @@ +module Gitlab + module UserActivities + class ActivitySet + delegate :total_count, + :total_pages, + :current_page, + :limit_value, + :first_page?, + :prev_page, + :last_page?, + :next_page, to: :pagination_delegate + + KEY = 'user/activities' + + def self.record(user) + Gitlab::Redis.with do |redis| + redis.zadd(KEY, Time.now.to_i, user.username) + end + end + + def initialize(from: nil, page: nil, per_page: nil) + @from = sanitize_date(from) + @to = Time.now.to_i + @page = page + @per_page = per_page + end + + def activities + @activities ||= raw_activities.map { |activity| Activity.new(*activity) } + end + + private + + def sanitize_date(date) + Time.strptime(date, "%Y-%m-%d").to_i + rescue TypeError, ArgumentError + default_from + end + + def pagination_delegate + @pagination_delegate ||= Gitlab::PaginationDelegate.new(page: @page, + per_page: @per_page, + count: count) + end + + def raw_activities + Gitlab::Redis.with do |redis| + redis.zrangebyscore(KEY, @from, @to, with_scores: true, limit: limit) + end + end + + def count + Gitlab::Redis.with do |redis| + redis.zcount(KEY, @from, @to) + end + end + + def limit + [pagination_delegate.offset, pagination_delegate.limit_value] + end + + def default_from + 6.months.ago.to_i + end + end + end +end diff --git a/spec/lib/gitlab/pagination_delegate_spec.rb b/spec/lib/gitlab/pagination_delegate_spec.rb new file mode 100644 index 00000000000..3220d611274 --- /dev/null +++ b/spec/lib/gitlab/pagination_delegate_spec.rb @@ -0,0 +1,155 @@ +require 'spec_helper' + +describe Gitlab::PaginationDelegate, lib: true do + context 'no data' do + let(:delegate) do + described_class.new(page: 1, + per_page: 10, + count: 0) + end + + it 'shows the correct total count' do + expect(delegate.total_count).to eq(0) + end + + it 'shows the correct total pages' do + expect(delegate.total_pages).to eq(0) + end + + it 'shows the correct next page' do + expect(delegate.next_page).to be_nil + end + + it 'shows the correct previous page' do + expect(delegate.prev_page).to be_nil + end + + it 'shows the correct current page' do + expect(delegate.current_page).to eq(1) + end + + it 'shows the correct limit value' do + expect(delegate.limit_value).to eq(10) + end + + it 'shows the correct first page' do + expect(delegate.first_page?).to be true + end + + it 'shows the correct last page' do + expect(delegate.last_page?).to be true + end + + it 'shows the correct offset' do + expect(delegate.offset).to eq(0) + end + end + + context 'with data' do + let(:delegate) do + described_class.new(page: 5, + per_page: 100, + count: 1000) + end + + it 'shows the correct total count' do + expect(delegate.total_count).to eq(1000) + end + + it 'shows the correct total pages' do + expect(delegate.total_pages).to eq(10) + end + + it 'shows the correct next page' do + expect(delegate.next_page).to eq(6) + end + + it 'shows the correct previous page' do + expect(delegate.prev_page).to eq(4) + end + + it 'shows the correct current page' do + expect(delegate.current_page).to eq(5) + end + + it 'shows the correct limit value' do + expect(delegate.limit_value).to eq(100) + end + + it 'shows the correct first page' do + expect(delegate.first_page?).to be false + end + + it 'shows the correct last page' do + expect(delegate.last_page?).to be false + end + + it 'shows the correct offset' do + expect(delegate.offset).to eq(400) + end + end + + context 'last page' do + let(:delegate) do + described_class.new(page: 10, + per_page: 100, + count: 1000) + end + + it 'shows the correct total count' do + expect(delegate.total_count).to eq(1000) + end + + it 'shows the correct total pages' do + expect(delegate.total_pages).to eq(10) + end + + it 'shows the correct next page' do + expect(delegate.next_page).to be_nil + end + + it 'shows the correct previous page' do + expect(delegate.prev_page).to eq(9) + end + + it 'shows the correct current page' do + expect(delegate.current_page).to eq(10) + end + + it 'shows the correct limit value' do + expect(delegate.limit_value).to eq(100) + end + + it 'shows the correct first page' do + expect(delegate.first_page?).to be false + end + + it 'shows the correct last page' do + expect(delegate.last_page?).to be true + end + + it 'shows the correct offset' do + expect(delegate.offset).to eq(900) + end + end + + context 'limits and defaults' do + it 'has a maximum limit per page' do + expect(described_class.new(page: nil, + per_page: 1000, + count: 0).limit_value).to eq(described_class::MAX_PER_PAGE) + end + + it 'has a default per page' do + expect(described_class.new(page: nil, + per_page: nil, + count: 0).limit_value).to eq(described_class::DEFAULT_PER_PAGE) + end + + it 'has a maximum page' do + expect(described_class.new(page: 100, + per_page: 10, + count: 1).current_page).to eq(1) + end + end +end diff --git a/spec/lib/gitlab/user_activities/activity_set_spec.rb b/spec/lib/gitlab/user_activities/activity_set_spec.rb new file mode 100644 index 00000000000..56745bdf0d1 --- /dev/null +++ b/spec/lib/gitlab/user_activities/activity_set_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Gitlab::UserActivities::ActivitySet, :redis, lib: true do + let(:user) { create(:user) } + + it 'shows the last user activity' do + Timecop.freeze do + user.record_activity + + expect(described_class.new.activities.first).to be_an_instance_of(Gitlab::UserActivities::Activity) + end + end + + context 'pagination delegation' do + let(:pagination_delegate) do + Gitlab::PaginationDelegate.new(page: 1, + per_page: 10, + count: 20) + end + + let(:delegated_methods) { %i[total_count total_pages current_page limit_value first_page? prev_page last_page? next_page] } + + before do + allow(described_class.new).to receive(:pagination_delegate).and_return(pagination_delegate) + end + + it 'includes the delegated methods' do + expect(described_class.new.public_methods).to include(*delegated_methods) + end + end + + context 'paginated activities' do + before do + Timecop.scale(3600) + + 7.times do + create(:user).record_activity + end + end + + after do + Timecop.return + end + + it 'shows the 5 oldest user activities paginated' do + expect(described_class.new(per_page: 5).activities.count).to eq(5) + end + + it 'shows the 2 reamining user activities paginated' do + expect(described_class.new(per_page: 5, page: 2).activities.count).to eq(2) + end + + it 'shows the oldest first' do + activities = described_class.new.activities + + expect(activities.first.last_activity_at).to be < activities.last.last_activity_at + end + end + + context 'filter by date' do + before do + create(:user).record_activity + end + + it 'shows activities from today' do + today = Date.today.to_s("%Y-%m-%d") + + expect(described_class.new(from: today).activities.count).to eq(1) + end + + it 'filter activities from tomorrow' do + tomorrow = Date.tomorrow.to_s("%Y-%m-%d") + + expect(described_class.new(from: tomorrow).activities.count).to eq(0) + end + end +end diff --git a/spec/lib/gitlab/user_activities/activity_spec.rb b/spec/lib/gitlab/user_activities/activity_spec.rb new file mode 100644 index 00000000000..6a1150f50c1 --- /dev/null +++ b/spec/lib/gitlab/user_activities/activity_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Gitlab::UserActivities::Activity, :redis, lib: true do + let(:username) { 'user' } + let(:activity) { described_class.new('user', Time.new(2016, 12, 12).to_i) } + + it 'has the username' do + expect(activity.username).to eq(username) + end + + it 'has the last activity at' do + expect(activity.last_activity_at).to eq('2016-12-12 00:00:00') + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f793c0db2f3..a4e8d8e4156 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1,12 +1,12 @@ require 'spec_helper' -describe API::Users, api: true do +describe API::Users, api: true do include ApiHelpers - let(:user) { create(:user) } + let(:user) { create(:user) } let(:admin) { create(:admin) } - let(:key) { create(:key, user: user) } - let(:email) { create(:email, user: user) } + let(:key) { create(:key, user: user) } + let(:email) { create(:email, user: user) } let(:omniauth_user) { create(:omniauth_user) } let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') } let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } @@ -129,7 +129,7 @@ describe API::Users, api: true do end describe "POST /users" do - before{ admin } + before { admin } it "creates user" do expect do @@ -214,9 +214,9 @@ describe API::Users, api: true do it "does not create user with invalid email" do post api('/users', admin), - email: 'invalid email', - password: 'password', - name: 'test' + email: 'invalid email', + password: 'password', + name: 'test' expect(response).to have_http_status(400) end @@ -242,12 +242,12 @@ describe API::Users, api: true do it 'returns 400 error if user does not validate' do post api('/users', admin), - password: 'pass', - email: 'test@example.com', - username: 'test!', - name: 'test', - bio: 'g' * 256, - projects_limit: -1 + password: 'pass', + email: 'test@example.com', + username: 'test!', + name: 'test', + bio: 'g' * 256, + projects_limit: -1 expect(response).to have_http_status(400) expect(json_response['message']['password']). to eq(['is too short (minimum is 8 characters)']) @@ -267,19 +267,19 @@ describe API::Users, api: true do context 'with existing user' do before do post api('/users', admin), - email: 'test@example.com', - password: 'password', - username: 'test', - name: 'foo' + email: 'test@example.com', + password: 'password', + username: 'test', + name: 'foo' end it 'returns 409 conflict error if user with same email exists' do expect do post api('/users', admin), - name: 'foo', - email: 'test@example.com', - password: 'password', - username: 'foo' + name: 'foo', + email: 'test@example.com', + password: 'password', + username: 'foo' end.to change { User.count }.by(0) expect(response).to have_http_status(409) expect(json_response['message']).to eq('Email has already been taken') @@ -288,10 +288,10 @@ describe API::Users, api: true do it 'returns 409 conflict error if same username exists' do expect do post api('/users', admin), - name: 'foo', - email: 'foo@example.com', - password: 'password', - username: 'test' + name: 'foo', + email: 'foo@example.com', + password: 'password', + username: 'test' end.to change { User.count }.by(0) expect(response).to have_http_status(409) expect(json_response['message']).to eq('Username has already been taken') @@ -416,12 +416,12 @@ describe API::Users, api: true do it 'returns 400 error if user does not validate' do put api("/users/#{user.id}", admin), - password: 'pass', - email: 'test@example.com', - username: 'test!', - name: 'test', - bio: 'g' * 256, - projects_limit: -1 + password: 'pass', + email: 'test@example.com', + username: 'test!', + name: 'test', + bio: 'g' * 256, + projects_limit: -1 expect(response).to have_http_status(400) expect(json_response['message']['password']). to eq(['is too short (minimum is 8 characters)']) @@ -488,7 +488,7 @@ describe API::Users, api: true do key_attrs = attributes_for :key expect do post api("/users/#{user.id}/keys", admin), key_attrs - end.to change{ user.keys.count }.by(1) + end.to change { user.keys.count }.by(1) end it "returns 400 for invalid ID" do @@ -580,7 +580,7 @@ describe API::Users, api: true do email_attrs = attributes_for :email expect do post api("/users/#{user.id}/emails", admin), email_attrs - end.to change{ user.emails.count }.by(1) + end.to change { user.emails.count }.by(1) end it "returns a 400 for invalid ID" do @@ -842,7 +842,7 @@ describe API::Users, api: true do key_attrs = attributes_for :key expect do post api("/user/keys", user), key_attrs - end.to change{ user.keys.count }.by(1) + end.to change { user.keys.count }.by(1) expect(response).to have_http_status(201) end @@ -880,7 +880,7 @@ describe API::Users, api: true do delete api("/user/keys/#{key.id}", user) expect(response).to have_http_status(204) - end.to change{user.keys.count}.by(-1) + end.to change { user.keys.count}.by(-1) end it "returns 404 if key ID not found" do @@ -963,7 +963,7 @@ describe API::Users, api: true do email_attrs = attributes_for :email expect do post api("/user/emails", user), email_attrs - end.to change{ user.emails.count }.by(1) + end.to change { user.emails.count }.by(1) expect(response).to have_http_status(201) end @@ -989,7 +989,7 @@ describe API::Users, api: true do delete api("/user/emails/#{email.id}", user) expect(response).to have_http_status(204) - end.to change{user.emails.count}.by(-1) + end.to change { user.emails.count}.by(-1) end it "returns 404 if email ID not found" do diff --git a/spec/requests/api/users_spec.rb.rej b/spec/requests/api/users_spec.rb.rej new file mode 100644 index 00000000000..f7ade32ce42 --- /dev/null +++ b/spec/requests/api/users_spec.rb.rej @@ -0,0 +1,124 @@ +diff a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb (rejected hunks) +@@ -1,12 +1,12 @@ + require 'spec_helper' + +-describe API::Users, api: true do ++describe API::Users, api: true do + include ApiHelpers + +- let(:user) { create(:user) } ++ let(:user) { create(:user) } + let(:admin) { create(:admin) } +- let(:key) { create(:key, user: user) } +- let(:email) { create(:email, user: user) } ++ let(:key) { create(:key, user: user) } ++ let(:email) { create(:email, user: user) } + let(:omniauth_user) { create(:omniauth_user) } + let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') } + let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } +@@ -827,7 +827,7 @@ describe API::Users, api: true do + user.save + expect do + delete api("/user/keys/#{key.id}", user) +- end.to change{user.keys.count}.by(-1) ++ end.to change { user.keys.count }.by(-1) + expect(response).to have_http_status(200) + end + +@@ -931,7 +931,7 @@ describe API::Users, api: true do + user.save + expect do + delete api("/user/emails/#{email.id}", user) +- end.to change{user.emails.count}.by(-1) ++ end.to change { user.emails.count }.by(-1) + expect(response).to have_http_status(200) + end + +@@ -984,7 +984,7 @@ describe API::Users, api: true do + end + + describe 'PUT /users/:id/unblock' do +- let(:blocked_user) { create(:user, state: 'blocked') } ++ let(:blocked_user) { create(:user, state: 'blocked') } + before { admin } + + it 'unblocks existing user' do +@@ -1100,4 +1100,78 @@ describe API::Users, api: true do + expect(json_response['message']).to eq('404 User Not Found') + end + end ++ ++ context "user activities", :redis do ++ it_behaves_like 'a paginated resources' do ++ let(:request) { get api("/user/activities", admin) } ++ end ++ ++ context 'last activity as normal user' do ++ it 'has no permission' do ++ user.record_activity ++ ++ get api("/user/activities", user) ++ ++ expect(response).to have_http_status(403) ++ end ++ end ++ ++ context 'last activity as admin' do ++ it 'returns the last activity' do ++ allow(Time).to receive(:now).and_return(Time.new(2000, 1, 1)) ++ ++ user.record_activity ++ ++ get api("/user/activities", admin) ++ ++ activity = json_response.last ++ ++ expect(activity['username']).to eq(user.username) ++ expect(activity['last_activity_at']).to eq('2000-01-01 00:00:00') ++ end ++ end ++ ++ context 'last activities paginated', :redis do ++ let(:activity) { json_response.first } ++ let(:old_date) { 2.months.ago.to_date } ++ ++ before do ++ 5.times do |num| ++ Timecop.freeze(old_date + num) ++ ++ create(:user, username: num.to_s).record_activity ++ end ++ end ++ ++ after do ++ Timecop.return ++ end ++ ++ it 'returns 3 activities' do ++ get api("/user/activities?page=1&per_page=3", admin) ++ ++ expect(json_response.count).to eq(3) ++ end ++ ++ it 'contains the first activities' do ++ get api("/user/activities?page=1&per_page=3", admin) ++ ++ expect(json_response.map { |activity| activity['username'] }).to eq(%w[0 1 2]) ++ end ++ ++ it 'contains the last activities' do ++ get api("/user/activities?page=2&per_page=3", admin) ++ ++ expect(json_response.map { |activity| activity['username'] }).to eq(%w[3 4]) ++ end ++ ++ it 'contains activities created after user 3 was created' do ++ from = (old_date + 3).to_s("%Y-%m-%d") ++ ++ get api("/user/activities?page=1&per_page=5&from=#{from}", admin) ++ ++ expect(json_response.map { |activity| activity['username'] }).to eq(%w[3 4]) ++ end ++ end ++ end + end From cfe19b795e076b73df75ee57839640667283651c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 7 Mar 2017 19:34:43 +0100 Subject: [PATCH 052/168] Add a new Gitlab::UserActivities class to track user activities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This new class uses a Redis Hash instead of a Sorted Set. Signed-off-by: Rémy Coutable --- app/services/users/activity_service.rb | 2 +- lib/gitlab/user_activities.rb | 34 +++++ lib/gitlab/user_activities/activity.rb | 16 --- lib/gitlab/user_activities/activity_set.rb | 67 --------- spec/controllers/sessions_controller_spec.rb | 2 +- .../user_activities/activity_set_spec.rb | 77 ----------- .../gitlab/user_activities/activity_spec.rb | 14 -- spec/lib/gitlab/user_activities_spec.rb | 127 ++++++++++++++++++ spec/requests/api/internal_spec.rb | 18 ++- spec/services/event_create_service_spec.rb | 2 +- spec/services/users/activity_service_spec.rb | 14 +- .../matchers/user_activity_matchers.rb | 5 + spec/support/user_activities_helpers.rb | 18 +-- 13 files changed, 191 insertions(+), 205 deletions(-) create mode 100644 lib/gitlab/user_activities.rb delete mode 100644 lib/gitlab/user_activities/activity.rb delete mode 100644 lib/gitlab/user_activities/activity_set.rb delete mode 100644 spec/lib/gitlab/user_activities/activity_set_spec.rb delete mode 100644 spec/lib/gitlab/user_activities/activity_spec.rb create mode 100644 spec/lib/gitlab/user_activities_spec.rb create mode 100644 spec/support/matchers/user_activity_matchers.rb diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb index 483821b7f01..facf21a7f5c 100644 --- a/app/services/users/activity_service.rb +++ b/app/services/users/activity_service.rb @@ -14,7 +14,7 @@ module Users private def record_activity - @author.record_activity + Gitlab::UserActivities.record(@author.id) Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@author.id} (username: #{@author.username}") end diff --git a/lib/gitlab/user_activities.rb b/lib/gitlab/user_activities.rb new file mode 100644 index 00000000000..eb36ab9fded --- /dev/null +++ b/lib/gitlab/user_activities.rb @@ -0,0 +1,34 @@ +module Gitlab + class UserActivities + include Enumerable + + KEY = 'users:activities'.freeze + BATCH_SIZE = 500 + + def self.record(key, time = Time.now) + Gitlab::Redis.with do |redis| + redis.hset(KEY, key, time.to_i) + end + end + + def delete(*keys) + Gitlab::Redis.with do |redis| + redis.hdel(KEY, keys) + end + end + + def each + cursor = 0 + loop do + cursor, pairs = + Gitlab::Redis.with do |redis| + redis.hscan(KEY, cursor, count: BATCH_SIZE) + end + + Hash[pairs].each { |pair| yield pair } + + break if cursor == '0' + end + end + end +end diff --git a/lib/gitlab/user_activities/activity.rb b/lib/gitlab/user_activities/activity.rb deleted file mode 100644 index ec052870ee3..00000000000 --- a/lib/gitlab/user_activities/activity.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Gitlab - module UserActivities - class Activity - attr_reader :username - - def initialize(username, time) - @username = username - @time = time - end - - def last_activity_at - @last_activity_at ||= Time.at(@time).to_s(:db) - end - end - end -end diff --git a/lib/gitlab/user_activities/activity_set.rb b/lib/gitlab/user_activities/activity_set.rb deleted file mode 100644 index 6b8e540e99b..00000000000 --- a/lib/gitlab/user_activities/activity_set.rb +++ /dev/null @@ -1,67 +0,0 @@ -module Gitlab - module UserActivities - class ActivitySet - delegate :total_count, - :total_pages, - :current_page, - :limit_value, - :first_page?, - :prev_page, - :last_page?, - :next_page, to: :pagination_delegate - - KEY = 'user/activities' - - def self.record(user) - Gitlab::Redis.with do |redis| - redis.zadd(KEY, Time.now.to_i, user.username) - end - end - - def initialize(from: nil, page: nil, per_page: nil) - @from = sanitize_date(from) - @to = Time.now.to_i - @page = page - @per_page = per_page - end - - def activities - @activities ||= raw_activities.map { |activity| Activity.new(*activity) } - end - - private - - def sanitize_date(date) - Time.strptime(date, "%Y-%m-%d").to_i - rescue TypeError, ArgumentError - default_from - end - - def pagination_delegate - @pagination_delegate ||= Gitlab::PaginationDelegate.new(page: @page, - per_page: @per_page, - count: count) - end - - def raw_activities - Gitlab::Redis.with do |redis| - redis.zrangebyscore(KEY, @from, @to, with_scores: true, limit: limit) - end - end - - def count - Gitlab::Redis.with do |redis| - redis.zcount(KEY, @from, @to) - end - end - - def limit - [pagination_delegate.offset, pagination_delegate.limit_value] - end - - def default_from - 6.months.ago.to_i - end - end - end -end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index d817099394a..038132cffe0 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -43,7 +43,7 @@ describe SessionsController do it 'updates the user activity' do expect do post(:create, user: { login: user.username, password: user.password }) - end.to change { user_score }.from(0) + end.to change { user_activity(user) } end end end diff --git a/spec/lib/gitlab/user_activities/activity_set_spec.rb b/spec/lib/gitlab/user_activities/activity_set_spec.rb deleted file mode 100644 index 56745bdf0d1..00000000000 --- a/spec/lib/gitlab/user_activities/activity_set_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'spec_helper' - -describe Gitlab::UserActivities::ActivitySet, :redis, lib: true do - let(:user) { create(:user) } - - it 'shows the last user activity' do - Timecop.freeze do - user.record_activity - - expect(described_class.new.activities.first).to be_an_instance_of(Gitlab::UserActivities::Activity) - end - end - - context 'pagination delegation' do - let(:pagination_delegate) do - Gitlab::PaginationDelegate.new(page: 1, - per_page: 10, - count: 20) - end - - let(:delegated_methods) { %i[total_count total_pages current_page limit_value first_page? prev_page last_page? next_page] } - - before do - allow(described_class.new).to receive(:pagination_delegate).and_return(pagination_delegate) - end - - it 'includes the delegated methods' do - expect(described_class.new.public_methods).to include(*delegated_methods) - end - end - - context 'paginated activities' do - before do - Timecop.scale(3600) - - 7.times do - create(:user).record_activity - end - end - - after do - Timecop.return - end - - it 'shows the 5 oldest user activities paginated' do - expect(described_class.new(per_page: 5).activities.count).to eq(5) - end - - it 'shows the 2 reamining user activities paginated' do - expect(described_class.new(per_page: 5, page: 2).activities.count).to eq(2) - end - - it 'shows the oldest first' do - activities = described_class.new.activities - - expect(activities.first.last_activity_at).to be < activities.last.last_activity_at - end - end - - context 'filter by date' do - before do - create(:user).record_activity - end - - it 'shows activities from today' do - today = Date.today.to_s("%Y-%m-%d") - - expect(described_class.new(from: today).activities.count).to eq(1) - end - - it 'filter activities from tomorrow' do - tomorrow = Date.tomorrow.to_s("%Y-%m-%d") - - expect(described_class.new(from: tomorrow).activities.count).to eq(0) - end - end -end diff --git a/spec/lib/gitlab/user_activities/activity_spec.rb b/spec/lib/gitlab/user_activities/activity_spec.rb deleted file mode 100644 index 6a1150f50c1..00000000000 --- a/spec/lib/gitlab/user_activities/activity_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'spec_helper' - -describe Gitlab::UserActivities::Activity, :redis, lib: true do - let(:username) { 'user' } - let(:activity) { described_class.new('user', Time.new(2016, 12, 12).to_i) } - - it 'has the username' do - expect(activity.username).to eq(username) - end - - it 'has the last activity at' do - expect(activity.last_activity_at).to eq('2016-12-12 00:00:00') - end -end diff --git a/spec/lib/gitlab/user_activities_spec.rb b/spec/lib/gitlab/user_activities_spec.rb new file mode 100644 index 00000000000..187d88c8c58 --- /dev/null +++ b/spec/lib/gitlab/user_activities_spec.rb @@ -0,0 +1,127 @@ +require 'spec_helper' + +describe Gitlab::UserActivities, :redis, lib: true do + let(:now) { Time.now } + + describe '.record' do + context 'with no time given' do + it 'uses Time.now and records an activity in Redis' do + Timecop.freeze do + now # eager-load now + described_class.record(42) + end + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]]) + end + end + end + + context 'with a time given' do + it 'uses the given time and records an activity in Redis' do + described_class.record(42, now) + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]]) + end + end + end + end + + describe '.delete' do + context 'with a single key' do + context 'and key exists' do + it 'removes the pair from Redis' do + described_class.record(42, now) + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]]) + end + + subject.delete(42) + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) + end + end + end + + context 'and key does not exist' do + it 'removes the pair from Redis' do + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) + end + + subject.delete(42) + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) + end + end + end + end + + context 'with multiple keys' do + context 'and all keys exist' do + it 'removes the pair from Redis' do + described_class.record(41, now) + described_class.record(42, now) + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['41', now.to_i.to_s], ['42', now.to_i.to_s]]]) + end + + subject.delete(41, 42) + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) + end + end + end + + context 'and some keys does not exist' do + it 'removes the existing pair from Redis' do + described_class.record(42, now) + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]]) + end + + subject.delete(41, 42) + + Gitlab::Redis.with do |redis| + expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []]) + end + end + end + end + end + + describe 'Enumerable' do + before do + described_class.record(40, now) + described_class.record(41, now) + described_class.record(42, now) + end + + it 'allows to read the activities sequentially' do + expected = { '40' => now.to_i.to_s, '41' => now.to_i.to_s, '42' => now.to_i.to_s } + + actual = described_class.new.each_with_object({}) do |(key, time), actual| + actual[key] = time + end + + expect(actual).to eq(expected) + end + + context 'with many records' do + before do + 1_000.times { |i| described_class.record(i, now) } + end + + it 'is possible to loop through all the records' do + expect(described_class.new.count).to eq(1_000) + end + end + end +end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index eee8bd51bff..3d6010ede73 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -148,8 +148,6 @@ describe API::Internal, api: true do end describe "POST /internal/allowed", :redis do - include UserActivitiesHelpers - context "access granted" do before do project.team << [user, :developer] @@ -183,7 +181,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) - expect(user_score).to be_zero + expect(user).not_to have_an_activity_record end end @@ -194,7 +192,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) - expect(user_score).not_to be_zero + expect(user).to have_an_activity_record end end @@ -205,7 +203,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(user_score).not_to be_zero + expect(user).to have_an_activity_record end end @@ -216,7 +214,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(user_score).to be_zero + expect(user).not_to have_an_activity_record end context 'project as /namespace/project' do @@ -252,7 +250,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey - expect(user_score).to be_zero + expect(user).not_to have_an_activity_record end end @@ -262,7 +260,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey - expect(user_score).to be_zero + expect(user).not_to have_an_activity_record end end end @@ -280,7 +278,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey - expect(user_score).to be_zero + expect(user).not_to have_an_activity_record end end @@ -290,7 +288,7 @@ describe API::Internal, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey - expect(user_score).to be_zero + expect(user).not_to have_an_activity_record end end end diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 13c0aac2363..b06cefe071d 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -122,7 +122,7 @@ describe EventCreateService, services: true do end it 'updates user last activity' do - expect { service.push(project, user, {}) }.to change { user_score } + expect { service.push(project, user, {}) }.to change { user_activity(user) } end end diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb index 07715ad4ca0..8d67ebe3231 100644 --- a/spec/services/users/activity_service_spec.rb +++ b/spec/services/users/activity_service_spec.rb @@ -14,18 +14,18 @@ describe Users::ActivityService, services: true do end it 'sets the last activity timestamp for the user' do - expect(last_hour_members).to eq([user.username]) + expect(last_hour_user_ids).to eq([user.id]) end it 'updates the same user' do service.execute - expect(last_hour_members).to eq([user.username]) + expect(last_hour_user_ids).to eq([user.id]) end it 'updates the timestamp of an existing user' do Timecop.freeze(Date.tomorrow) do - expect { service.execute }.to change { user_score }.to(Time.now.to_i) + expect { service.execute }.to change { user_activity(user) }.to(Time.now.to_i.to_s) end end @@ -34,9 +34,15 @@ describe Users::ActivityService, services: true do other_user = create(:user) described_class.new(other_user, 'type').execute - expect(last_hour_members).to match_array([user.username, other_user.username]) + expect(last_hour_user_ids).to match_array([user.id, other_user.id]) end end end end + + def last_hour_user_ids + Gitlab::UserActivities.new. + select { |k, v| v >= 1.hour.ago.to_i.to_s }. + map { |k, _| k.to_i } + end end diff --git a/spec/support/matchers/user_activity_matchers.rb b/spec/support/matchers/user_activity_matchers.rb new file mode 100644 index 00000000000..ce3b683b6d2 --- /dev/null +++ b/spec/support/matchers/user_activity_matchers.rb @@ -0,0 +1,5 @@ +RSpec::Matchers.define :have_an_activity_record do |expected| + match do |user| + expect(Gitlab::UserActivities.new.find { |k, _| k == user.id.to_s }).to be_present + end +end diff --git a/spec/support/user_activities_helpers.rb b/spec/support/user_activities_helpers.rb index ef88dab6c91..f7ca9a31edd 100644 --- a/spec/support/user_activities_helpers.rb +++ b/spec/support/user_activities_helpers.rb @@ -1,17 +1,7 @@ module UserActivitiesHelpers - def last_hour_members - Gitlab::Redis.with do |redis| - redis.zrangebyscore(user_activities_key, 1.hour.ago.to_i, Time.now.to_i) - end - end - - def user_score - Gitlab::Redis.with do |redis| - redis.zscore(user_activities_key, user.username).to_i - end - end - - def user_activities_key - 'user/activities' + def user_activity(user) + Gitlab::UserActivities.new. + find { |k, _| k == user.id.to_s }&. + second end end From d4da926f48503125307fe3d4a4f3952df92fc1ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 7 Mar 2017 19:35:32 +0100 Subject: [PATCH 053/168] Add new ScheduleUpdateUserActivityWorker and UpdateUserActivityWorker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../schedule_update_user_activity_worker.rb | 12 +++++++ app/workers/update_user_activity_worker.rb | 28 +++++++++++++++ ...y-date-data-from-redis-to-the-database.yml | 4 +++ config/initializers/1_settings.rb | 5 +++ config/sidekiq_queues.yml | 1 + ...307125949_add_last_activity_on_to_users.rb | 9 +++++ db/schema.rb | 1 + ...hedule_update_user_activity_worker_spec.rb | 25 +++++++++++++ .../update_user_activity_worker_spec.rb | 35 +++++++++++++++++++ 9 files changed, 120 insertions(+) create mode 100644 app/workers/schedule_update_user_activity_worker.rb create mode 100644 app/workers/update_user_activity_worker.rb create mode 100644 changelogs/unreleased-ee/27790-periodically-save-last-activity-date-data-from-redis-to-the-database.yml create mode 100644 db/migrate/20170307125949_add_last_activity_on_to_users.rb create mode 100644 spec/workers/schedule_update_user_activity_worker_spec.rb create mode 100644 spec/workers/update_user_activity_worker_spec.rb diff --git a/app/workers/schedule_update_user_activity_worker.rb b/app/workers/schedule_update_user_activity_worker.rb new file mode 100644 index 00000000000..f1adae653b1 --- /dev/null +++ b/app/workers/schedule_update_user_activity_worker.rb @@ -0,0 +1,12 @@ +class ScheduleUpdateUserActivityWorker + include Sidekiq::Worker + include CronjobQueue + + def perform(batch_size = 500) + return if Gitlab::Geo.secondary? + + Gitlab::UserActivities.new.each_slice(batch_size) do |batch| + UpdateUserActivityWorker.perform_async(Hash[batch]) + end + end +end diff --git a/app/workers/update_user_activity_worker.rb b/app/workers/update_user_activity_worker.rb new file mode 100644 index 00000000000..9f48eb46393 --- /dev/null +++ b/app/workers/update_user_activity_worker.rb @@ -0,0 +1,28 @@ +class UpdateUserActivityWorker + include Sidekiq::Worker + include DedicatedSidekiqQueue + + def perform(pairs) + return if Gitlab::Geo.secondary? + + pairs = cast_data(pairs) + ids = pairs.keys + conditions = 'WHEN id = ? THEN ? ' * ids.length + + User.where(id: ids). + update_all([ + "last_activity_on = CASE #{conditions} ELSE last_activity_on END", + *pairs.to_a.flatten + ]) + + Gitlab::UserActivities.new.delete(*ids) + end + + private + + def cast_data(pairs) + pairs.each_with_object({}) do |(key, value), new_pairs| + new_pairs[key.to_i] = Time.at(value.to_i).to_s(:db) + end + end +end diff --git a/changelogs/unreleased-ee/27790-periodically-save-last-activity-date-data-from-redis-to-the-database.yml b/changelogs/unreleased-ee/27790-periodically-save-last-activity-date-data-from-redis-to-the-database.yml new file mode 100644 index 00000000000..15e13c6f85e --- /dev/null +++ b/changelogs/unreleased-ee/27790-periodically-save-last-activity-date-data-from-redis-to-the-database.yml @@ -0,0 +1,4 @@ +--- +title: Periodically persists users activity to users.last_activity_on +merge_request: 1597 +author: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c19ccbb8fd8..96d39f7c4b3 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -367,6 +367,11 @@ Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.send(:cron_random_weekly_time) Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker' +# Every day at 00:30 +Settings.cron_jobs['schedule_update_user_activity_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['schedule_update_user_activity_worker']['cron'] ||= '30 0 * * *' +Settings.cron_jobs['schedule_update_user_activity_worker']['job_class'] = 'ScheduleUpdateUserActivityWorker' + # # GitLab Shell # diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 9d2066a6490..bf8964d7f68 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -53,3 +53,4 @@ - [default, 1] - [pages, 1] - [system_hook_push, 1] + - [update_user_activity, 1] diff --git a/db/migrate/20170307125949_add_last_activity_on_to_users.rb b/db/migrate/20170307125949_add_last_activity_on_to_users.rb new file mode 100644 index 00000000000..0100836b473 --- /dev/null +++ b/db/migrate/20170307125949_add_last_activity_on_to_users.rb @@ -0,0 +1,9 @@ +class AddLastActivityOnToUsers < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :users, :last_activity_on, :date + end +end diff --git a/db/schema.rb b/db/schema.rb index 1c592dd5d6d..9ce9df68de8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1303,6 +1303,7 @@ ActiveRecord::Schema.define(version: 20170408033905) do t.string "organization" t.boolean "authorized_projects_populated" t.boolean "ghost" + t.date "last_activity_on" t.boolean "notified_of_own_activity" t.boolean "require_two_factor_authentication_from_group", default: false, null: false t.integer "two_factor_grace_period", default: 48, null: false diff --git a/spec/workers/schedule_update_user_activity_worker_spec.rb b/spec/workers/schedule_update_user_activity_worker_spec.rb new file mode 100644 index 00000000000..e583c3203aa --- /dev/null +++ b/spec/workers/schedule_update_user_activity_worker_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe ScheduleUpdateUserActivityWorker, :redis do + let(:now) { Time.now } + + before do + Gitlab::UserActivities.record('1', now) + Gitlab::UserActivities.record('2', now) + end + + it 'schedules UpdateUserActivityWorker once' do + expect(UpdateUserActivityWorker).to receive(:perform_async).with({ '1' => now.to_i.to_s, '2' => now.to_i.to_s }) + + subject.perform + end + + context 'when specifying a batch size' do + it 'schedules UpdateUserActivityWorker twice' do + expect(UpdateUserActivityWorker).to receive(:perform_async).with({ '1' => now.to_i.to_s }) + expect(UpdateUserActivityWorker).to receive(:perform_async).with({ '2' => now.to_i.to_s }) + + subject.perform(1) + end + end +end diff --git a/spec/workers/update_user_activity_worker_spec.rb b/spec/workers/update_user_activity_worker_spec.rb new file mode 100644 index 00000000000..43e9511f116 --- /dev/null +++ b/spec/workers/update_user_activity_worker_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe UpdateUserActivityWorker, :redis do + let(:user_active_2_days_ago) { create(:user, current_sign_in_at: 10.months.ago) } + let(:user_active_yesterday_1) { create(:user) } + let(:user_active_yesterday_2) { create(:user) } + let(:user_active_today) { create(:user) } + let(:data) do + { + user_active_2_days_ago.id.to_s => 2.days.ago.at_midday.to_i.to_s, + user_active_yesterday_1.id.to_s => 1.day.ago.at_midday.to_i.to_s, + user_active_yesterday_2.id.to_s => 1.day.ago.at_midday.to_i.to_s, + user_active_today.id.to_s => Time.now.to_i.to_s + } + end + + it 'updates users.last_activity_on' do + subject.perform(data) + + aggregate_failures do + expect(user_active_2_days_ago.reload.last_activity_on).to eq(2.days.ago.to_date) + expect(user_active_yesterday_1.reload.last_activity_on).to eq(1.day.ago.to_date) + expect(user_active_yesterday_2.reload.last_activity_on).to eq(1.day.ago.to_date) + expect(user_active_today.reload.reload.last_activity_on).to eq(Date.today) + end + end + + it 'deletes the pairs from Redis' do + data.each { |id, time| Gitlab::UserActivities.record(id, time) } + + subject.perform(data) + + expect(Gitlab::UserActivities.new.to_a).to be_empty + end +end From 814212621f5f07bf8d84443644666be62674cf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 27 Mar 2017 15:43:10 +0200 Subject: [PATCH 054/168] Expose `last_activity_on` in the User API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/api/users.md | 24 ++++++++++-------- lib/api/entities.rb | 4 ++- lib/api/users.rb | 14 +++++------ spec/requests/api/users_spec.rb | 43 +++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/doc/api/users.md b/doc/api/users.md index 84dbc350e6b..a79d31d19fa 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -72,6 +72,7 @@ GET /users "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", + "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, "current_sign_in_at": "2012-06-02T06:36:55Z", @@ -104,6 +105,7 @@ GET /users "organization": "", "last_sign_in_at": null, "confirmed_at": "2012-05-30T16:53:06.148Z", + "last_activity_on": "2012-05-23", "color_scheme_id": 3, "projects_limit": 100, "current_sign_in_at": "2014-03-19T17:54:13Z", @@ -196,6 +198,7 @@ Parameters: "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", + "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, "current_sign_in_at": "2012-06-02T06:36:55Z", @@ -320,6 +323,7 @@ GET /user "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", + "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, "current_sign_in_at": "2012-06-02T06:36:55Z", @@ -365,6 +369,7 @@ GET /user "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", + "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, "current_sign_in_at": "2012-06-02T06:36:55Z", @@ -998,16 +1003,9 @@ The activities that update the timestamp are: - Git HTTP/SSH activities (such as clone, push) - User logging in into GitLab -The data is stored in Redis and it depends on it for being recorded and displayed -over time. This means that we will lose the data if Redis gets flushed, or a custom -TTL is reached. - By default, it shows the activity for all users in the last 6 months, but this can be amended by using the `from` parameter. -This function takes pagination parameters `page` and `per_page` to restrict the list of users. - - ``` GET /user/activities ``` @@ -1028,14 +1026,20 @@ Example response: [ { "username": "user1", - "last_activity_at": "2015-12-14 01:00:00" + "last_activity_on": "2015-12-14", + "last_activity_at": "2015-12-14" }, { "username": "user2", - "last_activity_at": "2015-12-15 01:00:00" + "last_activity_on": "2015-12-15", + "last_activity_at": "2015-12-15" }, { "username": "user3", - "last_activity_at": "2015-12-16 01:00:00" + "last_activity_on": "2015-12-16", + "last_activity_at": "2015-12-16" } ] +``` + +Please note that `last_activity_at` is deprecated, please use `last_activity_on`. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 939cedc1b27..64ab6f01eb5 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -20,7 +20,8 @@ module API class UserActivity < Grape::Entity expose :username - expose :last_activity_at + expose :last_activity_on + expose :last_activity_on, as: :last_activity_at # Back-compat end class Identity < Grape::Entity @@ -30,6 +31,7 @@ module API class UserPublic < User expose :last_sign_in_at expose :confirmed_at + expose :last_activity_on expose :email expose :color_scheme_id, :projects_limit, :current_sign_in_at expose :identities, using: Entities::Identity diff --git a/lib/api/users.rb b/lib/api/users.rb index 16fa1ef6836..bcfbd9ab3c5 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -538,19 +538,17 @@ module API desc 'Get a list of user activities' params do - optional :from, type: String, desc: 'Date string in the format YEAR-MONTH-DAY' + optional :from, type: DateTime, default: 6.months.ago, desc: 'Date string in the format YEAR-MONTH-DAY' use :pagination end - get ":activities" do + get "activities" do authenticated_as_admin! - activity_set = Gitlab::UserActivities::ActivitySet.new(from: params[:from], - page: params[:page], - per_page: params[:per_page]) + activities = User. + where(User.arel_table[:last_activity_on].gteq(params[:from])). + reorder(last_activity_on: :asc) - add_pagination_headers(activity_set) - - present activity_set.activities, with: Entities::UserActivity + present paginate(activities), with: Entities::UserActivity end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index a4e8d8e4156..ea9b886e995 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1158,6 +1158,49 @@ describe API::Users, api: true do end end + context "user activities", :redis do + let!(:old_active_user) { create(:user, last_activity_on: Time.utc(2000, 1, 1)) } + let!(:newly_active_user) { create(:user, last_activity_on: 2.days.ago.midday) } + + context 'last activity as normal user' do + it 'has no permission' do + get api("/user/activities", user) + + expect(response).to have_http_status(403) + end + end + + context 'as admin' do + it 'returns the activities from the last 6 months' do + get api("/user/activities", admin) + + expect(response).to include_pagination_headers + expect(json_response.size).to eq(1) + + activity = json_response.last + + expect(activity['username']).to eq(newly_active_user.username) + expect(activity['last_activity_on']).to eq(2.days.ago.to_date.to_s) + expect(activity['last_activity_at']).to eq(2.days.ago.to_date.to_s) + end + + context 'passing a :from parameter' do + it 'returns the activities from the given date' do + get api("/user/activities?from=2000-1-1", admin) + + expect(response).to include_pagination_headers + expect(json_response.size).to eq(2) + + activity = json_response.first + + expect(activity['username']).to eq(old_active_user.username) + expect(activity['last_activity_on']).to eq(Time.utc(2000, 1, 1).to_date.to_s) + expect(activity['last_activity_at']).to eq(Time.utc(2000, 1, 1).to_date.to_s) + end + end + end + end + describe 'GET /users/:user_id/impersonation_tokens' do let!(:active_personal_access_token) { create(:personal_access_token, user: user) } let!(:revoked_personal_access_token) { create(:personal_access_token, :revoked, user: user) } From 73c57fd3b0c6f4e66147f5eb0360ce99d26123b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 24 Mar 2017 18:04:57 +0100 Subject: [PATCH 055/168] Add a post-deploy migration to migrate from former Redis activity to DB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- ...er_activities_to_users_last_activity_on.rb | 87 +++++++++++++++++++ ...tivities_to_users_last_activity_on_spec.rb | 49 +++++++++++ 2 files changed, 136 insertions(+) create mode 100644 db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb create mode 100644 spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb diff --git a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb new file mode 100644 index 00000000000..9ad36482c8a --- /dev/null +++ b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb @@ -0,0 +1,87 @@ +class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + DOWNTIME = false + USER_ACTIVITY_SET_KEY = 'user/activities'.freeze + ACTIVITIES_PER_PAGE = 100 + TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED = Time.utc(2016, 12, 1) + + def up + return if activities_count(TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED, Time.now).zero? + + day = Time.at(activities(TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED, Time.now).first.second) + + transaction do + while day <= Time.now.utc.tomorrow + persist_last_activity_on(day: day) + day = day.tomorrow + end + end + end + + def down + # This ensures we don't lock all users for the duration of the migration. + update_column_in_batches(:users, :last_activity_on, nil) do |table, query| + query.where(table[:last_activity_on].not_eq(nil)) + end + end + + private + + def persist_last_activity_on(day:, page: 1) + activities_count = activities_count(day.at_beginning_of_day, day.at_end_of_day) + + return if activities_count.zero? + + activities = activities(day.at_beginning_of_day, day.at_end_of_day, page: page) + + update_sql = + Arel::UpdateManager.new(ActiveRecord::Base). + table(users_table). + set(users_table[:last_activity_on] => day.to_date). + where(users_table[:username].in(activities.map(&:first))). + to_sql + + connection.exec_update(update_sql, self.class.name, []) + + unless last_page?(page, activities_count) + persist_last_activity_on(day: day, page: page + 1) + end + end + + def users_table + @users_table ||= Arel::Table.new(:users) + end + + def activities(from, to, page: 1) + Gitlab::Redis.with do |redis| + redis.zrangebyscore(USER_ACTIVITY_SET_KEY, from.to_i, to.to_i, + with_scores: true, + limit: limit(page)) + end + end + + def activities_count(from, to) + Gitlab::Redis.with do |redis| + redis.zcount(USER_ACTIVITY_SET_KEY, from.to_i, to.to_i) + end + end + + def limit(page) + [offset(page), ACTIVITIES_PER_PAGE] + end + + def total_pages(count) + (count.to_f / ACTIVITIES_PER_PAGE).ceil + end + + def last_page?(page, count) + page >= total_pages(count) + end + + def offset(page) + (page - 1) * ACTIVITIES_PER_PAGE + end +end diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb new file mode 100644 index 00000000000..1db9bc002ae --- /dev/null +++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb @@ -0,0 +1,49 @@ +# encoding: utf-8 + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb') + +describe MigrateUserActivitiesToUsersLastActivityOn, :redis do + let(:migration) { described_class.new } + let!(:user_active_1) { create(:user) } + let!(:user_active_2) { create(:user) } + + def record_activity(user, time) + Gitlab::Redis.with do |redis| + redis.zadd(described_class::USER_ACTIVITY_SET_KEY, time.to_i, user.username) + end + end + + around do |example| + Timecop.freeze { example.run } + end + + before do + record_activity(user_active_1, described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 2.months) + record_activity(user_active_2, described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 3.months) + mute_stdout { migration.up } + end + + describe '#up' do + it 'fills last_activity_on from the legacy Redis Sorted Set' do + expect(user_active_1.reload.last_activity_on).to eq((described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 2.months).to_date) + expect(user_active_2.reload.last_activity_on).to eq((described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 3.months).to_date) + end + end + + describe '#down' do + it 'sets last_activity_on to NULL for all users' do + mute_stdout { migration.down } + + expect(user_active_1.reload.last_activity_on).to be_nil + expect(user_active_2.reload.last_activity_on).to be_nil + end + end + + def mute_stdout + orig_stdout = $stdout + $stdout = StringIO.new + yield + $stdout = orig_stdout + end +end From 81022d76671a3c8961f6969542f8968901668a5f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 30 Mar 2017 16:48:33 +0100 Subject: [PATCH 056/168] Add user cohorts table to admin area This table shows the percentage of users who registered in the last twelve months, who last signed in during or later than each of those twelve months, by month. It is only enabled when the usage ping is enabled, and the page also shows pretty-printed usage ping data. The cohorts table is generated in Ruby from some basic SQL queries, because performing the gap-filling and running sums needed in both MySQL and Postgres is painful. --- app/assets/javascripts/dispatcher.js | 1 + .../admin/application_settings_controller.rb | 7 ++- .../admin/user_cohorts_controller.rb | 7 +++ app/services/user_cohorts_service.rb | 49 +++++++++++++++++++ .../application_settings/_form.html.haml | 2 +- app/views/admin/dashboard/_head.html.haml | 4 ++ .../user_cohorts/_cohorts_table.html.haml | 37 ++++++++++++++ .../admin/user_cohorts/_usage_ping.html.haml | 10 ++++ app/views/admin/user_cohorts/index.html.haml | 16 ++++++ changelogs/unreleased-ee/user-cohorts.yml | 4 ++ config/routes/admin.rb | 2 + spec/services/user_cohorts_service_spec.rb | 42 ++++++++++++++++ 12 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 app/controllers/admin/user_cohorts_controller.rb create mode 100644 app/services/user_cohorts_service.rb create mode 100644 app/views/admin/user_cohorts/_cohorts_table.html.haml create mode 100644 app/views/admin/user_cohorts/_usage_ping.html.haml create mode 100644 app/views/admin/user_cohorts/index.html.haml create mode 100644 changelogs/unreleased-ee/user-cohorts.yml create mode 100644 spec/services/user_cohorts_service_spec.rb diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 9d8f965dee0..6c94975d851 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -366,6 +366,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); new Admin(); switch (path[1]) { case 'application_settings': + case 'user_cohorts': new gl.ApplicationSettings(); break; case 'groups': diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 73b03b41594..643993d035e 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -19,7 +19,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController def usage_data respond_to do |format| - format.html { render html: Gitlab::Highlight.highlight('payload.json', Gitlab::UsageData.to_json) } + format.html do + usage_data = Gitlab::UsageData.data + usage_data_json = params[:pretty] ? JSON.pretty_generate(usage_data) : usage_data.to_json + + render html: Gitlab::Highlight.highlight('payload.json', usage_data_json) + end format.json { render json: Gitlab::UsageData.to_json } end end diff --git a/app/controllers/admin/user_cohorts_controller.rb b/app/controllers/admin/user_cohorts_controller.rb new file mode 100644 index 00000000000..5dd6eedfb06 --- /dev/null +++ b/app/controllers/admin/user_cohorts_controller.rb @@ -0,0 +1,7 @@ +class Admin::UserCohortsController < Admin::ApplicationController + def index + if ApplicationSetting.current.usage_ping_enabled + @cohorts = UserCohortsService.new.execute(12) + end + end +end diff --git a/app/services/user_cohorts_service.rb b/app/services/user_cohorts_service.rb new file mode 100644 index 00000000000..7f84b6a0634 --- /dev/null +++ b/app/services/user_cohorts_service.rb @@ -0,0 +1,49 @@ +class UserCohortsService + def initialize + end + + def execute(months_included) + if Gitlab::Database.postgresql? + created_at_month = "CAST(DATE_TRUNC('month', created_at) AS date)" + current_sign_in_at_month = "CAST(DATE_TRUNC('month', current_sign_in_at) AS date)" + elsif Gitlab::Database.mysql? + created_at_month = "STR_TO_DATE(DATE_FORMAT(created_at, '%Y-%m-01'), '%Y-%m-%d')" + current_sign_in_at_month = "STR_TO_DATE(DATE_FORMAT(current_sign_in_at, '%Y-%m-01'), '%Y-%m-%d')" + end + + counts_by_month = + User + .where('created_at > ?', months_included.months.ago.end_of_month) + .group(created_at_month, current_sign_in_at_month) + .reorder("#{created_at_month} ASC", "#{current_sign_in_at_month} DESC") + .count + + cohorts = {} + months = Array.new(months_included) { |i| i.months.ago.beginning_of_month.to_date } + + months_included.times do + month = months.last + inactive = counts_by_month[[month, nil]] || 0 + + # Calculate a running sum of active users, so users active in later months + # count as active in this month, too. Start with the most recent month + # first, for calculating the running totals, and then reverse for + # displaying in the table. + activity_months = + months + .map { |activity_month| counts_by_month[[month, activity_month]] } + .reduce([]) { |result, total| result << result.last.to_i + total.to_i } + .reverse + + cohorts[month] = { + months: activity_months, + total: activity_months.first, + inactive: inactive + } + + months.pop + end + + cohorts + end +end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index f4e4bac62d7..13e9faa9642 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -477,7 +477,7 @@ diagrams in Asciidoc documents using an external PlantUML service. %fieldset - %legend Usage statistics + %legend#usage-statistics Usage statistics .form-group .col-sm-offset-2.col-sm-10 .checkbox diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml index 7893c1dee97..0c2e5efc052 100644 --- a/app/views/admin/dashboard/_head.html.haml +++ b/app/views/admin/dashboard/_head.html.haml @@ -27,3 +27,7 @@ = link_to admin_runners_path, title: 'Runners' do %span Runners + = nav_link path: 'user_cohorts#index' do + = link_to admin_user_cohorts_path, title: 'User cohorts' do + %span + User cohorts diff --git a/app/views/admin/user_cohorts/_cohorts_table.html.haml b/app/views/admin/user_cohorts/_cohorts_table.html.haml new file mode 100644 index 00000000000..a322ea9e5db --- /dev/null +++ b/app/views/admin/user_cohorts/_cohorts_table.html.haml @@ -0,0 +1,37 @@ +.bs-callout.clearfix + %p + User cohorts are shown for the last twelve months. Only users with + activity are counted in the cohort total; inactive users are counted + separately. + +.table-holder + %table.table + %thead + %tr + %th Registration month + %th Inactive users + %th Cohort total + %th Month 0 + %th Month 1 + %th Month 2 + %th Month 3 + %th Month 4 + %th Month 5 + %th Month 6 + %th Month 7 + %th Month 8 + %th Month 9 + %th Month 10 + %th Month 11 + %tbody + - @cohorts.each do |registration_month, cohort| + %tr + %td= registration_month.strftime('%b %Y') + %td= number_with_delimiter(cohort[:inactive]) + %td= number_with_delimiter(cohort[:total]) + - cohort[:months].each do |running_total| + %td + - next if cohort[:total].zero? + = number_to_percentage(100 * running_total / cohort[:total], precision: 0) + %br + (#{number_with_delimiter(running_total)}) diff --git a/app/views/admin/user_cohorts/_usage_ping.html.haml b/app/views/admin/user_cohorts/_usage_ping.html.haml new file mode 100644 index 00000000000..a95f81a7f49 --- /dev/null +++ b/app/views/admin/user_cohorts/_usage_ping.html.haml @@ -0,0 +1,10 @@ +%h2 Usage ping + +.bs-callout.clearfix + %p + User cohorts are shown because the usage ping is enabled. The data sent with + this is shown below. To disable this, visit + = succeed '.' do + = link_to 'application settings', admin_application_settings_path(anchor: 'usage-statistics') + +%pre.usage-data.js-syntax-highlight.code.highlight{ data: { endpoint: usage_data_admin_application_settings_path(format: :html, pretty: true) } } diff --git a/app/views/admin/user_cohorts/index.html.haml b/app/views/admin/user_cohorts/index.html.haml new file mode 100644 index 00000000000..dddcbd834f7 --- /dev/null +++ b/app/views/admin/user_cohorts/index.html.haml @@ -0,0 +1,16 @@ +- @no_container = true += render "admin/dashboard/head" + +%div{ class: container_class } + - if @cohorts + = render 'cohorts_table' + = render 'usage_ping' + - else + .bs-callout.bs-callout-warning.clearfix + %p + User cohorts are only shown when the + = link_to 'usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-data') + usage ping is enabled. It is currently disabled. To enable it and see + user cohorts, visit + = succeed '.' do + = link_to 'application settings', admin_application_settings_path(anchor: 'usage-statistics') diff --git a/changelogs/unreleased-ee/user-cohorts.yml b/changelogs/unreleased-ee/user-cohorts.yml new file mode 100644 index 00000000000..67d64600a4f --- /dev/null +++ b/changelogs/unreleased-ee/user-cohorts.yml @@ -0,0 +1,4 @@ +--- +title: Show user cohorts data when usage ping is enabled +merge_request: +author: diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 3c1c2ce2582..5b44d449b2b 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -106,6 +106,8 @@ namespace :admin do end end + resources :user_cohorts, only: :index + resources :builds, only: :index do collection do post :cancel_all diff --git a/spec/services/user_cohorts_service_spec.rb b/spec/services/user_cohorts_service_spec.rb new file mode 100644 index 00000000000..8d8d0de31cd --- /dev/null +++ b/spec/services/user_cohorts_service_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe UserCohortsService do + describe '#execute' do + def month_start(months_ago) + months_ago.months.ago.beginning_of_month.to_date + end + + # In the interests of speed and clarity, this example has minimal data. + it 'returns a list of user cohorts' do + 6.times do |months_ago| + months_ago_time = (months_ago * 2).months.ago + + create(:user, created_at: months_ago_time, current_sign_in_at: Time.now) + create(:user, created_at: months_ago_time, current_sign_in_at: months_ago_time) + end + + create(:user) # this user is inactive and belongs to the current month + + expected = { + month_start(11) => { months: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], total: 0, inactive: 0 }, + month_start(10) => { months: [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], total: 2, inactive: 0 }, + month_start(9) => { months: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], total: 0, inactive: 0 }, + month_start(8) => { months: [2, 1, 1, 1, 1, 1, 1, 1, 1], total: 2, inactive: 0 }, + month_start(7) => { months: [0, 0, 0, 0, 0, 0, 0, 0], total: 0, inactive: 0 }, + month_start(6) => { months: [2, 1, 1, 1, 1, 1, 1], total: 2, inactive: 0 }, + month_start(5) => { months: [0, 0, 0, 0, 0, 0], total: 0, inactive: 0 }, + month_start(4) => { months: [2, 1, 1, 1, 1], total: 2, inactive: 0 }, + month_start(3) => { months: [0, 0, 0, 0], total: 0, inactive: 0 }, + month_start(2) => { months: [2, 1, 1], total: 2, inactive: 0 }, + month_start(1) => { months: [0, 0], total: 0, inactive: 0 }, + month_start(0) => { months: [2], total: 2, inactive: 1 } + } + + result = described_class.new.execute(12) + + expect(result.length).to eq(12) + expect(result.keys).to all(be_a(Date)) + expect(result).to eq(expected) + end + end +end From 44f3fc499a333c1ba43f845f3cbbe87f8f75fbca Mon Sep 17 00:00:00 2001 From: Regis Freyd Date: Fri, 31 Mar 2017 15:10:51 -0400 Subject: [PATCH 057/168] Add user cohorts and usage ping documentation Still need final links all over the place. --- doc/administration/usageping/img/cohorts.png | Bin 0 -> 439635 bytes .../usageping/usage_ping_cohorts.md | 85 ++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 doc/administration/usageping/img/cohorts.png create mode 100644 doc/administration/usageping/usage_ping_cohorts.md diff --git a/doc/administration/usageping/img/cohorts.png b/doc/administration/usageping/img/cohorts.png new file mode 100644 index 0000000000000000000000000000000000000000..8bae7faff0770266d40de745d6c0d7ad80581cf1 GIT binary patch literal 439635 zcmd41bx>Si(=JMaB{;!hkT5s|cMlMT;O_43?!ki$4DJMXcY?dSyA19Q!ES!<_nmXT zs&jAE`~G)#)vmQ?t$w=Kdb)c{&-y7Z3qnOAM1p~VLH#ZvrU(Or$Or=iOZ5TaZ%-~y zffx)73Z8|isQhPkbn1N({CBA+D5-YbgO1z;aedtt_) zaf*B*i~sQPYaf__t_fFJtU9NZ9H|06rk>2u4D;mWN7(Rii+X3ka2odHZT)%1d777B zI*-$yP+W5l3XCM@NqRbiGJ(JK*Gy)*$F-#$*T()qEnT(HM5~n0`%UGhv`k%i!#ibtocQ|8}!K4Rmi`Jm?!iYe_-OCpMgeje^6AlJmyFrh} z*k(}L!L+O~{d7^pLJL1SKXeM#)Zq6~_%+`M{zHX&qYl*6E32#jUH&Y zu6dtpd>u)Qy99u-b4Vb^9}2z%r19P{NNQ5cqGb zZ%m@U#m>ROaAE|ocfzVVPMAlyTSUPK=6-$mfb|JItbh4Y!peh;^iM~mhlw103|;wH zQo|tJ1ew{6W)tFBNN86f9)oKW+ZpRJIG=r)ek$t#ghGyhF*e|A|LcfBJ;?vf&O7^J zEHF3D95WKUr6t(SCPKaSaSJy)!XCZ%N8vQXRguQC$QJsrhCb3kz|wnYuG9~nCvhWt ztfJ+2_g!n*zD2}l+nSw7S#Y53D6j5n9^vzQq&}nN_OadQm+#7cLL|*uwqM<1N+k%M zOuPqjd`GvR)&LG$d^ZCU*6^;_zr!;67>%f_5jwla^`q2@TqV1W=(mcUDI@Q2Dd!SD zUSEKWtSznoSPQ;Qa%ru17`Kagv^IOhMt8Znj`M#O`XUg$i|S@XLa*Lgo!MDaTkY~* zxTbNW0o*8km>I=f>3zM+3A?HIKEqv`3C*G|l)cTAh;BEoQC?|$l2rC&P7pmI#SL+(_RN0IE(tirT@ z$Jc3FMGANi5{BjeI39q!Np}F#*a_Rj=-DO33=`7H96&}96vB)lBZkf*&Vb3+OK>Ut z3}L1~`Ysj=VHficgTN26=3&!{Hjw>}v4pBY;T& z@?yG(w@t~jBV_z2$Zj@&rx{csPCrR^L#7#>`K>aiagy!=IeVN!3Sqz7wt)e{5_YyyY)!&--Ou1}$e-Di$u*J}jE%$oDmY zH^DN|GikUY-<90O8g`3ERKcffpu?nFqAOKKE7Df=$d**(P`FjwEx|25Qxzy&?&-u2 zLD549t&Xe?b;z756<20cK~^1+r&r<2o3G&fRZ$}NT|m~S!ZZI5?JXl!Kf3_}TWzvc zGAOw*C9D51@|t9TsL@gTwCQ+f-70e7VtUge-h9?#b^0N1X!>ShbXL1?>DQwK@i4`y`Rk?S&RV}ye^R6n>uWn_iA^?fzJQ)rqifpTFK~tIFIn)oX@(Q;ON+D) z(;CVeYD=NZ(+$2SiKllPs;Ak_tA259tA<7yk}AEGu%4EXgkD*PRELh=V&^Q`{IAJh zPC3oB9tDq_*J#MI-Cg8sIn&#zl$M9)LyfDZOFTcAyG#P~ zMdpI|RF&!NO1(=7OKq(RT#{X~T>@`Qe~M$pQB|b5J8Y{FA*L~;32>rYtJsKIBip1| zZ!BsoI-Jm*+AhzWz@Ff(a4mOa8uR?*vUtk-jfN6Ri#9^6325i${0qPezh;eQlNMroFP0@|R=carA%Qc5GwXYu zGaV9!XyfP%5}nLHnbs3@6V$pPy4sa(x)W_ln_PCHAg$oTYi0kjoVKeL&Gn!lm=mhjjlT0V%sdqf8rCBM0 zSHR09n5xY|7enuR=q>x{fnkM954QQK6rM(+Mtm>0&eYD(PH$k?-5pf=Q}gF@Z$E{i zqW1K9UYEt|45#dI#u;;)`=3*S0=B*JCVnL=)A>=Q+IZ7yGh!JkiUDd;iPurEzFIY+ zQh-uJ0gAmhPZ~E$N2(Gq*kUJ?wqw-IhJuQDTe`r2BA;DHH{x zOhW#>x}RgcLO*|B3tZ7(3yuCvb8@a2*6vzu@W8!R9rx13kiQu5_7*hr9eMu(FNNpK zbfep3g;_&mqA+6E)AudHD+!KqsUyCI-RbZ=m^0WU_&l+X$%<~8R#S2IT4_^`qwmJh z;4)*^cEqi$Q|&^nuCh+A*<*L%lkHG~V%qf8^h7ClX~nbklT+UWuvAH{vC=`i*5`{u zx&G;?OXl%iQgJ*^{B2oQO}j6wmFQ52855 zU$I}!Rs5e~jzZeP2-)a#T5h(*^qEyZ&Qoi-k+MX@{ZHd-))~vozJfQ;`N;3^#&vGm z-S=Xj^5c?pn6jF-z4Nz9cIN(YhBoYU@ICvsu2r|arq0y&qa}e0#)mQrE4_4uyn4?2 zgVVaWD%X)Z<~;cB?$-Eb&YjnG*L@tAj@OT$9`8d*89XH1)vqKT6&{UF2UOme-#|J^ zMhnHZ9cK;HQ*tCX_MWWt_^H+Qm6vbXLjN!f)?-VUBGw+kQS zCPMeUseCFP3}5pPtqvAKflELjkM(CQPwY&dUCKcv^96&&@&dGXZf{yKl?!TwKlp$v*=H9+x!I*u=iS#h_`cb0W8aQh+n)&xj z;iJ8Th7$}7F6BSxyYGq=7cemI;VqQaoz-Qed5!FB7!8c=3{4o_ZS4O_!@%&n^ZvcG zF>y8^bGNa!b>ejwApe&H@89cxx|zty{w3mUB|xq&BTpu3=V(I4!N|_YOfHB-Mn=Z( zXl%->C?@{j@V~zV$jzOd?RlA)+}zw4-PjoI9L<uliu z#nuVgH$=2z=m-Tmn zO#h59u`n_-{lCGSElmGEVE>H#7wli_`Y&_*|0v^?w{SPH))2F>F|l>}J2gRWW={Tp znfbp){?A1J2U7k2AX&KCxc?{ge+>N(=s#28m2jE^0?# z^6T=sd$?Bn0Bos5aj>R9z*`0+ntmDwDw*MZ0U!|s3JzrO~U)&GI1!|i(i zdvyLc@%ZfTx0?X$L5}~X7ycKF;U5@7fMffA+-ejM-L z&V;`{`0*7;1|In;N`v>`4*B<*A6?{sVO``j`^f)Ebanp1#2DE9^*J8-Ke?#{bn5>D z)5nazhW}5ZOMmj8y!rosx=Ckxoi*7g8q2LSE;-H3 z1YYqcKMrx^q#|mm%p#_Pq$tfv5Ulv+ZAG{aN=LVz@4FBl0&R%=y za6w)wC-gkLA6IpFXXj^swYDOe;;J%pa%_?K?Vdl&tbbjRY68!>dt)|B`sltW?G*H% z5Rr1Vd1j);CnRQ97vt=y&aX|}%H(+Np05FkN)41)5B9-=ot8M%UX&o;@kp2V73 zX$tP`$>?_Pm7F57JLYG(?NM50$u1sNS#~x$ZUGh+s77(;xFBE5@uvnyV~g(usahxi zk1t0R%Q=sq^w^04G@bN9fG!)XVC%y@38Sq+Db$z71fW%kmAr-mDx1_Wq#J^Vy4DB* z{-yyXCdS7CSJ-*3eh)z2dNPY0XmP@3=p;ESq%d0fafK8 zSe>o6Dk)Xa&_H#P0kf0**(osglRnanwHR$X1G_)0qX zEp3X-ZweUbt4x^J%*D;?{R`%X>@8)Als&DB+?njf7E)u=pJ2epqSFxkm z#(U7M)khI*+`A_=<$#i9Drd(@8JXtqQy-i-P*`ka<`&=Xj@@^A#s*6*`yRf>8JJ}c z-btx*S{Ped*ot_0A>N3m&revbl?CuQRQ7l3_jP-J|Gv-Z zvB9Pa`qQ-vm1^x#^C9CoxB4o)Fa-{%6NBQZy-#eYs%GslbUG*%s3K_@m#t%G&rupu z5$|sUUnYh~T0d?IxgQ6XbE|3o7^>ax4u8HjNt{&kqT;b){@`5nD{5KNxRC1ecBe0Z z-xKijXL!~`ZgH-Z(&*UO*r1tSEc%mx^6nDN^G4-cLWuK0e;b z#l$|x6fe7d34gpIi!^>xQ7>xsh8?;`8$1JWMQd>Wp_h?c z{_V8u6iN-tb$inFXB_ur{R&O@^~n!|*$Te(^owWn@e%*hTzdq`t;x4R38~z4m3KCeZ1LY`B}2a1rD5A?SYT=E>eD8SG3ctF0uJwT-P9h#H5-e$_L8vdPvZYX5R4 ztHWQZSOcHW2JK`twBb`(AwVNDp=4;FZ&$|N(ZT<4|3U@iMHGI`3Q5R_E=k30@@z6+ zbxkp7zuENb+bHp}axAj_1t?JqSA3=Azqz@dY)0=nzz|B-vEBCR^YUAfwX(Aj(Ul~3 z9D<;Jp#n5c><^muLA2AFANy`jV}YkO6~mfJO}F-WU_h}mVib^e;*c+``X%mdd*aO< z_6JmgiSPRJ1jHpjc#uC1>=c~9JjFeI<-}bR0q`I>gboID({byVfUTQRK(6z6o1sEJ zB!z5O%(lzci>XR>?Sya_s9!_`P;*U|iU)t8X4mBqfZqavE5%Gn>0=_QvVtX6T9vFI z*W;t4&B3GAIQNG`JeW#Md>km-4NpxzrWq}W4s31xLJrUf22_mILM4pYkQbMAzJO<~ zo>FUewMQW`i&s-=4A`sCa`AVwXqvKLc-}z3C#68LY^hqUpfj*dWvV7~u5~Tl1*z&#Lv^3;?!caecI=|z;% zXaO0gf^LA+V>(Fg5QNhCL^Bphu#oMv7l=P6nxx7eSWEbBX_@(bYEsw_n|?i zFiuJkm0}0*}o>*8++N1WP0E6$zDREkamJXc2s4S)cv;ye>wz7>8?`NwQ7ot<=Q)%AA z6V?M4gg2Z8An|mltB?Q2>m|;^s5+V+qExbP#KO+PRulV+CS>>h`H6Ygj}F0LE?Ko- z1*hAkTIwx^-J>4rjdy6HnYb`s$6aR6+TKY&Iuzn+8`=m}5_auvibVNP&4&$M`(E1V zu4-{E`CK>C*9mv_89MFdw35?we|`~*4sXRykNh5x|D-&cD)3&G6L%T8zMeCuB^L>2 zm+nu)N8-rLoL%?Lk>R)Y{0|3*4;5W|NhDocgoxG;LwXMTW%6Ix4py!^J?k2kcP0zU z&2O{(zd7}6jl#fkjHt{zs+qu-(9hxjFknrN%4sdZH@g7NPT^Asalekb3Xuf*lqO_wG>=cFeupTGW1?$ETii3En*i*3Jwmn-QwBwd0o729ASZVjf z)&HRG-!g(Dw7imr`P&aGd2v$FVVpV9(Jo_trz#U#|C<@b^A10xjMVZe_GonM?BA|y#J6fpi4_g$F zNdQe}Fls3p&-Y#~y_~rZcH$NLezhgu$a{BJqP}3f|3qxsgN(u(tsm8mv=dU#F-JoV zYVZMZv%XwVwLV1OD~N%;FQ_NB&Y~h1M<8@o`%tfrfpe}bait`obuv(h2kVD3#h_(B zPn17q0=&zq7`1L#!_J?R>)olmGs^2rEdfhQ{lmoh2Q}Bih#wVAEcIfch%bPR5ac45 zYgNGO)69dKGsm(tXfq2sc4+Bn8*YAbaqtv~yXw!mp!N6L%(>`QBcN~v| zJ@*4@p@Dl&-NN#E`nJ{C)ZwUgFjNE!r7+5Tl(`Rr@D!uF&~+rP0v$B0V|frA0*XFO z0#7#)-7gblXe?a2MJHa~yH%w55rCn~dC!j98A%sciE!-4utShHk>t=vlM`A!YR6&U z6@Mi!L6L6@_M^J|u!4B@#ul{1n~c778)z=zXY!__sa5&ldtL7jURN*+{=x3MZ5y2{ zsY@G4&ZP$mF`s^JJny+le3{-Ucp@1vLbvgsQ;E?qC;0lc`(66DMbO&;Pcrv1k!tE` z#QFCXH6I<+aklSO2{?!$?Wf4j1bRPvQV9~~8|>5TvE@dIdcqjk3N()8)nb1JhLXo* zxMTYjTL+aN=O**;Ua-bNkG+`b5#7p~k69vhocT_)1-5oBOn^}d(;ER;D~+GU&ryzJ zhCVpfX75}bd-cDnl?=-DS4?nreVzpvQN!Q!GM);ETtbnVcKU~LAJB=$i&%Tar{A<6 zA6KIM*F{dRhIj96R!@K9|I`d#BlQhi{_ZKA!iqaSznA)6e+T-5=**8WZl2v9)kA5K za5;r#<4ohGBTLrgejdkcv%{`6ybY2v$%EF@Pc*N;e#1;C??N;5y>}12am#x8^Yc!= zBL2&P2g8T^1?M)VFW5u{H(Z>#D!mFSXBOTMThx=y@J8&lg29Au_khEfAVPa5%E5;0 z98O};_QckopY2sf;_h#vv}`SVbiLHnJJvi=4!nr?hn165i*NmHLdOoAuILRUR-ODa z!jXh~ruN!*uNgw-xDj^1#yh{k%6+pof`QTGaUmaKa6x)kYKg{$n-TODe#%vc-LXz( zSAV7&WtPKjI%i}$(>sW%ZMy?as$pIRNqoMFS-rjtFT<7K?JeZim-)DJqV z63lJ&l;F@+yVUrza~1V$-CJM4{mHp*A#car@SUp6#gEf(fFTA$M`LS8q$_%L9r67^ z7grb7G!E@}1wmK52Jr?1$T#Nh0G;1aR|vCppPv|`(w4i~!DW3A3P-3Sp_BG0;}E1A zN8@4Byr3Tf!zli8zGdd#@^@N1PZfRQ6qB>|YA;7cvYm)~P}j1dG+ z3qB$EeEa-d*zTi-76o+i-X#PW6XmEx6m~BJr%)ut0JTQgE*W`h+VkW3RB1q;3I!8e zYW#~g!Af32!iqc6aB^uy1iMcgT3Tis*$y)^<0afRwQQv4q~Af4%R}}qP2A?{Ly)=0 zOK$H1>@TO<5t3l2{O!P*Bv`wzS$UaFD8MF+pkD^*aKDLt^_H(E`pD&J2oj8Q$PLj9 zGcjs2aQX;7u`cN>?p4NCZIlPsLEUu(yedl!6Nu`GwTxt{W^M7s5n66|8Rx9L7v@t< zNwe3aD=WKRAK28Z{aFcj2Q$Ovkg!~wUFx#y`C-q+@VTdZyE`Ev9lDQ|r_#3J-Fw+`#1!X8?CbD5?@{iu?jS2L5=i?%a}ML_FKj zAcKO&TN!Hs*KbQeiK@15_g8BH`A7m@zev-nguSK{4|*H*4!*PvoLb&g$1Yti8w6ZN zL&=&1N@72T{RKhNxc=y_^Vb_S3NrvmWLZ%fur)EwWl3bBipOTQtfhsYz1SJ1Pq^rw ztg+m=K7JRwOFwI-;*sH*_~B%l$fB*>+SPOrY zWD5&Z>ty?3jFIVNnN~flr;hq-05DOyuAw@RBWo%AEwbh5aaJ{fB{vs=gW;o+iYC}m z4vU*_sVi}JiFZY_?!%t!-Ba~1iiK|L!9-VLhVs*`&MRuu(E$0~X=T6W{(f;&%F8u; z1G}zxCn6#ub*-8*Vo_UVy=a7ax(bGJaTk*>&WvS1U0pPyT-QZD?kl!E`WEB^tro_?wTnQSmFd+SIwF$ zb8IP{iCJnpCQH;qRy$;R?No4#F}NxPF_Lc;7Un-P$|(n;oPJf1*q}^tuyUGwe^11{ zxZ;R5^)TmCp(#b|EY0D-$idafnwpelqM=5_$6;5l-}gBCCZA=eL@9cFJhi%9@iG*Y z65sIL`lk#RQh7Ggk(88B9WOy_nWCZ57h9#8(b|zKka1&L<#rv=L0l`kC^{bhUY&1* zppm&dw{`>$3~pd}TcuWaaS(L1*O}$0?^>Quv>K9(pi`Qd9{=Qg+X_O?`le9MlGV4q z4iU3PbGqcdC2gsEU~4omO`*`eMuE}Pbf8BIg^<0B%;&DhHgdZ~ivKUh2cD>$# z(np8-z++D=zOw@MMDv^ zT{w8h>-TV97djmp8W@CnJqm`W?f}Cd=euIFP*Kz4(t9*F1YXzX!QS~zjb>=qPT_o3 z%~X+tJK;&G?y^#8XyOI9?4}wy)!?NY9O!)S-p%Qq7TZ4x<=<+puC5RCxqo3&=JXu@z1MDKRG_OJ z8s3YaKByiTHQu0|=EGmM{(UPQ%L|5b&SkmXFnb8~1r(*W={sG)Ol2#HE3d|3^2q6h z=vDD8k>rr^yktJ7|A}G2Wb`R{idJ54BaAb_Lguk6^>_iyn)KCI9y1EH$Z%t7Sg~TP zK(j(=ST>Qk%E@^a%6#j6G?~7w{O3pmtv;b9d3i>9_9cOTTF#fn`w!t;$yd3hILL^H zR|C11Ek3F&UXs71MOoLOj$KYmij`R5opVp}Z4_Wk6;PX)n0Ph{n)B;RcFgB+s0H5N zCfFZAGgl-%nxjliM+sz0Sapt5KIR#@7kosGvGk$QSc>#hYSSj9Ii};O3wJC^N%)FZ z&s6{zc3^APFR!nTw3Om7alL9r8e%oGV+M^AA+L87j&*Se8iow0brKy;YU})%8OUYv z>y;F3e)DB2^pu#3hkmAioP`0O++)fhyln%%FQvD{Y)eQv8Y4ee7l#4~0G3up7l2F6XWD?qF^_8dcv4v-WQN+1naMf$Z;lAjNj4VIjek>>MB#}I2|F|D zH1pgY6dw+nMdt<&zZ&%T$T(yY>|*vQq>^;iml`L&0*av$V0Ig30GqFFg3xZ^-#3eR zmO;qhvWUqf9u4S-21KVN9Stb%FFz&+=Lvit`;k2gaiDf!^}?A~0G~9(-lCwNVvh}m zuLmkNcq!g}q)#C6>hhf7j=e*P0$S{|4np42fHHq8PN{(66Z!ZBi?pix&!C#~Y*tfR zA{`YEO1hCF^`XB4CI~!N@4+V!pcNBTqJqPOsF}H_@_x6XkCgpkF7p?+Bj0#UKoT-> z(#rzGvpg}xviH<039$5?@pq8mY7b=g{@6n4XiGxMY8V1QoKws}Y?NKb(Ok0n8_)+| zu+gv9T>xBj*D?-y1^cvM=pTUoM42H6RiUM_{i=2R-?^o{FfUju58E!wftCMZr95E_ z#z?dStFn)@{vA0;HGb0>YB5UO9(f1(A&Zf3rh;PvZjpjaud z%$!gl5iMx`W*CA1R_64=T6PKN*Of)ZVQ`fM)O>X_XxEEb5`;Hd=8ELs0Go;@}Dt9I&5 z-+W9AocA*S%gTwLHy&UyS#N^_y3@J~2l5qLp+hB>N7V0}2OaX>T}`}|pvvKC5*^22 zIJ}PldB~5mDHERXD9rpUjewB2VMo;o=yDevIfeJcmo4?)7M)~C+Ol)9%=8a|eSf_+ zFzvEg9@;&T7t#quUKwdj?%6H0kram$oa9N8s<9$_)JZP27cH<51-}y3%ej%JXAa-Z zNx$S}3X*Wh#iM^6Dqr?IX=uCke%5?lx8wC|t2B_DW~G+LWe-=M{8>z_j4LuAH-K)- z9|?N3hYQ|y){Tiz*ij^4O*^)7c)rl>?Jdk{srU|5Ap+S+r&VK1RS?O2V#XIpAj_rt z&QcrFa4zWIdcB)EX?c7QUUfm6%sPq?3hwK1SrjWv=L;@w&EO_=`aqg7+7)(LTV=V6 zQcm$RGfh-yN#o@4f-P_AyVAn^cz?m(#l>CE=r?+1dT;J{qe>b+v~%w|B8Pex^Iw}@ z?$=L)0=q%q*>DXXYle!LhI=#DsDB}6hB4O~0t6BwOF}r;tq5$j*!rYcE zH9umbX>y-0!FI10EuUa-#fj{y`v5{D(Vme#0wgR|{XJd4Vjp!rQPmDbLyc2!?_q7#O$)qz73a?%!H1kC-9TI;-BdRi4tzdM&C1s+Q~B~R_d1OcnwYdNNYF5tAbrG)o zM7$VZf3NM=V?DbsPEMZal-Hbb^&3Zft;c>w=NRMU`@7iV@5FS0`D6lUS*HRaSK)LO zpT&g~i?6S)2Lg7v^exr>alaH_R)3P@*`r;xb|d8vr$Y)E-qp9T3H9IVskUB13VLXY zB_^`J>9hq1dAx^LCWN>U+I38!QS;%Iby&fMcr7`If;-!TYCqur#y?I1i}_gauD{*T zB*^)Bzg^DR=j_ykimAlw-DO8trBI#~d}(`!ToAdn%sQP(87kg3#)nozDifGEZ)%t~WN>u#bot`KrZLmbblnpI-tL?%UUYv! zm3V%l!e$d`@$Z-QLdSu2^7Nqi=MBDaJF;R@;&5Q6%%(epRKAI=IJ$FHjRbL0=qj_P2h5Pu{ag%?I8a@p+2e0lywzuXS(3Lq1d0wBsdqJoc|bJlgz z-kJ(KOy&pHuDyStg(j+4?v9QzSYvXNql~DQi&upLaSX6*-YS?zx++dVQ@H#DO6isk zn-UC2&FIIAD@#K?h)TE>oi&HKbtg;9SNz`3dx&A+;$NK`;fp(3Q;N<=Jp(e`mZqQh zoQ1+o2QBFas@pEZq@;~D#gK6OesqEeGj1Egk#VSdYwn1qkyqy&x)X3UJH!pDWJIMSKn6TuH-3 zR0K?r3F}VMwW}T1%9Girvm|eElvw_dY}38wIZ&Wl4}o&z+igi;e=tG^WsUo?gpZaA zCbkA~p`f_GJJ%3QRN4Rp?2hsMkgp_Y&SXT~H5C*@zgB zYU+TSbiC{-_=q^xx@rSaz~DeQMlMEgxKiofLy7gddin+z_DF_>ETkDPNc(!o;0Dbau(9+IxyWuzJ@przyO2=lFL*|E)Kk35K1)o|}j@8Wn0sGH_&^ z$|$rSlH}MB2jp2eHiuCK5V*BhB>^*51*-ISJ*CmE&t1yuIpeoBK`SwKr+xDuGsAvH zPXFO35jK+IGmG3*rvp11`xjTVq5WQma#Qx^wid*BoqcpPrsQAKt^xxHCcn7SwI3Ym zpAYl`7oC{HTX>?LS1V7`SF0@7Y*1G3h#djNxL>JJ&3=Z`7RCW}@Mn32!FfhP^k;C> zSBw)7VrTKaEZ&edYNMEePU<<_fzfH)E(4<~>s>mymK7^NF?=|-nc@AJ#6gHmAKZ#0 zfL9WncTzkA$!t$XULMK%&f7|Fuh-;8S%61h1f{9?sSXNY{t0D%q`tJhn?d)C35 z=pC6e^li-)LXy+hM z(9rtXGcaBEspY5^j|2gn*W+3E188NjKOKNU3zE(J4JWlHI|r5cra)YWp3dWnF;FXs z!Rmp~60V4m6w@VW1eHL@`x6w|i{uDR)|WGNX>ejZ?dgi6hIoQscFI_*AJ@1XhNlpa z?f#Gygm~TxhU>z+rWdZ(onuH24>Gw-9;BGkI|Kq$V;ndT zh6VMLE=Pz`=fjD%C zSNr?^yvLDh$31-B^r%T?Clb=OJ4VBt@Vd^}Af=Tl4a@--L0qeOJN@J2b@=birAXB1 zB;uoo*t2viNhvt|KHk;fhDu>MM1(K*=y(JVXbaPC740fN@K@BE&G8*7j|GlOC9C$= zIyUQV3h_}b3VHZs2plxrsNlalDA1JoVY_WGgCdkwu*%3zKj=O_m4@+YO?=xIE~Jto zOP|QaJYc^26R>26n_M@Fxge9(*;S}e#vcrBGj~pXG$(wN1s#zwba*=-Ok?a-quk>?$*=pALWp~&!3Vu4Lw-mIEhb-K+(h56i9 zEY4AskoxaLS2Hsj6X`@ql;XSIgTueNwNAqk#O<0=*mEh);m}AX4fbmC%lKpmD5Ef4 z685W4%ablP)-0?m#!ooKZD>TrefkL~sCu`|#VLIsCwUXi^<8N)>Vx-ds_f3cTJzPz z?#=Z_KC4g;7LiRWWu*c_tvNG&o>Q`nf|77DP+u&X-I}1Gl^@^j@>gVdoBcpRH zOnfOKXN**a!U35L4Gm*rQ$n-a9=Q9Tew#)f6@P)8?HXl268#AOD%U7TR>hPK%tEGLb`4iHAn}DY@6np#uJCjKg|28O zaLi!L+SG-87Tf5FI@FB;Z~MMR0`ElZ?h$5P`O!wPhta3fOWmubP3w< zsS@Yy-AwW*JPNaq_|ZBZ5nEk1QFx1m)mbc;(iL5lM5zX7KUtW`?F665@w-+v1}?r)lD67SF1jCY3mJpW0MQ_vmw z;Bu9XgSyIl2NLYz0gPSIxCvf~YIA|bDaW_Q=l|~b2@{502*hE#i1hGrk}?f-j>@l^ zigZdj9Ar}JBGXs8AdSph1wIKg7P3qwq|DBP0aQQu@0*fKqy z1%qnvMuV*kIwQ12-&jn3sl*8Frc}I-H}e^O&9WNmL941}{!pYzu-ok|Mk-^#36+i{ z8K^0DaAtKS&b(byBwM%sdJ zFQ1X|qvtnJz9a4mmi}FlgpLv6CJrJ6Wu~~e0&%9#gKm(g7VA?PMzMuP(e4h-()aQ? z1VYvxe405wl$x4PmvMN$NVT%VfL5v&_pD*DJPNApqNUU>Tc!H4)7Sl*)}H!@wQ#gv z1tSnS3oRM}0Sir8Dv}U24oWvyo?-4nJ(Yps z1>Lz$)U>Hf0BM57MtZD&+yGY99T;Rk5^`t{)jlw$xLC>sRprNbImDyOsNj4OmvgJ* zWX^5JVJRestmy}v+2w6p&-Hf}u|ubMriUD6+-8Akj^;WEY+i50k@@YQrK>|B`>Kei zKmwp-iqA^F^^&IP9TjDI7rO7OR+;fqhA-(k-zDM8(7Nw?aTKoHttV{qnUT%?-Qmt& zU%--PAn})P z;U8|nNja)n>A-KJpNKvu+B^jDf~UWr05tx-RR9*uc4j*Ij=rx$&A>{Y2$F+y;#4}b zKgM@ZvIe^6q>i8Fj0`Lc>h8~(po4^90+XuMZCw8AMFIT1V}9d)83$wgP3d90N1BmU zX>jYinSkUUGtoeL?hA{A`$X^unlrg z&2545^TZ+)+k!{W_Xej>CJ#GYSy{I)9Bk|R`uB?_xEVTFo&+6K7NZAS*L0!VeV{I+ z151M#fg2p)JfPCdT&Xs@PFpBYMRj;j?lt6vw;|EM-anPCd`zaO^d}XlV0$a8BWPC% z-<)@IsPgoUdodKq-kUuBCypDUi$>~Z#(np9hgToO?i`d>O{&U>-nstr9agugxILuH zDeba*Lr@Gcuvi75V|&n31}xpVetP&YM=xgt7r4n_ai?;Z#O-KI&ZD^WgB_grT6D)r z38JE)_;q-wXY`HRqpDsu4k&ey%)afbqN3=}_GPZh{vN)OQzld%J(o4r@9n8je<`P= zfyN`uk%G+{2ecPox4i`F2qm*C{LE%s;H->_occA?kc8(-i2_>L6)$YM>8vKKsI3HX z!*x(N9KA}8QZPC?EYnYjIukWPEG8+md)rqz;rUePkoSd0@$iKZ)Ze$$2~N9+5L$hY zPM?1a0a|47R^7ujxH#pZ{`~{kCme{iV^kmOE#z^)h`^W1_EaVu2R!!TW(CT`0S7o6 zEQ}^|5A*Z;HV2rDd(r_G>phTopb{-8v2JQv>_C>dX1|3h zN-~@sXk2aA;VcojL-3$HJsUg}r%D!WQ}lo+fhO+Wp5j#0EEbCc z+`~9r>B>C z67QtJM9WQg>^##rQGd7jk^0?GpBOxTb2=+01l1VI5yP%v4MCy>?>{BVnvyxaA$Iip zxmINb8FYE}QhwWZ=Y`cxzFf-!@@b`nU5&r1BYHBLL-IRW=dxc-Jt{pMeS>3qeE~WX zYQ+FkPAx4YGrmN$xT1pugXIyrH0(+0W+%mc^qFFZAgWLa9I*;kQyfsiKLu!W-#=;$ zr>9-Fpx+x!h$uyRYo58yF2i1m_6%>fE@ zBljOq&1SVVg${0Yu{HT+aI=s@2AJ05?Et)}N~~k7qcQL3O*rr{ zD7USuh4n{^E34$nsl(gwWjZM=o4;4Tcad*nEm4oKB_GvU@0POoJMkcQHjHYy%9*Ko z=Yx7;^XcCPL)#kM08B2J@V$ObX!`Qctk*4@y4aJ^CK8o-)7-0Kdx5hJ7X+cF8Mb8n z?e9Y9SW>B$G4A$4IaZC^O2a3n+CquxpbYJ3f>R1}#u{<(7awr$5=$r#ez z<@BA|#CAqc2IAw2FqTFLCV1ziHO$I&C3MC4sUPW00(7yBr)ZU9bYRl_Qy$jWi0aMr z??N2CYYt)4C5Mcd#^LHBuf#VO;U8|VUms?agucZ z&Db^RYMF(2V?L^T>0f@im&Y`GliI;e3}5XzCjCk`66Hh4i`K|M@Gh04{^R=Krx=^t zg5+jXpej@5KxVLXO$2|?!p2K-w>anajuS>}{L4jC*O0?526NtNtv1ulY?QQe$Vk+p z`;2yzx^p#PNw9UydNZfnSd1#OM_tG)gpL!)Qy!F7h}pf*(2pAV>DH;*OxzAPcS%=N z+ZGc{4DP1UA>-i$v>hgq2a>*x?1U7EG;r9(e9&Vs0{8A>* z{b}c2@k3Y_e&|A>R2>8lTWpP=!*-GRTi4t-JP#z&OW3XHbEgw-701qhVFIUc++Z*< z3Gk13;Qt>0jzDq0)>@B+E|g@`@laUoVoQ_MGcVBhZDQCDcR5vqf&v$Gff*8GjQKTg=Zp`w( zmiEpFv327ADB39LhGkx}84kR$+YD@SGJmj{R zea{TzqyjjaD_apZZ7Mb}+AfSv_WHsMeE8a1EIm|!-S0+V((`L5w8bY>1ddhat#_}Triq!YV~!5>OUZsOc?^a;V54Yx4* z-k(M~J7y$X#%;^4VW0@#(9u}58q;D zUJgq#9zBeCqc+0LGYn7u`UUL$>yPnKLIY+jzY{l+CpL*+y@UL;W(W{D_0_zj??b=3d2d)@K<9xpqzZhU>Kv94<)2AYc&$1 zJQvb&X`{VWYjZs^laC_3rHeg^uY+@GWIxBHo`EeLNM|pTUCiWavSb#>+B>jvP8c(i zI$7#%AKv|B5AyRr#U}?taqp7Ju;e=LqD-escXe3UVbaodxMle)IGHgUH+~oX{rVQ} zDW?#dy^Kcr5tx4cM)*&hfH!{r7?PU(asQ8=#zbF7jwho;`^Bb^b{Op7iCgY_0F#(~ zsAopl&e#5feJS~j+TM$sRxtR2l{=JQZzvpQ=JpslWj-uR6Of&gft1|y@Cs+PEKAEM zM@fB24k8Ql8NgaiSyatZUSib$14VcKN+Lp=!Y~Q{W zJDCBw^YCW~pB9WMl#NyCsSY|QRhc}`;bXCMJ;!ix)8N&?fxr%}p@zo{mSJxEUcdLb$sq$B3cayn3AW$}ae@m&)z8+zNjeOO&La z#-`WbL0l=bRkpI^^rS#oah#PMZ73X;*4~&hXC#9JlCW>jKFpgCK*y!pKf5_H^(YRJ zBZDRm$AZbeICP}HGld+P8-tf#+l&hK97$tHY6O1s#xT76 z@J;Y%RRhJ5%F)rpOfCPJOR(X_HJA|Sikd=JD_~aa>AW(;GWc;$*gCk92cG=Z^Vsv3 zXYol=3udqS9@Z}&e-THjXYH&p!|XNdv1-vQuJv|AZ`;f{a2RnBRB~6ZV9<&)OPs1t zUi*h?72@hU20<1&97@Vyw!uk+dCsSkqnjg}>X4Cg9GR3$*M5Hq>Up{Lj?2J3;?A%L^+>5-tPx0yDP~5#}A}q*}IvNq0Sk|k<(gBl~ zZNM$dW>SXgplr319Jv)mi6;@8vy_=*!!cv+tq7bl5&!<#V@Pcczym*e3KRW$IFcEA z-9w;j)w6^;Ir5GNFmbpC^vrhH{@TBBAeC$W^*zjVnE-DF01X9>RPw6Eky97Iv^XBQ z4Dvm5wg{f%1GJzE&6!-3g%h+V=x9(}wsr=#yq(i;vg*;u(q8X>xCtdJckLW9jU2Te z;UVrU(RU8pwtdD5G|||0$iR^kY0%MAr|zJQpsu9eU3)QioG*>?JsjDL z;*Lwz;qJiz6b4Beb^GF!9NAfCj=48%z{-U)VQbV%Thu4m zv?~I!hhmU*J@szxheL)V>40)4Hh$a-B2sh7k*6@yeJ=GiL+|Yta%6hy31pLl);_ch zR%Z{=aaqGIl%u{l+#FCPd=&*<}cD560~EwOlu3k zgFk+fwm`*^%vz^#(JlUi@m>tH^}?-pK7a{a>s|FF*#0kaWEz9}|GgXQmW+cJRo0=v zkqUe>3kQTvUjXAmCLGY2o0eY;kFkDqT+%tLoXJHQU-n;$&r*J+sTpjCN|%P+meiX$zGnbO7A7CT5mmKsMi}OYyI}vpZjRJOj~tmDg+IQr6_p*Pn6~If+|HyP zAIgh`4DfmL{m+=}a0tJDdl+7N@J9I9nHy}>wD~qMU^Hm#Fh*rEV%K0+%RvnCPzA4z>&IobIf1ZfMV(Z06+jq zL_t)$5i90Thc!7eYRgCXc+W9JGYB^4h86Ik?Ruziq_UTJ226k#N#-b>TZh>$Vubtb zejHhgGiOdBmvZE~N0!4hV-FpdRo&~29LeO5_+wjfFp5rM8&9m=a3_{d9}a7-gHy-$ z;Jr`wAuH!oY&$jrx6c~~GbTzYo2%kT*g7M8$<5ffWSVBv$co)cj@*IswAIDtErXw1 zAm(gjkcZ;PpFe?&jzE0>M^9h^InrHmBo|Jv9Lc11D}Nu{ddGeAo47+&BH2ogJd|F@ z@4K+>x-sxjZAOO5V(T7(&--R+(N;`vrOVT5;m*syw zY42V%GZZII^bfYuqbffUzjsjRjJ+M+PQ&oni_hcW zzkY%DPL*QvqFZt6>Z$PbaDj^zWtL9EKq>L5qkfHx|$evF9x9--tek0dp`qQD5k(<3A@FCfrTi{tTKWbdkREvz;i769Q^(?=GLypa=+jSP z8p*&~r3CX(y@VXR$Kk>6KLNkdWB7cYTBHlWQfj(JBr$5@Y-=h@A2wk$4XWM&0Vwuy zqqp9aeFOdA9}ozCPeX04F2jwq>uT4gL0{KWMuS5n>y=w!=^gjufi<(?Xm6!CYyJIv zp|5ShD#?*g)miP(#CsPN9a34E*~zqae>#pq=y9j8Xbi=dcV!$W#PR znE_8a08}k|A3ty0SeS_P*LR_`Dx3AQ=fbkHS<6(buA`C|z7~%@ND`zoM@8fL2L!P! zoEbi4v};FQgVqn$mF1I+y8Pivi?XqcC;a^a5#phoUSRLLdc)hRwZnfZU0#)784)&; z*F1ns8gPmihiT3tl`WD%Bf4^qk6kc@HvgqueUSWxYc$N}e3=itTnF&!y&nczSH5HFP`U4Dix79c? zkR?W(S%&McKY9rVkM2Y2UF%t{lo4j#hqZ4gZhGKJteY98mDcY0y~+yr_frb2F%?;F z&Zj`wabJ<+%OSu@pKuEtj_^srIGh=Yqshw{0Kv=~LfO(#i9P!w&}wXjkg)Lxa5V%Z z8SX$r(6{0Ap`)l_=1$P~MR@dq4Hy$ZKH&%a{d}~3mA`uNmne_fiI}YU@TVfI9jf1y zWf1Or^fAn!;HEORgMtI0uP#OUKR!fRMS<3f!{5yv0mDM!;l4VsG?BVBNa(U{$B-?{zLaB)5diR_PKY~g|1?!5==XOGen>hv75 zfS>@hmeHu6myA7({+$@$YQT(pHjEsxa>L#D9*x2FTEb3F|^!!dzQ<|En1aXe)y!h9H2&UbIFufQ(FktQ}6#&TVLE=_c@ z^t`))BV!Llpq8>!@WjP<^#1i2er}@-iElPbKysahT$+JM~2}3 zM;~Q2wL7f3IkJl-r!rsp2&HtysGuG{S2v9#J-w`9o5Q`3Ybqcp7!Exg*{$HJ-nF4i z@4;<1(J1WJ&5?InlOxaK_ggbjT*IwoG+%HHk>8-$dSxO zcXnh6(MH9Q{s zz;HH_PsNiXJ;{-GwEY&@pMqI)B-s*jvN{yiK>PSPnJg;H^Cpra*iT0bqPkQ>_o+p%pl*fHv(;@b_ivu zGhY{0h3U1B8aOg~{}GKNhfiLH#~)aS5x#~j0e^C&HRt`WIo~UzwQO|o%?7kndFP)(hE3})mRRGyo>#kfhBj{kB2wRVPFXLPEC$f99fFjw?)y3c@(QB z4~K_}_*Vzz$Pr@~;^BK(T9O>8>;XQ$eyAZwz7ti5Y;t7Rv=BJEE19v46{E^uryGv%1=EUq;NiIF!_rBe@q09XZkn)-K+-ZsipG zdk@DbGMNr~FD=Voab#*jG|rY(!;(o5(?XrGopNMfp*sec=Vc-#CXp4Fyl^`?@`f2B z2XJId`NN3LPr%NY9E?!`=-QZZkc^zL=C=EAJFC~(b#tUOf1Q=_e>hWL%wQ36B*#bD zGfRBkX+ve{S7py6@A|W3p*mQkrT1`}ungaS=s^Z$8936*)sY-oiZg%PhB{^uHc*bV zurwYr&J?9{$dP9D7(tt{w^NnooY%l&vIS(tMow5Q?+Q7>`vZ1{}#HQ=nOXITx$1CKt8$?OHU@B$LzY?TahEr;g8!hSs|fzh;bx!ZF1z)@QHL5o)W3g7X1qhaw4WdLH=|gmf+1@v4}cK zj+{6Qt}63HjVd{E7Z*pW+_F23iQ-bUQa^#dixR4`_^iAvwOO9;w zAV&sZ80AQHD30t|ZZ_V-v7Q{cVtOcj80NGA_~FjSbSU}lpAgRg2*r`Ev?=s%%i0x& z_7}Ato&7>EW!iA;Nj=3N@Z}gonNXRDTG=D`z`>(vVJc9-$Z*=kX}4B`pdCtPVJY)t zk?bj21;0^q@bLXNF+k4SupVjqa&mIO3(x%wCGmT4GGh^fW(_x-N}0vn!XNiN_5|jP z@G|&}f&&5CsM7M^LPhm?t*xMHucT~Wo?f=x)2!j@Mwuljh~=ee8&%0`d`5Mc(6R2n z=uQTotf3FVz_D%{=|`hp_nVJXP*z*6*}jzhzxx!!iPzxQ9`wKVNAF<$?wqY};n`=N z#S>3Ffv28$63_ke&o~sD32P@;-0;97m^{KC#x!7AwXke85?t}5*{ryuvBuu0%wFXOOg|W#yfQwGdkVMDG*CmPGU1s3ZkET zn9%rb#R;fK!qI=>pRc^l-fT(8&nsX!f-5KR41}{skZPJd;86R9wtElwE&Y2eVK{m+ zMtj+JJ0=Z(H)6D}y=MSs&7TTKOFatn^N?{S1IbCr$jZ&9VTX!wmp1<{xS;y45-~1y zv+j>7>>8_4%1l;!Ur$V*J&uoIcwXgLGGRtU7_(G87oL4M_}qS)m*)A-db4MVALp39;PI@(COFXnV5mfEoBIGqa|VPIwATW7jW5 zu#0lMcRwrd;TAZWS(9V=Q#Fbi9oVkOvHS4!@nu=P9EPk#=L{7roiI*sH$vaX<md*+j!?=;# zjk(uMoz5tDW-qHTYh}rB!@&j4Y$eCh6S=1tdS@#I%b1F>@7VF|zroU8_3anS4}6}! zf$#q$fQyqeMuh}w1{d`gW8UAEJ#JjRsCelc*w2ljZx%Rvh(G*r`3VKd=ogb(-Dj7{o~ z%1L9+NYMog=W{I0P*POO`QE)wxDMeN%yLJ*y(Qh%xT#*~&UBWjUMqF%jd<~Kc{wyJ zG>pbzI={L(Uj+{M1O;h*Y&*M}*ylr41-bZ&*Y0mvp5N9D3r9_dDKl8(_aE0dQl~ib zd@jzU<lpdv?BR*)l2tSFbSfZG6$G^4XP1fxcVGE>69k-ZoxjvPIm znbg#S`g3H^Fql%2ZEU#&M{1Z~$eV^MnsjrdlFNDyb8=+J=unQM;z(7grspq1TU=<`rjvOF3!#f z3l8AAHpuEK;LO&^9j?q4(<_eTJ~>{QM<$;*!t$P0xPHTG z*wTTf-V;>b%Mqeh0&7;Fd-P@hmvF<%;jO?mxJ9XXOg0z--; zxz?=h+_7lnR2b7<6MrBQm6RhDM^+SMA&m}TcP~Fo3Ln!`;lCR=&V7v|nT2O+i%~O{ z!^NI~v)z9+qJ77QfpsH8hcl>}J$SK=_Nx6_ElBdsN5CA!{l2vxv zdLppYRoIiynSqd=6IgUhe2_q_b`kI^3t4N1}w>u4$g4$ z_94DT?6G|rj=T)?V@8gKxATP@8A4s3SwvmR4seNSa7_=Qj(B^5TK(;YfQrxq1%8kwJE;=#y0&NNOsuqXV74O@ zMb4F?mAW=VfLXC{FG_24n7wd5JXF8)9#GVGjU(&Xo17f!I|U0TkE8>#TV|nbs5o-w z>^WN5Wu+suv5J6psEbgJbPXHFz}-HMRK5-$a-HZi0((pHT1~`rg z^y`x&X@9e45UsPj6FIV;<3^6`UF_Z08}i>&e^`6W9Xkg$3>=QzaR}wKJt^B)O)2f! zN$Ie6aK(%nle7%t-mxW4`p#C`M;e$|WQve!|IglA0O(a++v988-6!L28Og*ZLL7*o z0ou}*Qc7E@ulCyhYrjYPUWM1bwpb}r910!N2N!QXVv$Q+(Q zw1d#*(#`XWEXHm;TGLsKtr&lnz>QhSsanvV;Y*pn0RlF*+nMf-EECp-AY?V zRk=%FO%?&fh~?7;3gej5 zs}ENg0g;!b$)b!o?>o7V#hXWJkTQgYMy)QKV;$06d-fr#@En>uJGDuI2(h)PjthiB zY`RC*AQji{c6w_u?BCPVnGZ@6NmItQTo@fyIe0M|Azsc!nZe1^AGc4B!co?eDdHkz z8*33XohxR&Oh$|N&E@#_);y#;sT_B-Fl#=qqki`rcp~>BeLtnMtarx+vq4TUo$1}u zWmt5(oUW*SM-kwC zd|kE3HamrBJXM=z(Yn((cTZCy__R>)( z8(9Ta%pl-^;@ljRRJ0>@)pzIt?}q*&-cH6Y@M%G=Dd%G6Fy-w0d+48=5}9QB+#C_Z2cJ=qss1EsWi z!)&}3xh@ATrp*1u-FW@Sf5WlkxlH974R6a+|P`QA(9@?u&e%vi@pR(4IPtuC}JoS-Csx1H`rLyqhZTALd=TA@ZUnm zj@oRd<*LT)5PKA6XQQ;L0|{&HW_?#T)~7UqkH8Ky-FA6jFSA=%=sD74`bF7T(UDR( za%i{_)eUW;f$KJnBdM=X%8@LcG3j6vNlq?J8B?GU0623 zMe5I+?x1m`2SRzz@8evqi}NgFmKp;`67SI_g(E3<;Yg!^jc2>y8z=V7lg=k%ppZ@a zVZm{0uFaKY(eiu}zhJXd`bR6?lkU?Zxrh~yj4em7bAK^>mxW`XE(d!KmclLJZY+)` zM-H4DdmZG+Ha^>!r4@;R%;aEN@`Si)9O>>27fR-go+GuC-rLEC=(GLdlWM%(jxtMMh6@k@9Ste0WU$t&@WdJeY07Fm{D`wXvpgrhdtC@4CG$-;G(a%pq?&eEnlM~`23+BX)#`pB*tc(E9msd%L7@WFs)5Fl6-5a z=SUf7=erB8BS}r@svVs_BW_;?UvgwkRTlEj)G$T74f5zTP(??nl)D)@MUJGO_vl6} zhMB$WoCKi|A5U_m)T=jcl$$l}dch7(*f&V0RC1(I668TMF0P!HBL!#U&c+tc`$jl2 zMABumABIhyt2!IBUS^lko}rDY{A%FHxnH{jZ~y!m96XYPv;@gg*Muz}lOt`tF@NT4 zt&Z-P>ky7?YsMLlIS)qQ2}h3Fv2+}1M@L=Z$o-UAPd^t!wagv-jT}i!@EDF1PBn5Q zy$ml0X2~w#&TJzhKiTB3rblWVslNv;I1(ZB!ArEI{DaJvi5tjgz^RSPy~ru|ew1;paMx_?9Wj5^T72iO zMT}asq%ph-7VY1|-eP*-Wz^u2)iin$XibVVh>L<@MsJPOqgaI4`Q7lvT!wjW&p_5B`PTOs4Y?2{=PFQCP{5+7 z`>}oV8SLN7w3Z!@;Ni8am@Z??^3G);HPF+JXO=!I=+8KL#Gjql`6FXT+7thVHo789Nqq7HpWKma*p~mi@K20G6H0g~Op=tl< z{n6D>iVt3S1|J+N;eA%Wxk<@@1Tk)s&>Du_x<$%H&XOpA-R69<8YuS#iOzzOWdjO`S|z$I0Y436BU* zGFrQjkyK`UP|70tF!K(WX@)S?vXI=wW9~=!z{1i4;x**V_C|k{iK?7$j8X4A7hq;V zKINfZ^e7r_LASH0V1vD#+6PUKI!_zw>(2%O3o2R8!P9Z;>_EJ<_Z$wg?rfxE6jPcX zK?~e*``X!Xcd|q6xYKLg!9SvG4byXT{1S*6JpenHu0(rUQ(+!2_i@c>A1`H}@yAI& zBz3kyDIfp)jr!S&26fk%g_sxf3XY%JgJVT=F?}cpd5z6*@Jz&9riHP!U?laJZA5ci z5}b$V1#c#L!uV~V2MrYJ2*y1vk_@gUqIWz4{&b43ir*Q&S*xO*6YXN&I{H#mr>_yW2u4(fJ=JIZglhe z5pwdFpT~UAAveiamkg;aTpNspmweE5zM};Vd@jMPfj1`rFYm7)N1jBueHgP|9HTDp z&YGmNn1-MAKF3|BHhg3p5%6Kw2{)5BrW^IU4mvu@*CXQ}e&cpEJ?#ANC*C)*ugtaN zSv_i8K7R{0(l1taj_`<;w4$$a4!WBu%V#5D;1u#$_tnvR2IjEltIY(C935*zq9;*j zntuV=N1f<51u3kt=o{nw% zj^V`KO_&?`09y8~Cr4W2^9z!65#Mzaq@p?D2-X@$PICE!e6~qy*$Esu$-bBN)xNT7? zu@lD^ry{OD1Np4Q>LYm|& zLpLw@D&tHz@}|-wIp4RX6GlpM0=6GKj^leaV@~+JXxQ~0N(XH4#aohf99cV_pE%w) zh6HOJM@~e2V1SO#{Cb2WHs;qcA1)}8OZxGq)FW+d;S!XD)#S+6j+K)mPa(+C57{{< z(Qf8}Tkj%AI$APU)!0qDw?)?w^O=mdu|JbI2hql8T1}7SSTJrPvoQ*a@t3i(9sT-( z?{s>kaO4GdyWxJcVl;lY+e4S+nw&TR2WJGr#M?UM&yM-oV=8P~{kK;%I!7yhV z(sQId@5UJAIo33eB%ou&q{;09Fka>}N`jTdMk4>IZ4?1>W(#r-h{dv5!T9ILd0dn} zf@qg`oJv2y44-cJ?gMk-$pzoW(L=O%f}#P-VP-6vfGP)SC+BtYSR6C2Gssn5O}23# zO@6t^Tb}r4v3-4J3ztH!n#Vjn;4GQ)!O_JFcipp=wQt@)>ERc!FDng85`CFt*<4E< zD+W`k>zJCDhGmIFN_5+X;Ag-7HCCoYF)gQVVWx_Ol#lP;ijLk1x4Nz& zS&^SO42&m{2t5aP)<>b2Mnij-R_`N@X{u~!l+ZQ~n5MdFM!L0fKnB6%qUm#uO{+Ik z5P=~YmYXWe=>66WYma&O!;`;4IP0?NQf-dl)ZkfsbcCT$`ptqKBsTPP?Cv>@JmzE{ z7-pI)-WqbZ$)aUPa~&#cThVXrgm~uUW}x%vcAi^ubO+r#a~kpN0#k5zv@`bPRj|fE zDGCm3rFZ5`te738c`nPI;`QN3<=V=knzio|uwvy>EtQ(#G{ek~)<3}X28;rB;@BTI zB^`fS_4z^w&=JJiGXl4TJ7ITa9zNK90;wUlXSu>JY*UXmuqHR=$2YPC8`b-CgZBpRl!J=~);HSyDD26C!FMlqI z7UD=jDf)(I(U*+(hkoat&UTcaIjyxC&DvoS!Ff5G;eNWDW7NK;2v7wxv&Nj;R^r|C zXR-OfAw=1gU`Ih4+;3ThnbY_T9(^=x2gEt!&k?C22v!GcENnZz--ybd+2g)=>X>6(mVj_KHH%F)Uwh=5>MxJoqA zrvl~j#`32+)pc>O+{5Oqn-P!DFn{bVEy4#ox8uy=64(Sw{TtS4V*TFk@u1Vg+SSjsBNtemMiZR`W-ncd5EpXfxWT4n zLE+prmB;2YEm%!$GvcR--eZssIkLJGt#lL!3UPyr7LD|YO%FPmX|fyJ^_%UJW}KCa z7AzRq6B3(%kWgP7I9Gs;JGUW!|2fuei$Ov{B-ISowv^l5WRq+S?#wn6M2>7bR)Cf- z&A~Lc(bUMpH0}yV4vY*UQR7HmVHT4nF7xfU4(N|I7_=_4u^V7_UO72(-l{cNpYa?v z>^q1Ma}Vr2-2wN-cVh-?mD^g5ORvjkp&7?#962(B**j|U&Y^GaG%#nc9*+?%OO=iz z%@M+=aljVwT*7U}vid zdL)f+u%sS2gIE@p)}V$_C9(c2OF-M7ZW}>!HOnIP_aG?D9nO*^O)G=+N%2y0n~0nR zLgNz=%$nJUO7h5&4#?h92D^xOOpgyIN1DSVv9fl!WT*3>9vMixY3s3kv_3d<3`a_N zpStoh7+@?>QjAWIoUD+*#J-&G8^ir@zTa=Q@5zxYt~YPZ9ax|BJl@}XfUN5+#Qv%7mxlSdoMY`-xa*~3f&^~}CzYh$TJM2Hh%?X2RKq^88;3%Hq4j>B7zwvY#AI>pM4hHi&=lt(O~;xMcSTD&KoKi)e1xe zdyb0ya=T~PUya94srMOxJkP7(t10e$BZYjUQ&}K7J`85`zS{KOhs>GJFm_H#EbTpz z6v{!|-GXg9_HYrdM~fg7?JI07%c3&ee=htsB1=h zM;8sn94zPZuy@C9GzPP8@E;=snI@ZZ0E9M621!9{#s zw2hf~x)PP1`f4;ZwKB~p^J43Nn-nY=zfAu;cja*{k`?R#&?Mz!yT67X`|9c}ESu4iDhP9GduTCbd z{yx*-Y(9)_>)t`mnF_RZ((AIP8+EL6xZ{=Ak=tYjpQtGKxk=iz3wLGwuaf>HBs2ux zjw9&Yzkw0Lc`QQR#xf7x=xk{~*53EA<7g#p{bCW!MR}tW#`uOzKT99Xbl%x@j4*2> zXZCRc?V?flrfsPAqXlg|_|1xX}p6pb2wFAqsRr|8+;+gH!jMXbZj+JM6G z0R{^SRr>+SaoX8|Y2PpNXbbGdlOUFojo0m=E>i zolN6)jA`=(*KV}8HlieJA2w`1!ZGQNU{lUgH{?f?H>XU@&hl{`&O0Ov&tmdl%}`L`{s;A(rIGu$W-j*^D6b_$Fk~ z=p_1mAJfMQN1i;i139OwG>#1Qcb8emWo~i=|8ZOo40iKAB1d)$M^azB%$9D<-8m0s zl*5cUa}nTXi{@Q#Ve8K0u=9+;J#(bykTzQ#d+Ef|!ipT35=uq&EZ%?bBXVRT$Aq3E z&t~Dnk9M)Xq!}WDBH?6G?`dp97ycw?mib(7Z#`-iM@|6C!7~tR$&u!*HQ4d?ztP;< zg@pJyOk3}zapZ&p^c)!+;zf?!&UC=X`0QyVNA{2-8*r8!xoPK78jjtO6gllva3tsS zB*gP^q|SaYp?5dI7djqt4V{5yGlDRfvm1YZb{#XLx#GTCX2F)Jd(WpwhR2d4t%s3u zY!fmIO*pcl3MUTkMBdqII66kqD9^NQm%ZsYQWnMy94T{;ei|Jo3lzNU=5K%ltjUox zS=-#-#TpGe-@?bcPmm+ya2LxDXdEdJnr?cIj3-Bq44%gNcZDOHbR0=%oT@XYuxaZ~ zjUyw;k&gDCG=rINB=yMNI@Genlcqac4ZHe z5@sXPpT=_jK3;6%5F#Z70)xHK)w&Ja_$(aDk$dssu47zt@j$YkBPXa+%K9Q*@b$R4$|~U z#gTfnn2wi=RkPFPu{N(2>UO+^o%=Fi?-h?VGovodk;%f5#aQ>wM<~(s$S!oTWL(wh zOl;h;lj|1dhz_PRmfdA=&3df)ss+b8Xsh)mWSKMxW$M+P;`g=Z>^+3>6*5 zv(LVRK{E%enMd0f?JL5gV>cNqHVBRg_QM3 z;5qj2OZucgC&%N^P#^8JRSeAQ(%Cq9{LLua&nDthNY5Y;{Tqq6a`FzvvgHf0JM%O| zn5PQT5a{hnCDjf0+;t}oWc?F4``*JJYTEF{hi-v~buSKWd zDDU8b)%V_m)6cwxT&9lsV`(V@Y~9g1Sj7V2m1u77rbF~NBO8UTP3jH(q8Cw7aXvoy zUx8L7RPY5dn)4(dVm$Z#hDDLzW8?JA}hp)T`KZn~99~q6% zAg>D=a;zAI;!E!do4zW%{o<=!Fue=Gu2wj;<4t^=ewsz=nL2aKoyQ{Aj*K*-@>p>$ z9UI^GL)ZLN#Lr?;SY1c7zgk5CDjsGwe8{c6m(fcvB5&V1{Jypl_dT!-UKZWh_VUX( zR?Vb-^kQ6n#~LnNGHEu$(T%dvUD>$57yfnJ&g$c3AwX>P3c;9r@uHB+adIdG z9~+dU@4&m;4$*7f61P1ZtreEPjK{V~Pt;oj0@hq8PFc7L)64{~-!>aQu38d)-6_Z26NY}6{wTXhJ%q^ER3yagMfv`0y!yBQL(4sk zQiltCw#H(i~uC@Gfw z9Ly+wJ%5`Xpx#eJKvT#@h3M$a6qYk@!L;{|qK5MD2?)mA#2D5^<5P&r(^w+laZE`a zy<{mCZrY9a%X9G3(|x^47)6FW44X1I$K zk*9a5Ss4y}`~kwJ@g9$hLv(BexE62|ZZf$P40Zo3nd-zZA_+5m{gGWzh8~J()}m#I z_BG%=9wKJLuwqL82kv8y;%AV1bOW9osl@7)x53S}7yGDt?aN}VVtaQiU9pH>gw(0H z$a1l5tW^0-VdRCghc5=oYw*_7&%^4WJ1{*Z83CSjq#C>P#hSh$ejk6yw$#s}=iiRR zCpY1Ew;1Bgv3S{XcxjGiSc#furQhnvlGD| zu@H{De=Uv{KZpDy@8ii~;m8$mweGnuHxL$E7^WT-ny6ZAj{X< zX7a|Ikrz%hA`etn|2faRB0lOt1@;!o4O4M&{k)!2~9{dS&RubZ#Of+r;@q8F^h z^rt?=$sXZE3oKr`lv#vC2Q_fyC=MbwkDzeYdQL^&wxig-{-5N?S}aVCL}O_>HgDO5 zeCGU`zIYZE#RY0<+&&33wt9{{S8);>)@?-3qFEY8GI&>i3z+ullbqo?I>x|{`b!qw zL^5q9rOb!lhU2IsNBW0^V|H8w#~}gYXWg#G1NenC;#S;>d0P))oUW+uT-o^86HsYhBc zD;piQ2C8XGd-f$*erXjQO30CtZQ=rLOw#NP@s);*Va)N0Y*|7pX5nfiJiQf}Mvh!E z%8^El_(pqg0nd;~ES;5t0y;TvTlXCL`)aTtIRXu5k76@xy%jbANlWM8me^_IIMUeI z1T4q@Vk`Dcmc}ZhZo1(;KXfnDaU=uNF1$%Y`o_k$dEo=6@~?)A(p`~`BR!c4Jq^Ae zp3pclAT$!QV#Bo>pIXqJwyOaFzLF#3Z@V3Hw;jf&k`s9DZ-2m@4}2ccjL^*4`#QE9 zEJF)Z1mAVY~05jE?&ZstdqKN9fG|rG>(jn3eogP!>BZA z!sL{FRdn4zkY!?Kh+}3yGsgVPP7Kd6Q_RfF%*+roGsn!#%*^=A%#7=q+1;Q0*qJXK z9UV#CtyWi8)vbHWtI^{wDjphPC}%X&#_AzW2F33#^BfOw%i7mB45md zlUZHD37YS%@&&HQu2IW-KT${tjf{bLBCuSs_p56#OD)hk%QH_ldiy92&WEYo#Ocq4 z1&Se7*kCbrU>xAZy!3Lxo~e8Lr`>ME_s3Z|8Ot~lo!v>fzl7;)F+gWrzm3B6^adsw z7`w4FW=&=PB-3MLlM^iwIHeF38$>M2rKpDq%cRde)K9lQ1l2C{XAX%NIAo-~aC?obX4iUo^a|JaVIR0w9_F z?eZcof6o4dbh$)5f=J2q$jKYst^V`@yV+z_px#lV(oSD3J`T7U(fd(~TWS44>h(}y z!45nVVR`*L5R85!<{oX@bf&yH5F@}F&boK>IBSh!ddH~I?vIY!oj%-QEVs^LV7HlK ztHOr8F*X|-#emw8jca}xPW)xE{gpB7 z#%bHRbFEO9s)XcM-cI#Bc(96X#Gi;4>(*_Dd0TaCCo+Y1DM~j^4SJ^V)oO;Ock=jd zmPAmtzFOC4(B$_UOC2TbdY}4@Am$n}{^p4v1&;4%XbUctJAd`C%Hz|YVzp0GckT)c zx_?azV!?NT?$f`zHX-0otr(Uuph9KlxgdXiy2{#Snr!n^ST4GJ*v!~{8==-2S*H7e zRJvH!pK+tN3{&FsR-5zHXt|e8_IbkVyJ35v_WOYs@B8gVHAyP^molbNQj6FP!9_Xy z1%XQg+sDI4T;ShM#LVfK5D6sR_H4O3QA)ywjjPB0=+59yz83_RnwhJ{O~M(n(7jl` zqCh?L$e&s2%-S~=%!1ik0=N)8X(+Q~SN_Ca#P4hYmLp?b=XaGlQ~*6g3kmL@DTh#l zV|_`TPZ;!KNHg*cVK$J>aBtyRUijYcgQe7r8~y@sc12DRT-e2L5CGf35m2?CoW}ig zd~8s$+WPiG)BCl7C_h*xwr6yO0Z04ed2yQW@*4|F?bX7~M>uC6WE_q8+N<9W=ObQ^ zx6&+Q1`+0!>L7oIDLv`YQ&45eavnfStEl2@2{I;nl%WWk9XEvRO^eI1Ow}?0ap6nj>6nWZ~99o z&u$675wb`lW^i-X@;mUDVo}u3n+zTCP%=w)$(Q_tR!y4lW)f|PStGj`*x!CuEc%7h z>y}7d4ds=)SiVuF1KY5vP|p`G@wWNUiTDUhiC-y|3R(Vqcg_5|x@*CO{UJrk`6i=8 z3I4tyshU}HN<_fmiC+|czvbM?y?Ht5}^pJ9{m3-L>?mF53 zQz4kEVF{l#BuG@)COR@bKm%pY-}%Kc&0cq(BN)a>K$PhwMN;~EbIm2hL;Z}aat*Hg z!a&XU%e@RjEzCz7jO`Ry)!oR zNSOA*Kwr^Q1tQjH;^%mj=Th?#CIL?sZggiAO6}_rd__{FuJtT_aLV9r4i)5H!1%lc4#NJ5DB#8V$9}k^Bt_O>% z!wH^liAA|)GPtKwgD-j;FT?`E;~jCxLGo5BOB{vPM1it#xB_Y;O&lKMUIPp**yu;> zz?cM?gW65K`!JrXn~w|Z%XVte2p?ta;@d30#-g`_zQn$FPYk!Q3|I`6UX2wscw+u9 zfUjB+D4(^UG5e?P>!(#sj6exbZYPNLC`YdB?8K!)IU9f%WglWS3~{p*mV{XFxME(D zj;hN~H&~b74_NVuz2dNnv!(0>$pi5@Y;7{CZ;A4g-tdhPoWYHq8^PAE_qZOqrr#{s z`i&JC^FYHikH0sge%l8bRrV@3bMvnPi6q8)@D0^9Cb;)9hd+IERgO#^djSTMaAolYl zpaP32#5?{#=paMDwSKlfq>c~in^P@bcm1-;`HBd7@DACG1t`4HFMF>*KW@6TB%H7w z11yG?(mUo)?nE->_URG(Rsg&7>wnU0J)j092h*FcfLbd-N4EBdk~s%rqBL>BuFYf^ zgbzdYt+f+#c`~msF59@LjEvn1oga~M(YJ7LpS4zN(bnZDD8?Xs(aIl$0f#XV+Xl~Y zs~Dz{k%b(f*Vq>{uUr(WTKlWpEF$BT3-`;LtT!Zni(bWOHZiqliVMf*xVGc>;fKWs z$_FxqM!T}eJuw;jW^?>%N~Hh81;8Jr^jXK%UgaVEUE`B9L$hkgoE{`P-i{gVQBPQD zz2t}S;!Nhb>2$_VoXhtk_Mz(&Hl?ociEc5S1ALBBvkiwg zEYZh25%0agB-lRun~FlzVmA@)JFa1_C$m!Fz-GlNXG`haa;z{+P_+>2ah5#qtuPRJ7<&771X4yl3$murV;GF`6t}^+Y4?{P-4i`XXCvT=*zkIe!usq#sE( z-GSDgv>q?OIYjwN+JIY2n(~=ca#c9$bpS7h;cLS}rqwJMOY_c-CM2IxL=~KxZjSMpMJpLftidE>gjXi{0KI}G*%jN3AyQ|t&;_77c&G)B! zO{W#;$(&ahCH?}LV6;)+D{ixLcD$^!Q`ozv7RZe3wyerwDFX+)+jw9$E z*1dIF%@d|I3VRBl391%5ze==#J#sN;R65!Kp(4v*NE<)#m3&gq48>VBK7V6#(19Z|VQ2){&sO<%t7MnW2|LTLcAwWk zjt#O)Wj3BBfMWg=R(OulYK7cNX0VrK#4W~u<`S;P(?r5eiVpti6~S9GAOGR&>vc~7 z`*eV}g`W~IXQRC%#dJ`?lNKpicx#UsAt>)qoH!%Wp}blI%Fddmg+6MPowo{JnoGIW z%WVsOnUYls;Vb)-z-G-%2H|l#!tE$kC~H|VWJGSq_v*jnS{XQ^`1Nc&5~%*y{}-MX zO!Jn$m0@u95D>~$B|Ezq3gxy*CNz55Ksr&FX*Q63{tpQq9rj&$MqS|bhR*SRS23_p zG@vU3-7GgAp#<5Mj9Q^e_bUqY2=Cj10U#7}sm4=E+~_j^xZ zhv?rwq5g*;CfNAK6^}-&$%^#0}OQ#R@&ZFR+QRNTq2;bxwKC_dyl}@GeGxfoSLQvMl

      ~F&$r|g`EY}(`2S2_dyY5muIZ|)D-94KcVcT zOwVPIQ%K~Un#Hn=QRcZAVdJ3~>gm}dT%N*$^Zl0FZOu|M^U)$M=09+IynWrtb zi_033Jr>8Z9*pD^ech2u@EItqBHq7$GO{4FR$1I>G%=Uw6uK#Zj?PW=&7N3+naX|N z$rZ~=E|Pg#X7ZTe<$m%@Vn|RyaIg&7s%s|?!aG5(0UilL5P$H)3O|^WPf*>Af<4db)e4_A5hepiybI$D)_P{Ix{@;Lx%KAkYrbc#hhzPc5cV@zRantlss~TO&)hL_ z8KJcakz)E8($Ta9#ZNUM-axvKUeB>GQs6eX&{Sup9dlM8)IC*W0!S1fBB7qss>eKR#E@ z2LA338}93%)4v>aDM8fOd24Q56Nq*)IW^Vz)1kF!nAtZ7n9^q^|9){~XdSP@Bpc15 z*e5BVL}4A8m-ZxP45G!cU^DCH?U)ACoSRqnSNK!u4hH(G+RtnW>JJ}6oj%8>&mCe$ zS>PfGi>tj-gRb2p0X*E`(ZCJ3h2&2uOnJLq)#I(-A(8i-cKN(7siky(e@0mtq&h%? z+@^oBrVtzrU(xP}jzBoDE1423AASq@vnw{+ECN!XKp~zS2+5!_iUS}|a)a$%os18# z%nKkr`~flC?ui32fv(HcK+PO{nRH?NfjAoj1rbuGB5W2t3;tZ8!(y9u#K=Z9Pt~u| zd*7K2?;XgQ30J8>O3dXLj?i`n*SElhh$V&2EmvF6yfO`>BECjRVI>d1EQ^ z)9%o}LZZH@5-t|OBtMz7QiZ|si6Na(d_quRxe7EBETc}N5;Nu%^D)6qu{{)yV-&jE z*+av;9u?P2%rts!a?4U?(C~?8lcRG(EZ`yAks6M5W_zoA5dhuFG666%t-L~&5p>D^ zl#Ez4pVU2={?E`=FY!csP|6IQ}E2Jp;aS$cfcsDEh@6b;+-oraF{ue~M0Ki#HEDp#R z&z9V<8E*nv=HP(j#UZ*7Bz|V^Pqa+oMsDds74j)9zP8!ZO;}wgB=UC@iGYiQ;CK5T zlp{}8G5OdYLC;XkZchuswUaJIUg1v6$R<5Q0N_*8gCMUt<@uXLub5g@ItPY~odFO3 zitS$%&2%Q&gDTELg59B?$1%CR@phS9(Qf%vXTCuP8+U_IEkxl~HjMVDVJBl~iGTg7 zKDWfKazc=hbKR3$7AZCcaCc&M`NGswapJ)1t{98Lx`*D}0{A?F;f9qoeA(or@a-A7 zsJa^zVcJonr32iO^o(Qri5XjPL-l~I>aMlB2+1dIg$R?Wn#P7b;HSX+R!J<5YI>BOc4s-MEe z;k<>?7v!LsAW+mTapZbQ_79Rc^x;NR}nW1s9K z+{3Tl!pc@pFO(rm1b5SY|#@V(!4xGcQkt|hOH1~VNLM~ zgdJlt3bRx7%5hK3``&s@c8Wk^?n6rVBP6kR`tUtOH zgRO=&OL7tVINGtNei)KwyL~31VLD1>kD;nYkBZ z&8_-fhsJoqn;mu^>}xssHx0BF)q3w50IngDse!Gpf+ z47c|7Ju+Z4YTFkQu=#=-Gv1x%Gxo*b z2^X;$5FW)FDp&Brzg%GzL2I_lZ!jrpFaUAc=#`?@U2kXMxNMWdK6gNq8n#^3+AYuR zD;b3c8xo`g)BMsdgB(l=OjHD=9Zh6#AuRL{QBeZ7lQ}Xp3-E*(T%!t);*C6DVa`cx zn75yT^8+m=SITx6F`7aA_7pOny$$`g*^!*?>UTF?lK)pwFvXpdJ>d&+1zAF#OX< zyl8B!!vvKk0>{>-%jSyX%9^dho3^yqx?X=k6~~3q&s8Met2}W<4@2fQk2B`6p}(^) z4|>Yd%%`t;e|xya8z`p*;u;66O?RhrX_jL{e!?wHvCQiK)oJ3x^_UQ+6 z$0sF`K3s#thL&xnhSkThoEuI_UI&_Xr7eU^jZ8_)Z7ZGx5Pj;$Jj@cIRyzk0G3Aon z&23y2)pU_==u=qD0==uEt=tp`bd0-`8s1Yug?L%YiZ(ra5#-OG zB;j_gxnywzpWU<-s}%PS15_f6Dc_TS9_cfcOW6Bkl;IEQ-0<3GS1~Q^HT1=aabbrr zA3wQ$5PV$j(qsu-@0dSsjTH%=j+oO^8H%qSmq!i7MK!ceYhZD?<}~C27%ti)kYB=o zPd=2#ZB}kh6 z|2b{SL~r_rp0v2vQ;=vDQF6GVmO3@mBG~+7cvwIV>kistW=LiSk%9M*A&ftLO7q3q z>_3~B|J|qmS5p*lebZdP@>Ez1!5zDX#8edJl!f_xI1la5#s8ny{XbpNaD}Sq>)DGh zvvn~zrP=5-pKXuPy}i53sjnxDnqaJ|uMG@bu0IOnF8$B>*d&LBLxd;n zY|Im+3ol`=zO<~?Jti4*q8L*-RMgptD%1<`N#UZWFVnF!gtxb+$SWbs8-8YHVS#u8 z<=FCGRpKecb+dgIx|9W#7~elU=$n|R#HAG^u+uXz=;REn%*D(;X3AO#2?^n#HD9%- z*yI;rmQi!|$Lw^c{>$RJfAqj;i7!gU9ZcIErJhzLL`=Vu+wafPjT%8M}YZx7E3J_P!w=wyAI|?t=R}WZPq7L{u5x+n$IHci_q6Af{P%SJ=K>tJz!oRrQc`rDCR3mm>%+F2(Q|bxVxE340dbPT7o(7sZ zjkVAsuFF7$MTF6AbeEZ^STt-DDtC28S@q)PjIUKR*R;}5+%L*>&p`}27uzt(uqVvz z7|UT+tb$LCQww^Q7f{$En_=cc;z_uW^TzLB$_A^*?Di5ocJ{j}J9xjWXgUHQ{7gd( zsRNtxPFFW%NbB8KB>Afb+`Ym{4eDd4|6E$OW=Tk5>@}tCip#7AL;Ue`h1zh3Z{S(| z6_y8V1V%odpD$I`IoXpXOy>9VlKXj z%bfQM5Pu+|)H_dQj|O6dii7zun)lNlr?IU?e)%NHPgOvzDVHIuTAYizPSfyyY>xs{ z)nSqjmmlY&JHwaJy_8Ss|K-qyd}(&bZbeoFHSAnWwg=U;a7=kCqz$1&tB_coTRx5~ zcK9vF%<7a!U@N0N$}G-ZqGdpG<5$_gsk>Z{mmi)U^}_)ej-G}r==W{JJMcT-_urB+ zvBh>Onh;7XY00KGS0iKN@Gp~$HwER`+rjG!SG!qWKD%N96ty&DKWxzst*8g-I^StF z1lED3l@_LKYEAdH9uJ7WZJhDUby9D?-0l4l^l{rAA{nW<2k#xcN3=s{;0YjLqfB$H zbr0ieKc7!lo0;ORS>S5sV|7>fNlti&fxErfu(SF$-*2H%;@PvjLGLUt)0^Aa4@?2U zZ&DU4Z6v>xpFWoiAxJ*z5pKVax071xlH%UKiKMXU7)Z<#>BQ%*kLE6y6$LgqU$r9Z zOX;(2(yd&IiQCWEA5GTNT+Y`s4rfkcjB-IN@(T=>3|@Eh^Vw!o?YSJmI~i|bw$+^56%ZiKRlod?lhCDb)d;0HHIqJhXh=a zL{i+Z1Z0r6m^z=}d-w=-0i`--t^07d%(+j8_ncRis13SHGbo5kYY5uhc1Rox*^i1a z2sI5`Pf0=G*E|sV-ocoNF-4;}W1TG_nvkcnIx@eDJ!_am>eRG#)Yn!k(Gu#8PX&1P zH!*zl=N3G<`6<|@XZ?*^8x>Y@Ul~6AP790E7&LIsKni+{`G@H3P*CkBSW-Kydw9g^ z5!44@=SN`N?=3E)?e30icDcDr(gh!exzFn948u9rYw|#mk2gn#Q+?YX3wAYm35Y^+ zblzG6JM7jucL(XqaP3I?9Hx4b8p5Rh3%|Uk`}n1+O8D#B0Vx+FQf8EYF&&pb29=C% z@2K~lTPWRmD<3YAw9{f)<#8VjRM7PNM#P!*J0z2}^k7ImwT8l{Wr^}cmFAG`#YV#4qZlNc}t+vNnpEqsl*6wQ8;%XPJADb zvY0W<8&pp0< zyWoK1v{b7S1>)0-lYQ(C&H5oK4sd&dbJ{H#wtkhW%>F+^{IFCNs0a;3K;a2}X~r z?pSV8HGU6KgXD$%7!HTKB$y?l=)I2XR*IWa^&zf1Ez)00HJfN@vq7j23D0mW75Um~ zYH~vAGgKYK;HKDy{LHw2y^dbEGJ)Cwh%b*-XFC7N2ZAW;I2yByTGO};R7#T`d<(q5 zzmOc_H!RX=W)cTtoOSzZrJ+^3x5WczVkgP5@%Ri!gmP|x3=>M}g4 zSF(`k)Oolvvo6Cup^ULvV&g1%DnzWHO|aGujr#}L<~B3@;_X1@FWUAt zN3Z?RW;-lM{y89$ClDX zNeJL)4GZxty+k+K6z0H|G;Tx_^FNC`&O$C)RKXh8qHLFReh}go-RsN_PlBrprav8m zneI2qhj<5zBUq>Gyh)^+^AF07kEd0znRS84q|z=ykG{YQpZE=n&A(#294!>HpL?tc z161a1(Ls-|XO;Tu&#Ep#P~ELTC8;HgW^sKo3;Eo~tUq<2-u&AS>+62nY_oVgRP&@1 z#s)d3ydQF=%#LmsB!p@F5VRZDeo6kVb8#=FQI}72gb{Y}dTAU%fZ$W!567KqhJHQ9 zbah`%TlVottu!9tLx{~dpT4ycDpm6j_!JC22X#cw>!+_fK$aKK!!qq*WoIwqhUV}~ zh89bnElR8DPtxDq&BU$#)zpBj-GY8RlIaL8{zAN@9+h2GjzHXT$;tZxW+vciLkyqI z{d7`P9xKMFR@PH#5v$JEHBC632Sz|0{-AR$C}$9UA+aUM^qraaefDGEC?dL=M;`_v zvGCK+FbB}=sP^A;Ev-i&TX*{1;XmZMcH#cMqM-(={~#rE5=)nnA?!-TXs)``@~Ql| zgjHdoL)!246yv*Pe!ujd%=wF$6}%mHRCYW}A7xf(1@(;AhO2vPuMg&beGm1{{ug>L zH)=vo!Isw)MByy?J?=cw?Db{-;0g=^O|kF#)p40F4MR)DWS$z=oUk{ArQO6UfBLG& z!4BKI6`nXY_rK6i?$Rf?L+uR{4hic5tYztq#>0Z6OED)52t~YXo0T>PX9m#WS@%g9 zDZgh*bBO*P>TND5L~3?*rRLX+;>TMmx6DApBl5*%RZwN>-Y^NcII|GeHDbi0U;pGl z2|1qmwu_x1!gP`hba^JVawCh?7t*!X5AE(?B-pISK7v&(Domlt{uibH!N`+-b>EfV zKiYMU+lVfmeLNEWHz_6XpJ?IikH#PwLAaUy3124jyy8O)fa38uG`fz~OC|@wax=D) zZ?#xNcmT!?D&kcrXp&_1FQN40z~2N!qwzFESUN`8>m07B+3pu^3=!jr+`{s>@XUo| ztZ%-_ODe)JFVVw+O!e-^v=TPAOYn1ZjkdZo1Sn+;-vrE2^nSg8MTOEV-Yja_n6M2A zg)Xn|2EMfawnGSGwm9=ww^9%KL2MlH1Npb5Aq@*ljhx)#ki)Q%ntC<-aM{6kIGF`n zxz#!0?9y_g!Dz$Jq@)E~Lu1~D3QU8?E*vMPcm72Bry3z_`Hm-F1~s302!ymW-HAGT z>tFvg)Yv!-bbJhG9&@)BT zaY=Isvjk(5mZF}nQfO#GZ8&ZZV3|tdpcHko^LBG+>CP8Qgg;e#>@h=u(zG+j79~RU z%W%28>sz~9a8#fd_~4MZj1phd9+czo(hwhE40h6+Eu!RQDDXYV%dhTHN1u<$SjKzp zYMwZI(+R_jTE=ZttG}Z}K`QJEY&5;r3iDg(S+a5BA&o!KE1iQyG&6ufC2;PC_CGQ+ zu$??XWq(q95>R2;=E}SKnk~SU^^N@Cgp8DD@k?J3dAI8%O`T%{Ga(INao%Yh9jB0? zpuPer(9#o5`;7$fnQ7S=KV)nt!cDCD679Le@%o0t$SSXx~v4vaCU;i~Rt zc@ZK6=#{?-UG7!BqnS!4JXQ)MGPBnn#m>Xky3XbO@T791C%Bx?@ z$BP|Z&L5)P;Q{ALp=xp}9+;I^6RIgOjbd+ERL+5V=lKcKe=v7(7$+cb{UD~crp28t zuZR+j&!7vy&*;5qeh62=oeS(8M2TDmc{a7cY3P>1mEIwJ=EMZF1)~!9{8W|CPM5`# ztBrO^0F5orjXD<$qgCYj_{5fcVGCNK26oIf;ku1!4TSxg&isBLqEtll>z@KQ@XFXk z1EbbfeW89Vv@J@v{G9rIYFSt%T%KTBkN5X_F}R&=WIpTw4X3tCi2FNrQpzdJPc9i# zYGYcG{Q@GLn2~a=pDHhh(bbgfI8H|8OSM*nQSnLs31&a0Z1C_G7kgG$F}iCLyXFlQ z;et`xf_pMNsVk4R9FcM>8&TU8p`ymLLo0c&G06knOJPT;HICO^(RCE{wIMn9UQkua z9$9kpdcf3(No(3j)9aBGE+>&m%)48`Q}N5U>8y)pRaUE)))qNMG8lZld;FUB2O0z5eUKdVR=^|ah&!dq`iZL$NFd8;#T-H&bY>~iR|D9#2}VxFHOv~H3hU-$w73d zvN-XDP|cI8?N*+tIp%DUX}j|Dqw3ab`vbNy?HiJgIwMko!|r9J%&3QCvdXr?(C|~X zFXx+Kj<}3)Sw}vBwY^iD^I@k#>Q}F$7kL$>S#i3e=i=(^d`k`bu(6-;XfKBkd0@?t z=9NMmh%(FNRW}qW2^byB6u;d=)4T~ZqgdZAbeeGM!IQy7uz?jOO!9^@Qh1NLS*s}A zknrLFe94V9Xy#aKECdy#Fw88q*(}N@U(`+xb(mv%6f(d0jDlsC!@@R#$`PTGBnhL& zfqI;{J_g0o*KKHOSz$=ooNqKQoy7&=Bz@lW(%Pw`Dd7%m0pe!Zz@+@li;mJhVt3$Y z{j|gQJW`oiuEBHgjJRKiai*1=QPMcQBBU$miuhcxW4G6AQd20hu^5?f2TL0Om=Qlk zv&Fa_U?-T>k}*rFYl3U=vvIqy`1$#5oSbq$;!Q}!F`CWnn!7?pRUn)4I=)jCgH|S! zBU5yVBrBDp3J07VX7BZN4Pj*-Oq(92FJ_dki z5Ubt<8D~|wb?3`R!F&7JlMs&V+yxW1%uvF{4pl|0u(L*cStY-Wtp!fcR0jO%lKee0 zO#YP%5u0m11G##lcBCv9iM|dyEG(<^llY(q1VDHFX3;r1Gd5b$SjF=K!`t9)1RqN> z7T+Q<&we?87}4HZftO+VyWFfdk;|@f*{nh?5}e)9BxirRutz=X9LX(VJj<01>{TnU zf=?zq>7B73qs}g&+OI0`*Y9~XtlH1LU9O?z{(jq=G!bpq>I9d}3OB^3!%gH$4}CLh z)U!~gJqn~=Gi%`jy6f}T0-mZ1O#>=fgVJueIF%lnQT z)2@l21_s({6*F*$b@G2O&NQ|~5`1$~IhFUgdB?O7q~qv#!Mo2G>h5T_*1cO1W6CciOV?K(2CBfGnmObw~RP24W@Kp3mKfcr~MwRU5Z_-7kDF zLLZlc99=#xe>3tTcL{J4NMO7+4-MarI$#x7(t_AAHIf2E9w%1%llPAhy+@S zh)CL84b=MRe>{&?>Ip~H(Z=VuaqawLeAcH>cA>7e82Xri3eHhC?isJa4{q!Te7N7= z$vAjN%F3oLlf3i+jg>!kJNsX~B2EHos}K+`T)&TQLvHP{N~vd`T2*!>a&nMVw!ze1 zyd#{aXF+kE=o%2d!+p4U6Z5;>{ig3UBr<|7g`ZPwRX1RnUtNxB@kP&NNIG4#A+sq+jRQle9Qf}Qy93U_v{y_ z|1x6S9xq+YI3d&>3gHv3UE(2KI%lD;+me`7X3?bc4Q-5ZYC0x7;f#SH0-TrRD8YB% z_D*YuH@1aM91>)>}fCH2up3~Qj!T29nT;w7xCelhHMl6^YD21lyCqM6f z`KbR{1jQ-i9azCfyvkc}CFD~K+?mcM0TzzY?upf+vB@B>wqkK@>e+?PjsPy!FZ{R2 z>SQj@-&~rDpA!{?oXWxvk4u&%pLt9m^T*u+=Jf+%DO_}uqqy74kxh*E>%hJQiuLz^ zK7A+$%XGZ;PIz}xnxk@p_G*)Uyu|d`gjKT?g9-+Y80iI&g+U^ZfDz73wf=90xNH=} zg@(EZL?QY!nmvc}i?gFei?)%g9o{8>bjk`*daXz*J~FC%ebd{d9Sst13LKEj)?5WPEf9hx z&zLF`>WXWlGJO}O$hH?}Q5Y`1bz+VHDMb;YbM!nV4;97gZvVa|3o$ROnoX18(_7(wgSova{(dvk48 ztA5^ot1}ri!^96o!0V@++#DKO=Oqthmfss$Zybuog zgbx5V#@LZ8goiCTF6+kf>1G6D%fmmsI0!M1&Rd$RH-SvHm${Mt1BX(^6}x1QJ>=r; z%X8y(ut=_a#rNwhFAXD2u9RxztTy%p#v~_#N%8(_dOG?J_$K`^74lHlMHypv=zE8n zD5_o`u^^C>q<+!9v{Z?w$iItI( z5Tyiu*}D^EL)n_Y@y8wK?QbM7?`>EKGvnwRTd=@=|YjYDu}~U2_7KxI?c@w z!yRQ!^>8t`p|8F3p*7y6-|30>7N@6^p9yN96!n3L51S#NS84mcSFzFLcxoAPEw4(N zy5z`lGb*PV36Zy|Kc=8>^SF7TKmFp}%Xu-M`ZFGVEQ6xH1<4ytCgQxO?mqUYn1+|J z;@r*UF|aFZXKBKEU&mDs>6{=Ls(yEYY~dzKXX6t^)py&$IOeb`E8TM!ep6@zIev}T zv{0yZs~0gSu2dn(Zgk6GvTQeT&hp2g6-8WsKQ^QP+b8C)MW&W~MbS#_NVe{yExXpx z@gDPE$3g~-1q18MGV!psa%dN3+SLhU?o@#%1d^me_F`+V)qL-)02)j!bWFRoNPJeP zY0Xl^GS^G6h@dq~^<@dhpN5tA_~!m1o3O0AQ)fi^x_GsE-Ffc)XcWsu{5Kx&K#Jhn zDuE)X_LYEzZTlTz;E%6-3fn5!Lb!T!JBKJAQ*cls!dr*7bO_@A>R@FD*?#_u!X_kH z8lEZ@ag?TYL~BfiiTs>*Xgf9g-x(A=H3a^P zwHV9ffKGsP1MssD5Lqa^4Q>W^r~4c&M~T>asX}`#qZGFFtpS&gsTGu+Y#62aC2q0o zW^IEcMKUuGXV-84IX=J(cfPlS`yE*RMJw@TO^jm`wmNmsu&2q%*TcFR{t0ouT6h+9 z)L>W{O~ClUPkj7n#`*}_8E`*0W>U!Vhoc6y; zrra_o7GaU)%<8=EzxyiAob+*R+39wq59%5hFQz4bOPsDnS%6`6wJ(?ZCxU;r)CMyQ z4-P@P2fzLnZ5a)HDJlbwF!h% zoHmKAw!-4b-y`*!f?Hc+P}?U}4!N7;DvNVV?NNX|tm;TKcTiSoN{kMX5QK`POj&IJ zQ~!%`v_ZzQIPy_%?dzTt%4PIMxfrdkM6AVv7NO8x>auz?(ue7A`Pdv9KODWLm!lr# z?h(vLSaRt4Eu@+u6ii>o1baVf6Ct5ut`Pd~&3tUu-#;U9y+bLI%q@PDBxTe#Bs}K4 z`ADB>&~*<-AfpSDRpS^52J4N(Jk0*=T2ZKQTfK<0YjRVamG)n8Bpxkp6?M7w|6E(a!QTv=3jUrBf-?TO(=wVARF>JN`1pKGlnfBWlZ=)8g+Jc{4 zCL?!TikmX+>^wZ4)k@a!AlJpBClxa{4&{DS-wfvPY++-we8?4`nBke z*ERm_m{aCyG4d(O$%#i4=WOs@Ynx%E@SKAz<7A=B2rE$GjLWSS{>It ziRHuJfa{kjCO(rv{)pT5oJDThk(1{RN;(-wFsF{ZOpDWY&h+=_43gp91N|A3|13L>JZs)9=%k(KXPtKr~QuC{_u z04U|8dbip~mDN1YF^EhONNBy~LdE+v&Q`vE=_FNp3=knDCBqTjQvsy}1qc3AP6-yO zlP(k_Ve1Kk4HCY7IB%=PHb3aF8g7M1ZU_JW_X-Zi=qD=n?jN#Wx zXOx2dT-Zwm(TZ5J%Q-xHc}|YSi$yzXR=POP;K-4Gp#XnvpP|2eBC2Mxq->r zKVQThk0$~nSjL*FSYK8*{;>^M|yVv_RDv2ci(w(H|cM=&}?;Y#R&wrDajzGBJs( zgqfTUv^rK?p>4a_KT&C@{Q|B&L&v}zcBq!*IX>?5_2fx$0$%)T9eGjC@cGxO%!4c@ zGy2ms0XavK)YC}6&f*VOSUe0ZN4ldd?zsE?j@^FgwfKKbYCtwtbpo{H;2TjPg9fC! z4KLA0Z`L#wSD1bsTUEHf3wy^N1f=<27%2P`4Z_}OG8QZ^N~-s(^F|e*HN!ykX$hBl z*!RjH53@;Cg&iHEP>Zsqs?VCJ(ka?%3R*kXYDbBR`@rqf44{T;Sn4sxQmZGTLm44) z+T7Q7ETcl8F~7szd9G{bastW_!#5sMjaJW!tCI|J8f?tSZ&-0YKyBhO+JAz~geP+> zqAM%&o=R(n9_RQg@^Jyd#<1jlFwB6-6mxkNWH{?_`R_zat#>4LVJS}($%anHhVGx8 zJpPBKLzfGAJ93doT3_`YUK2zX>WDE;4-Zhs00)}+% z;eXl2M)P^`dV>>xe^RDjt3h^Gr7YGjwK1BH)w(v5ke_VUSNWyfcJ=(BJKMs(RJDWl zhxy;V;g6L<2NC>AvbI`JW=9ryxNF0%$lGXTbpvcqFZKbhWajIkpGJE5q|arp@QdzI z#|YF?DpD$6JQSho##otB%RS`a*vM@Rk&}=o9pEeH$(K$Q%so^+x1fTk0=q|5vW`FL zA4xgVv;P-+UlmnHu&x>0Ex0ETG`O>IC%C)2yK8WVkl^m_?(XjH4x5cz*vN3!oqJ~P znz=J~-sa&v_Uf*xuCBjp{Z;izGlj+k7hnJ^1GXPPztxv7k2yRW4uQKz^1soKW6(#3 zJjUP}4H3ddfo87|DJ9`aG#j?7EXoacJvie8X%;z-?uF^~Ux*a@B~pK<7ZZ}h;h;;6 zf+=q{=ElV*{{80{US62I1>FXo?*^!-wxu2}KOPv@zdJv=&tqBQWXf#B-dbtqb&q71 zW*Ef)afcZB<2-cO=#RqaU3N%_Czq1}75~#65|NoyL*=HOpiB3X%(V{Cz@c}46iz+4 zClyYp9W`-&^8i#Vwu8fha^8yx6K*cW00q{u-G~0j53r;V5gVwr_2WXtD~9X_1A@wN z$v_2AAo~^=wC(;6#pev`(wmCHl-I-I=ndWZmDn3S%r7}u0cPFs-|F-%r*=OGU!(!S z=xu%0=}zGeI|COeBaN#0rX_J|%8wZmWe6J~I)UgdN$@pslY9ZDfA|N@qn7%y7D?iG zpQ*j5#Ctpd&#zi-*}xYq{8se@XFTa-+dzL|PM0E#iFDYiXTO=4SkeGT5XMd&p5`hy zwelhP)SQ;iu}|6wi2Ix^)?q^_)lZH{H)IsEDO+w=TQjNO=Ci4RpY%i;2Zpvb)qcQL z(-lW;a5yzmb)KkdX^Ima*askY;ycqRAuu5JBs?vd_dGczxyth9zOSQ}$nXnvhN+OM zMb{-1e(03@SyihvJXF1~t>3CVM$W67+qvSyB=fYxH1bOARK{vF;44{?x{h3-=OQYsq$qQx;R)^CRkkEbDX?f9}K|ECG z6W0@-N@;evbE(skG z3Z8L53d<)en}aa)78OWYr=`m?yPwU~q`b0rTz^5FbZ2s=f@0VrrD`_+D%O=vL%X2$$KkSkyYk?}lE!T-E=!kL|z-{*q^k{2SobzmpzXzg2*N zZG*X}1~<=nhC#6Odo~VGp;J$&#z4mB>$7uwjKqk>+Id@tSL*qR3hDbBke+7Wk~=1! zhtqzjDbuoI+e+9#1F69(J-jO@)L6wD(;8PT_jlG*Op(Y_wfQf`7bobIZpVQAnxxwI ziqOPTNtJ}Q_CdHeeoC&Wn;KgXvwFZ+)CE85CGp!pyz|uFn{2|2%Tj?X? zhhx@SlUF;e(!b$nXdpKghsuO5d)&J5aSX|Rshn!DG%e*{1TiBdU5Y0Qv)Pt(kA|Qj z6|95=%l(u@VC2o5k;#Ql(0%HMT4ZXtf>!QOQBZfE4+UABGI57Zqk4#6d}B z+Lha;Qgfg7R6R9=0?#`=POJUiM#(Iy+6sq1MB;PojtA+c+3Sd_2-~hLlC?IpnS(SQ%FWOYU z(qNPrN^4lO=IH!y>Qvej@z^0k0HsI{g4I{S9iIuhNshrz&0_j-~;wioX*40kkO zjKW$*?8RENB__Y9RXkj1w0GJn$*Z3W%fSvAtNFHg$LlkAgu1*pMZz!2u(Q2K+kE(q zzPy&Xk%%4EeyR5^kE&fJgpuyD{1qXalOSEEBC%xaFEI|IeO%QK3=8~0FQFrcXu>TF zOZ(~m=6cG$Kgx%M+=>s&>otyVG80;Dcw(sP(M0^HyHZmd4Oo3DavGL3I*l$gdvw0> z=)@5}Dp{fde|Wv%^tD&BoU>j;q6-pj~eP-GfESp3-;Kwe1N`rs`d zto@*6ReZ2e8?{|NIP|_ZetDo5Q@b$M~AY{ zS#Y};u)L(kdOju{rd(k!OTi?b%yHryzBS&?>pzhlx*TITn1#-xn>UQHLHU=7ttL`^nZG~f>>l&|!=Rt=b>sV;gm1gwkZUZF@W@j2{%x<5D$p{A*&F`;`#$HT(u^TY904mqFh5;0SRuo)Ds)AA*@@2FHm&N z^JS#4u0Qhh?TAr>=;5M{{UTE>Etx!; z#gu@kT4rde_IheBPBmttJ2wOjjYP0NRUuR=m1JdrTE)*|>}@zCx}=@`%?)vc96Qg` zkmT!o5!<{W`TqWBtKMq6jmexWlZA4(lx1c)@=DWh9y{v&=sgHEM-F697)SBQmP zLYp&N?6k_aMLP!gIC&w5DC&$ge`I9=AC&Pv?VY%4-=9rgDkO`<#c%88sDthd4zaA; z@#?}mI*@GWDKAV;=N<+Ayl~#jbOYnw9KE`p$2{fe6Xw(P3q|bk_Y9eK73dpCK zp|+OmOs4{1rYK7u>+{7D1D6aQ=JWS?&JWqC^ z#Ye>2y$f2X>S*7Jkq3Xu&E6ffcWe%~4jf!&%Lxt>^w57^|I#K)9ec?Y{p0ctWxMe_ zz+BJk^ACET*`iAiUd6nA?`-;kIqp9-Zl~(eD==e`kvnGB!zU7#!zm>6r9L^)i^fKG zLfsFBD|M4UU(3yQNWGVGWT(N`m&c(w{WI4mtz<~jaRs}X>hG}RK(o2~KkOqYCky-t zr=^xCVUYYz93)E9Ph_iUkPw}{fpV|I20qVUWi*@6^!=Xf>9URxaCSu$6(RU)$Q{?A z9_x-`IF&&WO{Fv=6}8qS_X9^%R-fW45kBYa&gqFxuP1UPj7P(`QOVgp6WA9sSj<#e z<;)XeYbuuN zPnEeyQ>_h~!U)U?X?`|}$6Y08U#;+wWAuefucpC5bdEx0!fnMe^C(|0J_kLW^>fw) z-%6?{5mM9B2V`Q*5UY}U4jX7J5C}C<3gwhFeDcQe4euP@oUxM>Kn7wB8hlC>DxN7%kPTUYB{1%QBA+Q&<{{v% ztO4lk?4BZQ_n^BF`+O&9dFTHY47Y$&aD>8DDpc1tF+b>EUj9b6se5ec(GP2@RE|ZGzuUbu9KeWR2LAfPq|1X z8|@G&5}Y_NN07;0{Ys;FYX+#soS*t45G=?CK>*SW>JgwLz%b0BF01M9lcbWj*B5og zi-x=CLg6R^!^ee-H(zY-3gzdF?oA$-S3dBnt)@I~8JG}Wi31BrT7bR7Oa;!?8YR5W z`_RKNV{)!G>^~YEzvj(RYt!jnq1i<2=3n;e8rum;6L2q@l!Oj$R&uve-`?yC*VUW7 zRM>xK(5X2=Cb5`hh{u@fs!3GoTeR70^yp(O!4}_gB^`>r2y*ZzVEw!K{Cak&o2Fw$7cDs^FQ}4L*E_C%8N@6|6pyK=hqUIDP%?ws*g)V{^q>8X_>}jwuez<)Xh< z3AKq9$NLrDx2D)5uUvEvDzMB{YcvrjRG_d!Km9(_+mtrO`16!iEXC%*V)9y?t`NxU z8f!Uf_GFtp_4P3IelHG0m96UuX%(BxnTCP64Qu#Jn=scunfd&Qj~KG!HE`B?2cGx- z%A!jC#lQf-mm06*nW{5^Z*?4>6>c*wsiBXFC{4?jn%X*bj|N|~Wkz?=ZO%sC1Gy?u z_FRDLtnzm%k#eVmL42qUIl)`pV4wuU0vA`0Csg+obNt$$4B|`|uhZOUG&$HUa|bjv z2YKh|zRV29A%>jL>k5~=h=3#UBkK2R2r6z>g9BsAQ?~1hLt2rX5)y?REYa_(seTIm z;*e<@I5?l&*3HoJ$k}apP*KsyNPaW!M_t?**c2e#V1Yl~=*F|uUOhfL0x_9sdO`O2 z_w3v{j3)(0k(FC9d3S14j2fGyK>i%e>flX1nnu4t)Lxzs>~Tu`M(KAg zW}e+v!YKRMkwLnM)K9aona{jAhI|j9K)j>V>Q&~O*jwSQ* z7HEvt%q9M%f&Y0cxTSQwCZhT zG$U)UXJA~CCku^bE5_PRhac6H^;IebuM3NvZgg4Nwg3`o{2!+65AZb&X-$ru&Ra#u zu^dasO6i2a!nW2|+#t4gse9Y|fvo$Ln{R4MI)%P^-c^h*(PgY3Yb^w>RYuhrp02c5 zVXNOqb`J)Xgb!cM5!z_q9$LdUGBiW!VU0qtc9Gu66E)iL67Vq(arL z*4Lc-c0$& zmuZVn>Y92(YxQp8I)Id~(@-R12A*4-hbUiXI^cC;os^e@caUbwQ0 z-b$OURMqSj{YwIp-c{S*Q*|YkyKP(fzkY!Z?h(VVsEEVLKNQRT!-x=HE#AB#NVjh$IKUDr7i&&@sqloR_ z(SNn_pAN^rz8LgBgZ5uB_H%02L1*3li#p@~_$vP)LjKo_9#x8*`O}@fy_@WVov1-u# z-Rx(2Ix4_Da|qPYUX^U089fcou(zPor2)600S`e6aK%I$t``a8sndic{iR*~#Ef_6 zY%g}s{<`@GW{rIp4mFeUsa{K)o4Nb9ded)g`GD5a2e({VYx1(mCEwoB{W7;R18?Q& z;B+gA7&e7U;@Zk<$g=~tTvoRENA?E;eF<@ax<&rm;|CG;nspm3|Dy`v^o9%Igyz^; zEcal?1@PqCPKk8iIRsXDm`*5t_;&RrX7o;ip?=k`pb_RFo+CZzH3iAV2GgtajhCAZ zy5K?aM!{C)KB~%&2zYmK%v^0Ng1U0f%fZR{5N}r&$E!Z_B6DBtnvc^y|GZQNq7<0= zn$rTHX0^51U)jqWBCU*D(ABWp7}nC2O?9jiny~}$J?^HQER7Ha*3FpMxc4Q3WF`J` zQ2wb0kE~sTGNcm)ehBUt+$YDxDaSm*cPYl=K{oSd6b>U#MjUTn%8aDT8gIj zE~=JI-29}KUM+Q{t*37LqkLIL7tQv4MAIe8xP#K797s+t+esczLt@G%XmZRLsjjnG(( z>{T?a*M&>lrz>%%8w|0V_***CoE-MYLWOZ1Eb`iX-ivlt;9}O}S$-bAZk}7e*Yf8# znb)dKmF$u`VvZ(8Tv^h*Biduzgkq~wtF3Cl#LyA-F^Jq==QBj3o*K}x(A}sUz_+Y! zUE|ETc)S$*CxesLcRX&AP!;M}jS=h|N8zsz2oTpADI^tr_#e4+l`f=Iz{j~#bwvx_ z5&@@1hwrZKY__Y2m6LfTt;j<>)Qaa06SnNn-t)MJOeOTQOPC-%_pLp3n(t-hV~#gh z5Cuh!q{ZJ|%t&3%HxPQeNH&V~BC08`QB_nEzT7&%Iw6i3KA?)~$YAmE$(YlSEbg_p z)ge-d6*{z>Vqop~_J%xbayk&d=HgN7McOXyx)4bUgwmc(@^wI=6K*3Ke&uI!z*tZS z$TiXVa8aC#Je!y5bdFEy=-53x&85$?PUtBA9-sJeZ0IDF%j!wa!GP1HcT|sTGq#h5 zvlqe*Q!-1qvkye+Wa(L~o}S|S;r!MvSE`_^85;cx{^dyqr#3cea4k{ZgaD6pXPVBX zjStMpYw9RLr$}rPA1xJ&UmcGF4SXWTmS}XE8tx4`-55rA{{{aK%~cbY1%{ln*t|6} z>~%#&A?7=ht*h%I9mbq1tGj{89ofN>iUMc~(?kKUX0JamdR;coQhx^utF17{e*XUB z3T+9VS5yQ2EN_RQH!?ye-GnrwMOqA0Z^T$kvRzai;qfFom8jn><0K~oz<%}9OWSMF z%Pr|DUenYx3|NXwTb1xm+wi|@O9O{Vjpkt8P{6cuCHQ?>EsoqN_|3#c2V40$Z`ZPj zsN}@fLZl)w40~{cYX1wktHX$(DH`5l_OtD0t-gLqW4y=GEROIvV!@#iiz`}U zAES_jN7zS|Tz$f0A1)4!g>)U;8B-#X*$jy`E>@7uj5imQ{zla(j($u~WK{(u)$3jp8VK3mHrF=y0>M`|P zs9||KGKQGJ<%AAy^en)noc=OEIY9m)hZoCXK24rDu|Smn+xb5F)$&8wE=&&c1D_Tx zD4fpqkFggZvh!T=koT+LIDwzYjDY|So=u5assNvLY-Mdp7*a&ZX}PB;3%kf;-JZ{y=> zBWq#x8qM%<-X3k$v5b!z*ZV=rhn*YMm(cO-X@%G?|2@IoA)wq5eH-o9_zPP0aK3@W z^9f|S8BM;rt0|a&EWqi+hM}r)xhZ~fcre0pMmXpj#aC5e3K`nz-F}1UZ*L51uH8b{ zY`1pYLZHjK<=)ET)#s*ha-NMg%km5Gc!~2LjOhgQh>9gy$3PtpdZyf62r&B}GWbnn zFCJo_IE{;V+;nMU5bvpb3;InPR`xv3zs0(n7x(5*PCi`?)?Vd1)^poN3cZa6rM&cp zX|yn4tE%x>B^;A>vfSg?yL25Ye&8V?RNg%Qn1Iw2e}}DJc85%NZHGG%R1mCd#d*Oq z3NWMQW?ir)9U9zoDqoFB+!}BY{B>1_Zn_BVemiZ}F@O=$FIK3a``)*;IDaKzKOteT z9`FRbmH)yp0-347FfAl=*jD)&&fUJx+P)*`HBA0ztqxn{%t{Fs-jKlt@0(C`yDt;2 zj5tHE6>haE4Au2yl>GiCF;!yOV}^0yEE4R%ohNEF|2%0xmfZ+n6WV1r7&WO{J|t{Z zCM1clE$)r|&12iagJ6m}Q2IuGC!KMtC#+s6p6%KHYAoM(*SgGKYl=s<-Y36}3r<@9 zjd*LrC-7BmAJG$rvJCiz7kut4iM;EqpO0)>e05mx$Yi_=P;oLmmoj!i!6vn<^7{4- zW#H2P@srkIaKGT{`uC_>>qVj$;5OzjB(#N+69jWQJs)uO2@$F2z6nTa(vV)_B3{G_E@(NMtq9As0FGJXy!GBid>n zqx1nE@I?v0deM0rI5tyi^}3);&?>hKH=vpA~( zcbRm2YTIXj|K0armaNtyI;y5jS>E^dC(F!;TSr>`bykmJRBpQ=Vef%9*#;Z;Vcok& zQ_N|<%W9?Vmo61y|7wD34Q{NT&f9}re$^@3MUxr2L?2rhv+`1 zyg(44cSA^bbDH)FG+i{<%Eg7&AxMyqlL z`hg2<=j*=m>^?(gYv+`*8LStuf3^^~1U-fOT|GSpHuX=t`fl#=cCfu9%iGn>?FrW5 zvf$SWUs*rud{b3sH~S}&>h4#XSQA`_*y5%&_S8aCb>6oTa=fi__GLBm3@0h1x>~zg z8K0|OmxHTy8%u{>o&LqmV21vo-3YKa8Knk(S?fa?-G{Z5K;JNHQtnUR5@}a%WMc6h z|7OAS- z;=A~mpkhoT7?wx7-t|lQo~b*`s~_i4{x8>#5Bfr`-dypG)_i*2Z;UI-z04;YrfEpW zS4Y&m*X`zxkKqqCq^LxzO_0@HPq3&?pyRWO(C?XI&)e^GB*Y29p8#;ygr-@}9%y?0 z_pawWnjz^!yn$iEXdzBC)xcQb6k|6-;!mfNTJ>bZO?T((F@MV3hUmckww}ZyX}HR1 zoN2C?=hb4S3VU-|iNie=)PZ8A(-;B6x3)jDDg=ivO9T$y8WE8#6Fhsz z6J5ZfcFVgTxTL8YZ8}e7*td~b<0)ofkv#=}%TD&)moPEP^2ui}jLGu_1F9uwj+@rO zXI}lu>e_68%5|vbZL`+;n#ks_Ap?KhkAdvV6Y*OlkIuoB?i<36*F`hDS0m=PrY#jc z4pw=N#+1>Gs8RdwNc*PDpZXqj&+d|DY~gTHvBU#uC_FXKQSv*-modWbHyI|N(E-WD^l*Ge&=(HetN!Eu)mCv4r0AUx1lo9Plvk@Ecd zg_my)7Xrs!?y5CsKM)iY5!GA6hm7w5CG4xRFm_rjp|^mcQ0j+Rhh{W45LXK{z{&gV zl_TCS+GhH?UhFr9#*-n=hj6!R%DGTcp$* z+Kd(yk4SYG(rr5`YV(6HS}ha2KUC$Y!SBz{G2w+4#p5y%p1@QhXa%=^JG8356J@WA zfwhhww~+yF+-X*~+nfH^THT$zkLONUK>EVdcCgptps&GqCB+kxqcFHk`n!s_@$m-Z z_VDrTa?fY15WUPl7@rB?wf!jPnyQ9kWs$CC4+LM{O54$K_d@oR@l*Jn8Dd*P_L`WD zcjA*H?UO5pinHZJp0Y_k01gr%gkO1_Nh!PTQBk=jTRg8oCwOYs)YeC2Z95?KBwVBS zGkgzlrsYU}FIVmGmj?UY;o1u1Q?Q%i_*CQ5Fu2R(`mtNQxy1m)l^AWXTI+(N=jjf} z*NTz6SR%ZR?{ZK}+_{R4S*i8M=1<*$m=<`21@-wV{&pwQm&pj`TkOJf0)oO94~;L* zLsHlbY4h}->3@^Gp8A3e*gzOE?@uDAYU4zRQPmMYh4ouRX3AKU=+^gs~7EALW;9z;>UA77Rcu z2Dm?L^nEX3ueZSil2ND5C1cQ98Y{QmD0bQ;MaJ4v=P%%#Njl_`o&cb}RZNd9gfU-qY`LX)%? z5<=pwo;9e>D;ksQfgjsf)Qv#3&vwf%p7y9{vRoq~#oIJYT1}(t0lKBe*|?Bp(CoEs6EPn?ESs{s8MR znLQb-%ysqe$y#5%4RC$yy8Eud)!RHTw6l=w`tdoz!v;BPvnrx{gZsc0<3S5K}x9TL6_yi|xSi<2$)_+}sNL z{(2`hHFX~IBm^UFk^wjI?nMvHnE#Oa_B=idYQEhCqOK=_*~Iow`{XKN;1hh+>WYNZ zR@r@g_Vm!T5fbdCH`-d*4^q#pg%%FA(7%D_#v0uo%t!vs@a?R1_{67>TE4e9tc16Z?a{=sO7PG%bi z-X$wQ*=22sGvmfGRY0h;x@>XNVY_BZ6D_7)kw!GZLb`8_$xtiw?8}Mzpu^vMj{}i^ zKa}`*o`CkK&Yf|1yxQ+4W{9c-XOp;*cF{18DWA2`c|oy-<&s^8fHw_d21Lt!eDPJ! zNtTT3acJJgK2v)~w5PS3;haBC?sCB}dJc}x2G#qF+k{RBL1DBgKKqAWn3pu#Q>v~) znmhE{y9ZXi`+2{2y?UN`I?l|x>cJSL3YYJ!{gVdr z#w{j1^o8!`20EWfeD1yu%#gR|PN+C3b@ooMDFehs5_V@|?;WmXdNUvS!@EF%1`JaD z>GR8EuG5al7UPndWfHLm7=K3Ax2tT9gE`dE;iCzaz>)jKPGbo%wgssQ_Fl3VnlxDyxwttb$iAgL56 zy!(CE^L)JnuRU&4@FK05uYXyuXAfok>e`+B zdRgB!B&?h`^>lr;7}j7y7j37B|2muR-a>0q+GY1&%eYFsdMX$eg%Kyj5MFwbZas->$r8mh} z9*H)e!;VWuyUBFbWq!4glAFjl9&gdSHY@vUFaEaq^$z7mQ*d15G$1a9{=gBl1B&}*DM&?&;Bl=R!~PB_k7Z4FQ% z%?IP`z;@yL!NfOO!a~ZQmgcI~uXiBG0xuV9u)A}IKAK{oE6=WL(i+V_ZX2y{$v}&5 z#TI9^!)^Z2Vi9f)J@rE~9K3tHuAc55zN4_7)=WyJ(!Fo@31J1mg($w$KWG=q+sUUqrlE7Dk#qSmahJt)u$XHE zz9L1hc}KpxlQh2fO-5;oZA^!>^Li*Y8&-Q5Ma*S8+nK7J`Dq9l7n& z@#Xo*4*r_iTCs}fPu|sywQ`MBFYb!_F|`Y#qZkIbD+rsRrFEDonuHtc!z1Hd&L0hb zy(u@xi!D0(S_LD+*<7Z-Di5)tYgcXq(7>r|oBcy#P3Lg_$=8|xcY26veR6)^U|@UJ z3;7T#s8EbB`N-U`{{`U*2!~W-2?7uiuzjMT@0eoIS(;fb%4>j*>duNZ&cGCJmKE-O z07Wk-o0q5maPWo|VIY?y*o``lTt-Dl)I6AE& zC!lUTX8-ym#)?>n@Q2>_uT^s`q00NDe0d7G88Q7Ko^+LSi0*OSxVvmgp4v#YM?tH_vbsBBoGS*dO|Wn0g?E>FQghp1q&l+ zc^lzc)o{PXc|zLzT(JSZ1zX<-5^RMf-akE5YyR}nMp?T*jaip)3rm(V9bNF`_v>xB zjnyt4aFsB8fV2@%ns8C371M`_u?GIqSBY8S06APv`ksi5_~P{*oNj+^-h3z0@;r-g zcj89H(nx)lE|fS(5WO56k&Q~K8(Nft(qOL^#jY-{%}FSw$DIZfNGpw_Z(lOMSXtoG z1FndM7ebT$UOSJB#uQZ}?=qCB-OgqV7xGUwYf(B*W{b~OJrqes`NE8zX0TN1#nK3E_6gzcXQD&Q z633#DwEl2fTYaPIUmhMHD4?J&l(2tQ8^!X3qmDLMQop?h=$lE!rqybu&nnxgn6;#K z7%Ge;RFi<@*xYpfT_2@CX_7W(USOVB>invOxQKl2S88Gm&yc`3-Q}k z(13I!HWCoq%y=+>&*2eK>{#d(T~q?~kitH{PY5B`)CGFo4AMt)TtRHPwcbrzL6l{! zIqqP4al|N+v$3?qyl?_7C%G|l&4do>aOmw1oz2ty&Euu(;v`%tUGa^^=P=+lS0i&hkcm z>!b$nvr^w&yAa%CPLSi;)>Mia)%7wccMMF%6pNypS;1l|3ZH=V*#dH!DZkYl1q#2Y zJBOWTZX1&lA3?Uo4t$uUXe#SY(rjuLGa{q0+-tGrDdumRm^P@)?0V@J>`oG^ErEQHV);4b#gZly9O#?4iR4%tfyZL- zt*t4}xS)W=pv57UB=BJqoF~s!f6OdAK6*mNE~m=ecv%Z&>2T(Ab+SE_Z@ey9#A6-- z8T$vqke>PEB5ZbkIyw(zUCSKQ$Y?pEW;>aP=*#$!3s~}0w*0JXE%nr{BHe9w3@`z| zOf%c=s%Zpz@5jl<s{Ir6rN;H&|IS**JCl194D9hplvlek9N83da@(lKD z_cST>vK`Zcc)_gJo*2rlbp?VpbQgF_(#~E=vqD|~f`(IOXeh6tIy5>_8deEvs$ha* zCnMGwbjwmcjt?K1DJPC5CBLCYQA-FnhRmP{XHN2OX6Pio4qU|e({Z0|?qJjQ(}Rz4 z`YkC~VMGfyVtO}!a|@_<=(L(42~Wr8V@TvDRVIjI0t_<=D`;yHJ7 zy)T0W)=yWfLd*Pl=OxY-uK$Lt@&6W9zQq98R=&Bh@JmZeK!Z26?avr}4??me#wOIV z!fXJJEn!UqPswiq!=wZdq*L>YcuNk-z_PyIkQqkRIG8vCu4}36jau5}>}g>kUNLRw z3S}sZCHD>Atut8>)rMPQjcY|~XsmysSSdh4rYmsxaPpG(1-euL@0l_|CaPVd~+ ziymsaqkzN;WA39QziOZ{Z0|%IQJbQouvF9Ih$Bj?7o||=7w+wQEB?LeH8w69( z`P)t&>x?x3CXRO%h4bzVGP5%pQCMzcDmAejU!Bswk%4c26X>3mKrtH#Bd zM-|H5mE>_vpkZ>J<@+rKM;kvUXVO!3Y{YH)rLW^&Wz5=HK$aVOB@bJx5vf0x6!mN3 z(A0`3Cf%C~UGimLz``aMLFsUoahT$f7L>o-%4S(qt>c<`%BwGJUOV!T{ROa`=6Skx z0jL)(rFc=Jnj2{uW+7ZaMi%JWZh@!BYGp@jTM{r9I#NU3qRHxLw62G>9@>*$&!;L; z+ZGsQ6ed<4@65$VYMQ7+-P+PW%=sw*!Y`ww9ySkl5!=k3K*HkF$VMZogt83vgk(QL z)_l~~rkxB%yLWH%lf~4B0a!yJ=gH*Vh&rsfriSjxSC!9oHR6ORW$yZ3NoxB;{d*>r z3TM$PdNu?{U$Hz$>}g{{{&H2QJYB4&W8rmAX4~k>!%=^x(O*?ct@bql1X{M=w8T3 zm576VcDeye`p(qWLND8&;n+(Nj>Jyi=S|e4Q&WQG*AYe&Vos?sNl(#2)x-?FPyb*7MXiXV_u;=YW889qwteU2o+%`r8L)z3;i+9V?UEXzfKNg zE?HCZ{`N4h{8j<+<#WImRcQo4A(*8gxhrGnL&?V`dzpo=v|CaXF>WA)X~SQ%0vQ@2 zkPY5xZsU;z(itp% zmmLN|=SP2m6w(>${8iY0-WM4RP=y&kK0p@H@dYSO%;iSw+p)V50UDqr;s*P&T%oC) zsLi)=WV|iJC|N~)*scjrptSe4s4m2OezGKG+Wv06;$!QUPY@Ab2ETrY+aBD%o)?$yQM3 zi@>4K0NkAHTu7Me)k+}_xkQI6`cs*y*WD9B@y#A)K4K-6c%u$FTV5M6ZX~wly>`|D zd*VSG{Dn;*Kxcxu-mco-f{5g0{#}rC12JIBeD2Hl{kr-J1@Ll||0`D!AXWQ&*jrVqZi=m8mK|_T2)SJ%a-y)&k8Xz$3Vq;M0 zA5G14#RgQYqYP1;{E_mLD?=l#olpJ(PafV%kGVu5R++~tzINV&Bt_lwBHx|)A4qmq zED;04dJ|C(>FAj}Y0IpUPuN|sVCxUU%BFpvYq^{gZ_cPbJLD5Bcx_DC0C*lq1}7)B z4uwPtk%notRWa&fGj?7E#QtQSiMZ-4dpd2|)ANV&tv}`&*AO04!ZqCcq%@=1&|%a$ zJgBF%*M0_hZv}h9x|+4K-bDNYIeq?>czIYrGyECy-=L)qG3XA9wIAGsm=^NXAzFr% z$Xkt!j`+&9wJ{gH#TiXr3~XG4dO3>Xip_=KsU6I4F1e{?CkQo7l~I?1j3w5&f`SG- zuY3s5XYwhj#9d)V<@#jC7vcO9SC3DF*s5%OPdTpoOI&Gn*udnPqW-kjXbU4cEbbe z;k+oqzW6Q{KiMqeBq0EW4liWGQLni-lL+<7I=Yx34okINjL?#N>mjh)s5d;9KaBxp z3ToXgc%T2mlDjSoO{DP==Dx`c0;|c`;tWq0-upu*q@L=xjKY#!u69^=@#k=$03kd3 z6u|3gTr|sgk-7iyuDK_8nDR6LJsp6YUKd#SS(5< z8L=~t6;!SJ?GYN1#mTha=lvah=32FXt=yY!_)iNy&9o}}n*CD?K>R*)%t2EqAA5gv zHdpsk6Jth%*!QKpVBdnwDcC9%oq_ufsJLiDmy|IPsZ~$tf!!oRvg@&EzNel0WX-7Q z*cau)E7EIpvirjn@ZaMVmw^D(PB4pz;Pe`YmVn3G3O!r@w8}z8P@||klW`FD0Aauo zi_H6u{>t*uZB-F zCq7@|NRfY$#nWOxE1B1ox7BwBu!xB8T6EajtuX~Rb|TqiZsfF86gewVz?j2`J%kFN zv`BFHgxuk2Coq?7g@?YyZVD^Jd*SI9*aiJe!n#{Dg<*S2 zB|jYD81VTMPPSwolu&9wJ$o!mu!xz3%Aw@&r>%>mmHXVXfY#mpKCf2+dg9dn%TfX? z4ypyQO(;3O)xs|tVUvJXn2;|8B6zX-8!>2aj_76+HdXBIc~JDoMQuheR~N}6l-`p5 zbKhdaQ49VkqoTLSCjKDRTm=Ze)Q~ev3PvE#?OJkoCz>fG-w14z%NO>1VM%YHy=z;8 zrCh3UH4Xj2zf9gp3+N=MVr~dOcZ%oJ)D$J=YGjtnoGi$XUQ6knJuz;Du$}_-d)rem zl?4ArW2eNs*WgT1)$2t(Ytyx28u}T#&-4Wl7(ndwWf62Hd!Bu+%V)rN#QjjK^-*ui zb$*fA`d|uZ#hEE0RS;41&Ti6A?7tCpjoo!{Yp`u>8x0!Uwr$&1W7}?Q+qRPvot)T7 zW7|${KizlDZ`jYCHETdSo@s1+5p--%Pe)G!Sce29YQc0l{i;DdjIxJzWDbsmY04Gp zD(c%b&W_lM?JENakA6cXW6OJ!Kl#3&H(IDxY*@!JFq7DX2XAv7@<*seU2K{h z7Hg1%BzDj`1nK3VeD_!U2Yi%mz#s2J!%MUnwh_&*zZzp!jQs(g#fDd?lw4d{ptLXR zxBy4SOzo~h27+*7c~np2a zbZKwV0fz3D1RD@>-V7vucO=z!O{3rD%<*heBqLvNs^ZO5evjnc_oLvWafRV(72U1y z*K_BFpy{L$AJhi{@n7K2trWO5E#_Vc`{QaP@^|pLK5lTJUEK3Q75JR4{x-1#5l?SH zw-2V^|KaluWrhJ@#kgDaS*jN&u8lUAn5lC@V7lC!!py#`Dj1lYK^TN!YfXWn}#cE>T8W!UZP8>%~>*K&x zt9mkH@yse4ff%++Tq28-xCl5@cWfrRz_faJtknue3{=SHIiWaFD4kaxr!8RNL~r`p z0{lCFvarF+WSs_}bR{6IqB&L6Y(aoR>>>%spuAi_p`PrDP~~RY^4nu8p7O`J8+{^u zsAkfA=iYT#2-Nx9WEd+dm`$H8GqFGrH((&OEp4#cdhxl(ekCWoSeKu}IVc)GV26gb zrl;7Fl=bu^Xa4(!-qq)e*5_AqFuW#?VLjGg3pd(mp))dT)1C{ zX+xn9<8@az_P*S-g4y)_?#V7cKAQ7RSHwOw=Lh%Rq9^H&OcaMwH9jAs@#Ex-=10i> zIJ?`ey^HyKTn%j>%4Ve4%EP1fJVJvOF;gTfC7m6b7T!D16)2Qz?*T97-``;(O?_(6n_Y6N*Vjm7Q;mF7)RPGl)gf$fTjJVt=-uxVCeWtTP3f+{;qcP(0C?a-1)nV-`e*yEmGvu3)%rq1?jiwH+CZv=4BeierZ~sm&hERj34~IZ~y_? zAqZ|>1X)@}Lt*!ri-RaI0fwQ!q({hq-O|Thq!f4w!Dm(d&AKoFu_vwxqB_ z9d)TIzN%(Zx5GzdwlDEQ7!4G#86`P$yw5JG`UX$X(yo^|4htlbIpAYz7_VQ-3{mp? z>&mdNhlp23hM>?&1)?gSA2e@1+|oH;cO8DpizbxqwMhK=YWIgu|FcMGz3(AQ*Aoz* zb^$o5NUwpQ7|bs|au?tMmO*wKrxNu`RgV>@->~@F+G1817J*N#gN`%+4K- z@q405$p4g3Cqs}66Q*dW5XPn4Hy5SGMY1MC%(}&Xb&33gucsE8hTP@O(yP^k#H7Pf zU~#k*f#_o9`;SZX5fFNhBS~}9BZLYCbF(+l@DGDl(7{~@CMf49DV^yJ-x_$Ww^{qy zh)@H2^@|;f)JwZ*>}R(_?d3d;aE(uZltdS)B{ihoKZdqtL`2nncu#(vC8qhI5+@t=!CjOjIUD8XOl*v1dUhHjR3$=J0Oi6k z%uD0HKd>g-Ql>66A;3M9ffy@>_9M=1E>!053!RnT#R-~d<+_>PGH{L;iBS<3kZVdW z_dgM28q5`V-D*A|E`I!&?Q2qK!ijU`ysr(n6EBV zmP+yN9XGOI#J96&NuxAZ_G@_ea?_W&OKzxNEBXk_N_EjLigIRgKdoiQkzN zN@!SKkpd0x)4OxIsR0^D4q_E{*S&gZng?eCWepY$(nVZkoQ!+u*u%s;G1#4mWa!5) zs5n`ZtwvC$K8mjplLF|~3s)!B1@jMiR;zMsce4k+)UnWL$qz*KR7N$hUSvlG zUCRS2kh)If)QP)`J1ifJWu~T`zoQpAcC|X0llEm7Z=WNbwDJNw=7NBX>wP3wZXu(| zfk)PtH^SzR_IRL)bisXd5njFhmsPqL0$`y6O)1M2vWw-ecq*?ZT7aa3T`d7sTmQ`< z{@Jze1RDiZ+pOY!^Kfe*Nn;&Q&_p;?pSxp9F&7`MZO*WqTDq5(f~QpLJp4SGCzOu& z;7c~}dRFWHP{JzyD()C_FKUJ`D)}e^L3Os*PWa*Zd>Z+@g*H6{|2Jn@5^t6rjQc|(x&+bp@p(Lm-7bKIZ$BpQhsQ>cR(33OSfPn@^*@ zJbPgo`tAsB>2pSq;HFuc7PDPgjG63$goeV6zT!4x^pDI{IgwxbEUM(wzh1YDO#hXM zB0H?8IE$duJ(O*9_ar*KQX(Fe?V__7^1QJ`FxW*T2?SOKm(dBFd3y2@xB#|6K@KAN|>Tp;ph=Gy*g;Rx2J>3 zpn1Pu=ZigD(^$!AqVDRAUfNcCSij?`(-s8on%!H=UZ6 z7Gh;;j5z1wuuk+ou&tW)sA^^&cx)Cd7K%qh$QrVy@yha{RmG#_BOV+4Wl!IqMvQ2P;jYs6)SMR zIdeC|A48*{8!g4@F*yUUT@an-WK*QK3veDT5|w*CftmbnUE&jscQRU;feHe;K{2=7@VV?5o0~BWW)H&eUcU&;i(&SDMP3^Bd<`0td1;<9#U}U;5C`` z%qF-){gv+9qNpPko~^40DYdD+=<^6C@> zPscwb%bb?8LFjC3SS_93VhBxRd%{sympN0VpGb7zjyRyOkf4>d*;qcLndNsrMs$=) z-1_fVleTR&L)75<1kcIHP9CrgNBe_Nob?Cy123!?{Un1>9m>I+iN6mCh8FGe(k8GvQ?Pr3 zhf;={@mi6Mu|1-jrCoN`t*TRTVxV8bqb^TR(UMrx7fPhp)D!Km09&R;lsAs49>P%m zxLmoTkv@ZGbO-Ua+c50;e|EyF6_YO}l8yh{nD#aQglK{k9gO2{i+$=_oS(efiU=SM zj(n5#hsh1DoUJtRZr4mgeWX?iF6!YsXA3nQdHF7Nf>Z5)VcU*~{$NVwSAD^WP>HjU z8QTIcDo0-}qPV~ae}x&aY0w>WA=P4P#1t0g`nUHJBVWB27<)E1lKYyeIO0jjgmc&R;b6-j0&#FeR?ap+R~^f707%BU!98j6N;uyM z;;W0EB#)Mq=>NDr5)~}u+IK50tt4DsJWUq>br2XxYAP)>$m5TQyA0b= z$?F%`dV>}TIyW>joQTO+T%ii_OGd%h>zS=lFc4)4ngNwcB~2%EMwk~zS+CbXodL12 zoF^@}+d-?qP(P(d2%npg-{=}=Gj&(s@X^f!%l=>BSk}$QTyBtrIgH;*5h|Xa1q?{S zlbZ+AiPNf}k5~TEFkdf%V9V)vnZ6tt3?-ML|IH~4%#Oo+79^%~`4hg1w&xR_z=IMe zl*OjI$3)%WtV478yb7Jqh%`B(b2$tltBbNI5L9{>IE+8)k(xtvIpbqfM z(%0OaKL^&Ag8*&O{-VJ(NPwyI;DTA91-$jR0|EO&QWfmz1MM!VAFRiix>BR1zTsrr zb$%vL1EBydln+`ACd|sJjGOGpg&*dvh*@!5#dBylV{vhDjuw2b1gwM#eEhhFmDIm% zHzEqXP~hio?J>mC8ZNu0ktI4O%7xIFDmBpK>|Qf77(B0Geg;Cs%ZI=vuiS0+)l+nY zcp1~#^c*Y__G%dSG8dIrk?}H8Sah?ivrf=)8EKRQ*^TjwftEe5B-F`+4Q5~z zPmYi?iXzo29M*DL95^?YV{8S9RJ{2Z-RVjTf|-ZZ-0HRQ;}u4L!?+YUr{Z#W{TxUm z>h-bEoGrt*0#?qqhmjxFTi`l4yVs3RR_9}4u~A+#zY~#>17%u4gsi=|4SpLanD~RP zfsuc$s0f7kG@+30PcJ~+)Z$tQV1g^29+|_CI(629`br2#IjJ8)B73!|^7Ig-fB7;X zJc@RL7n&D5-`+pWX0$sEFYoqt>{LTBqSZ+?L{95&SQzacvC#K*2Tl)c5=ZkPVtFN7 zEn6^I_r+W|Ain{bapVwM3am|3-i}i%uj9~c>3FFsm zeaAxi*RT?q#6M~tn;R>;*0*qRF6v7QDXw<`JT1#Xq+Nz2EWa%79Yj0>rQbV0z)!V= zjQNZ&jw1t|w+67Tx+ArU!ipT?FOUEBaF|bdLmJS2P`vAT;^T0Tq+d{QJ?rGlEJeP< zSAV|CC}&a6nkfpzoZtzsy~jIo=5&I5$+sIMMp=ktLdlBf6@WEC()yCEg7qg51Zn43 zk8$#jJKQsq>pw5NV*zk*gd!0&WT^5b+wUXYn9j+L+Ui*5#y6vN#zW)G9w7*WJO=#H z>Yws*&U*6*#eFU=m8w|9h8R!!WCzkQ*(?|r-Lg)mi^k_2e`?G*|RMtPI_nl2rolJ z1MYr`sBu{{*&$J7xb*Vyb{5w8JA1Va^UR+Mngdc4lsS(?2IV(UNh-#8Zy&C8mnRB+ zavOgo5P<-NTA*BUbqjMaq(dZ_k(1dyuj$;3s_v(E6Ca-|@x(6V=jM<%9)#UE=LYJ1kkP&$N|cu~@fxkk#*|en zZQjT||9;Wmckcan$Rz|p?e8Dh6B_?<|L1uU!-9GtZ#*9SN(G7W0~SgZw?MuV3sLs; zRFo_=t~95PX2A&Af$1sr-@>Or+GEN)F_8_cYyq$DY@}-gL6Cl*(-8)A4@0+!NvYDL zB~;z`)rL#5NP?qcW@=)@OSA?NLAV5+G5+u(UXa9BlC;dn-=dD^z`(lPh}fd}P|@Ux ztB4Z0)m;6x-yNQfHxg3OF^k&UV=_fR*sR7sAmO%P&O}68m(@}XCzn)JS;7a{(ih}d z&vE}i-t1@9*sF%gU@?%G_>=t3DA}E=j^LSvvT`aCIDs=dTAH&g zE|^{V6GpC}pnUV2l!k;;48<`^MWrY>9ERPBMo~+Lm;f%=YuJ7>%$W=W+J*<2U}#ga zX~}dL&BMFdbjBo`tXuU|#Q z5&?3G{k847`vpX~H6KZ}wV2UCIQwq$H%K@nn> zrot=cA}8S~4pX&0hh#)qCCg5i<+NDkVvZ2!%$JI#J8V4^!_G?1&QkT6?;jJ+{K||^ zdRRP=jO2+J(b+XL+rpVIjVTJN$IWsl`yTZT-rE$LyVY4p#EJS(ohd0-4>Bo_2S?w? z&<95oY)nngOc)LjCdNyHNhF7D_6HgpjV#r1a)z4KuosJQSZPNO1r@NSV1jP6qn*{| z+8#lMSwJ#$nQFWz=KY+8u?@Z$O?GzIi^Qgm_K0`sy%7RyvGkbdMM_lSwSblm@@7@d zlFu8O9H9UV-iWi??`Lq^t)g~GSXQAGyuH&v_zlD|DTdhUN~0SX>%Tw0(ZHK?Em62e zOQ|B+#fOPA03O-6mZhmttO}&h$eR@w^G-Xbv@|pxmXUIZoUxssCSA;9I-sDvn ztu`DrZKCS$bYZH43zrzE zLAoJB=Kx1U7=yocsj?_K%k&ohw4uITH|e~U3^8zfCziDVDw5u^xHAYKKntZz%xLLd zhYNsDSE$(iFXHMECnRPunej-t!M3W`civa|0TpL*MiIRkK-)R(L-8GkuDJQ3D`%%a zDF|?Z!ravTk0>h#DL)!D0X1n2{)8r6ilAU`kMsm;%AU%I-C(@GPHAu^s0Nf1l3w4p zgJx7s3-jwkfHMWye1VyzYs~42M{myzo|L7w;9Z=f#{inKzAN;=(?2vTeZCCzRs`3_ z&!6=TOJ|(XcVgzbsoo8^OKA<7O^yOY*x<#3Pc9qI2Ar(~(M#u{6BM{C=F>raMNI6) z<{~mU@%A5K`3^3>d|V-(_oH1UdxW8yu=@^vPeU{ABO~ZqM0~Wvu`TG;K=ia2>dYD_ zQ9ODlLj1(#@7Ir?9sQa{ndnOy;usHCCwLXa69zIZ-FdB<4 z6Ccm^Kw+!N`mS-Bk{O70G#>Z~`%}DZb^Y6(VOiYN7w6X(7yLiAry~M7IuN)YY$UkL zy?&MD77e5*8MsC8X&8GP`Jjb2UrNBc>9^IL0UZK|_krIPXqOOor65 zP-CsAG@N@>JaRd7Bg5*qB2g4uC)XsYLQpvo?c_75EDnA&P%tIQE1ttdA)Z~C3EpjJq+VJZrpQWe?5oxa78AG-X6LAr7Pqr557aEH7Q(40lF#8B0h61N zE`(cTm~nmAm76Gh<`XbF2$agb%W6ShT342vtI%I&V!%kuCr)LoIeK{F~j;5Us%Hx%xfQr3}3BgKUC5hIR@+zbON-8eRVcN(}G4ipL zhV+cVPxg2uHy8F5F3k`+>-*r>_-3z?o+*kG@DEv=H|uE649JCNr|4@m1vYyvOE?&O z$s%ppu$yyZ4(WIgtPbG6cv3j-D6?1ok|+sdwPEc|!!+J%_?5hv_&Uyx+L{^^HV5yQ zOAJj?VrsV=U;Hp3qDGgK6rj!PDL6RAZ7k#Y0ywXjJq z!pas2wZpkO$s$8W0whLSc@iewQ z@Eho>X6o=Xp^Up}#yG=&$|wJ}cMfIA0ivqarO-fvclD>^2sp7`9x_LZjWa19XR9~l zqH)&gk$hwZJ0pR2!oAx1qPVJ4l;?5O4edC4)<2z(cGL*GwFG7MvjK+}nR({3ah={9 zS^|4aXE8ovk0uwFpo{LiQdi@>{q=)eWR@KD(*D)Vr$qP_k;fKjPLGV>3$g`642tkc zLQF9Ixs!LCzAOgL6MwEy{W1WhoXSpdt1PMR0ULVSWuY<#2fiyQ{Vf(+;8_^f)R_KO zwQthZaH$B74iduVxBn3|ys@0W&r5y(EU7o?XI?9r_T@UwzAqBhDm`-U(&b{fU3J4P2073QV_Ip)-k`j-J;_X|Sb^F^$? zzm@A>r=IF5*x*VBdaj>F#Ysavtg!`EG$<$yAPyKpH8iKy z-EyIi=Lkr};f30cEt}&aL`{pIBZbrB!Im-4nKt$y5;A$lViyWDa;RUK8exy(E{9*! zP3lW~&#xQykhekKzCE(BooNqqX0k*`#3Vh!!_Tls+^A_!jkUNgLo;&L)?zoTvoq0n zk|FAkiE+6ajr`TFKB1YEaXK9nQmx1H^5X6XDT5y$JCKz7$VgtjA3Uum=$HgCnNSot zmvdN|BXIp(y5odgWI9$o257G`*NbKXINc`U61r+jFexMvO|9wX)l;UnD`-sp$}!mK zlq__E%dKtF39^E-V;~z98}ibXuzDK29b>9!X@+Yt^0sFVl);~{gRMl?o!(gjI~zqa z>ts3OGZGg^f)H(GbS7}E=JC)1n-0~!|w;t zq~Di$CuV6y+-i#-SeO@bvS8I)56P_$HD>ReuJ(f$Y<#my9^pjo3dSvPOJ}j7eBZ!m zcXUDXdv!vI)2k4RR~oY9yKkre0#kIr65XSWYQx6AR~!EUvGhT)lB z?(Y*3Xnwd$V3+;jjKHD(0t0ok_|%oZK*jiVqXq5ax-XpDitKK%GE=T5Wua~F2Z6pO zyKgYIs}<)W3TI-pCvBj6Ld?*Nj?E(6D?sojKp-=6iUGH}_H(p9&d9NmRb1(xbY7zu z=(^egB!U&IS6~EoR4{*IE8%_?!EpYe)AsdE$DCV$xst+XWqMAsq&pjN>bY6?`O(zi z&aUuHndOey=>ti#nF=v+^XGt#0(@{VL`$J%apMzW=$J%j_Jrgd`*%xD*UU~8ms)Ja ztzbOvj#3k3do?Y35&3ZocUxi&+iv+e$RR)zC8NaVSq@X1vYGa$VWS&LYHVXc)I2 zQ>&u*@ztD*K4}h==p`)lJr^DoIhEJB8IKV<&>Ph$-6k9Z#GlSpBG7%|!rAPC!v5I} zN1cNPDSL&4#nV$-(q0tS3`*Pc8;ByyGQ?bjQ|M!d`+SV)=9uXZ^BN4~70>Ye9x7I2 zr09-#W6hSWc_dyqG_godw15S|DTUPl58);`xrg!Tb%cR0l@ltXWLRLyI_kfidJgi6 z2t)il{>v|FjLT>|92RQ;1#f>4J9QMdGe6xo2sh0jNTVTRnioGpu~j31SYgcLdKSv= z_vqqw>Ol6YEwyA-t=k4h6{?1TiY{6Cr5{8Ca^k28jP&vy3x9IM3J*KSUX4w z;ep!HqPX0iEQU(hw*37!_j-UKKLD`+vBrw;@nR){q3;bw%@sE0S4(4jG~W^VtiZ^- zIGEof4DG8AgezSv(`N-&#O-gQvv8_YR{P`rJy04Z(`4hfz*1amH0nGcuJ5yN*X4NEXc0!4^GMAF1y)z?9O~ zTGTUj-|!LmsLDz;6L>A{YBmf`5df|)3YVm{b&r_Sg%y$t5=W<=RT}1=|O$n ztT;loa_f}dqT+A?TUo~j$I;3P zSlUugPbE^+S2dm-yZ)*y)=k;nMvOP2zrsgpFe(@RJ zE{+n8HPp-V4$Bi4ag6yrV;i$b1U(l{Bqm7wUjXoU`K&N`A$!Kf1t&cZc$#H;VcHWt zy`V6Xg>hklS|^5blNrG5$NKeDoT_H0d&*Loe)gGY4ktv2Ba%ndABYin$^u)VOEO*K znc}6Sz)Izut;>r2=}YjyFEp9FR3$eO*ipe^iRh6z{}F1@Q3NwSj!)MZMiO5bjs)`3 zQsvAKx;zEZf}3Y;@u~<~Fyd+(01ycU#zST(&~pDBrCAhm!!S~8W2(1zf5(E=jju>l zOgxgwm5-KIQtX$J8-*Ix&pRSwqP37QRAZJu?8vdI##4R4jHWOaP^hj7gj5qA^8^p9Gj~QBt!CHxhzDNPp zI<4UVrf54UQEwRPp{!UfImzEcLpIS)`uLQXrm^?oa-(DZeAv(tFBu9Gd6-2IIrQZ0 zxgM7#9PN=G@T13Luj?{Ye;UerTeAT^G%kefIMdS?00f5h=;uabBg?CJh&rIe#kv8> z4v@V2M!MQGpt7o3Ff@{k`{5*hR9swU)od4C$S%X52~yx{4HBc`ll+fgkzIY6=?Yd} z7r4vtcCG%i{TpHTyX+5+J)m;c_g96y5JOz{pSi}+U#%b4@74I-J(i?w@czRF3iiPz z9VnHPA%{6sg|9IKXvH0O+vHNz)goIFap^qXMEshV!R>Ye#4p!wl$L_kCXlvja(ukP z>n5i~@kGE_FGu5EqEIk>~qc z3WZ^T$wKSeHv#}iC_M=$XBJ5z_Wh8iKiNt$ut$n7Owq zJyJtW2a1J9BPN68r{4Or{zY(@<)S-kE;aoC7R{5<@~eZR?G?3VI&NZXeKM|RfW*FG z_w9!5KYq*KuLi+F^+&-TprPY7#9z(_ki!Z7Mj>OhNz}--`D4w)qV{6%6+@WlUisGAZ(#O_iVN0F%!`D5Vle>Z9 z&T+8RU9JiFJ`JbW4lBRsS)C@}ll-))uo04*!d=$q976U)PH(a*t<~uU5CWmF>vFg* zIT<%J>OeN{X$!K|BBSP7)m(=%+)vKHo*hWD`Te1_$jWc`=|qK?Ll^8WXu-=Ldf!`| zi|T4I{JG?NUbK?ajBO*}59i&-Awl-wFL%yuVp>P^ubV8-cIXPR{cE%+G^ZcL13OQl zAh^o0wRgPK!s%-W)#Sygu&B}OBvdHEh<%^?8|6~^FJpJi z-$~J$yAimo&nvi0`J@V1fC3rDx zb(aSq)q5}DM#FKlD}*vK&vVxF+=XYa(+vU3T6U}{6XQD5n#%7a1fwv1Pq3`0lX`>c z9Hu@={x{c%@uvzNdBGKx9PyaQ3KSK^!tOjA@luq~ZjAJKSC>}5kzGv#U;O)+5oc3` zU?m<;;t11Vo7i`ND|Bz>+QLA*_R;s8+ZA$>-wzItvu<`&Mgp2;Ap`d0_$1UKFH^2l zA1V?dKb84ggNx$gT<))Pg>}DYOz#Y;)%GPc99=+jU|5_7gn!m7MjT|ZG6V1*Rq09? zCd$$`IV|u$8ncm?qQMfl&tAdnqu+>d*Y^*E|ACvFrZLzus>^&aBh8SO3O!Sxqm>=X z*EL;`Y4WF%9?M(ZH?l~#i;atwlGJD~f;hf6Kr48iWu-9kvE^XoS-L9k1^4MJHV+Q- zyQ>`iQtu1QT=^64^DG5pfqQ=o(_D~EAz|;QByStmPiF`EY*trkHZLTC2t$T4n~Q=Q zuiL9sXy}CpznIjkEKTnGKv}h|M##%sj0qp60A2C;F;lhcTLNutPpJ z43IjBbIwgwaoUs=cTkRvU+CHle!Kt+7Qx_VU5!W&X{S-Z6y`gATkh%!lt&P3$pd0K ze+xr5L1qx#<*n7hP!_S_y+W#a-PKiIwGZ9=978sb%tYEeC5eA)P7eW4D)8Y;{g;Rz z-)+;J6Z7G>R=f4MV5SC6PihpdQ1l26#?T-Tu?iyWD+CtS!Zb4H*A_3T^#an z{@enIqQ#ff>mB^$nZBsAH!zM`MtRrCNr<%2FVng^`2B4_RFGo!Hs#Q4NAsMim=U#T zZ3Xl4aq3@8tX%a^FD2;#E#l>7=mpQ?Hf4OQfG=U_!U-eEW-=MUAwc<;u}W1r8msgw zmec;WUu%)xia#SH#Y%0{YOQObUJ|PCKWi+hFQM%HepxRIcnFw|h=|TW3Vb%F5Au53 zSCj~o1l4(NK=nH_b|ZooqgmyMCeW=6Bf7fL_}9oNG4Tz4lyytJ`!6z>b-6tcj}Nqk zMO7k}1I{>nfyr_vM(zb3Gv%qr;L1xEC{tW;4TIDPTC)Npazn%0LmEz|ZAibU$Hl!L3YD!T2r*WLCD4 z@)G6FlpNAd_^c356Ys$=yeGikr_N1J-n5ylm%2tDbn|Vi2{PtGmSqU)eMc)|Ei@ut z2*uH~AdLVP2qQMj08*5RXEHdhda1Xz+&uMH&1p3f%N&rqf4z+c>*j8jbi#mn;S8+Z zvO8bN=Bk3``ji**j{H89;z6>6Yv1~#6XM@~nTD8eGWT5JO1@lOLHgvmf(8#wh5pr% zyI*}>3DO!%TeFy@sl`eb6J?~=A9BDsT99>`YM;K69@E(kgo~wU10P3=cY)3+26iOT zY3@P-2$1lda&XLa>hb4d15a+Q0G{3Z<}Z=_1(ozoWdB!u>14stf_P)mHpi9O7n0bW zLB+8ffw5_{5wen*>z3nq5p(|F8$Qx@CsK%$V1#>Le=jD6)M5)}FiEhlYawhNZrmkS z&Q0C`**cc9^-3P$o^z=VjY+-`M}Fp+#4}F|27!BAj%eS=j6-_2Wx(V_A+4^Ybnz!u zj$J%R82rtCubuL>Fo&gy(@K_>_@Nr*WQ*6g)97=u33>b+nu_M2*Z&OaD@goY$2`>y zx@f5!8gVh9b8a}f*$vj_G#t_h7z2ULL5VBBk;YaVUGhzc+lYHc(gxU%Ko;9?tlQGu z7?xFrL=N(UH3@SGKpSlX*p9#ATGJ2;K#S-w$fmO(}!BrmqFLoO9;|_p>jE;0wV21)6eTyrxxifZ@LCz4z=G zXUG5G9s^>yT_Z#S_Tr}l@~Ub+IXfydiSzTSe6(G~^TRQyJjM+`4*`BL&4CVzl;l&s zsb`6;jzqqVe=DY^GX`+y8VGnRq_l;~!eN$E4vPWKZV#e$C`nB2AWe@h&3U0sOSJid zQeLbwM6$uPE-3S9f?#B%Mjl2dgjH#=E5kGR*azary*zOQPTdA$2I)b z^MRkc0(i@b?9h>x2s&txaFs59NJW4uORaI1>&`@n0#82(BH*o=N$JQW7$ZwL%FNM``swG&`-sp+Jv_Tci?laW8&tCG4tRriO; zObY=SWV-^I*e?i8&DA2`Gxhoknvg&28l<%*vK{WuhwmIN8}PhbCfIg8OUthb;%aed zSx807JHaQ;r9~!;Y|5=R<5onl``V4KKFgzb=Owwi_~z@pnd_VoF{VR@9wA z1Y*=D-~M?}qo}~v2Qs}60iDRry@5G{1(jokbgDCNodr+zR_UzmXu&uS14B=b-M3n? z(AbQ(;?l3yK6`$jn2A4IMsS{wH`}i7dVf8)oQ3uEeF_}o@45;&IBP7UB929!n|8nb z9gX122vNR$!CP4o9F2f(Fmi=C4>&ULfduOkb!wocOX1pCE@GvD`RRKL^9UfHT@kIM zv4ra;kntq9pJLEoy+q}wg9v~)8fb|=b{qdN^?z?_Ef97C=9DxY8*5^^6h~RJY zCWYdWtA9e3fXUva31efyfa5^l&EIBw4Gy5!k<$Cpv}9j9Qo~WB5oPwVuFq+COA%YE zV3&Q-^@g+aEdNY}RpJ1G9^iy;fmbuG1`4f2uEh@w)KA+=`A-t@*gg4I+^6y{-tq_r zRq)>L9udH5ZbrV_Ro6H8`f&pcnj2ihEw3oxhx|gkU`8hW7hhTJ(UGzfi_y~Nu1PND zS-+o$rTui`ft8t<%?!(I)Az;v-WJ7pQx#Y zv{BBy;r^lr5(vY!$QLDHYl-$3>t4ctCm1dFI$c^&7Kp+a$X7g_ei!G9@GPw@#iWkx z5jBm_Iw6RCVw8;PM6(mB9ue)2??{&OwwV6BCLHO=MLfb?5(+lF-{v(F``}5kK}^gd zRnbt3v_6H3ZV9XCz?imz{n7D))8?nK0uLS>o0?$?3;DL`r{DA7N?58Onh-GduI$W5 zv=ng&kB*FU6ze^Q*I>w(q*tEJj*<8Rf$M8$ThUehB=Ohd*`b^Can1Go3?b7Q?D*R^ z!EaKcLaHQ+ajxX7YzMdN(B&CsS|VOukZbUwj>$nL#j6*MFOklC%ZV-0DWM&yDvsI?FzEH{T_<-^L?@bf#mT`i2Omi_N`#qP$nFcDKI`ILTXE1bc+>cTNqUNA{ z4l4yehHnl2aOBivVCrK15q|^S7_k0KcM$y0uD!A0iu=t26vmWSk+~cl>AWEXC@p1# zwfYUDmYWE6s6OGN9T?rXu{j>)eH3*4g^c30D9I6#HZB#_@W@Eysj#@4VINbOYkK`E zA3km5wbg2e4xcs^M45%UVpQgf+Pz58_|xzjJl$}l{G(T^5o{nv62^(tg6U^t;WMl{ zt#$27X%oiw*5MUNoe#fUMW4nAbtqpr}gduy5A&iL|+!y=I*2ccb)m zfnDmJi5!hupM8I%AI3uZGKqJ4S?YUP7+DEag|H_vtmr5^F3KF3LRBhBSpiRJm*RdI zus!6!q3&_gZA+_w!5Eg}npgA;i^sjZEwAm-wz2{7^K)FvL%lyBx2``IugtbOPeaiP zAW;ed3e}`c;}M;!Jr6e${*QC-=9fn{52ivMnf+f1L*!R=E|GsSf67*{-*Y z6Ci0SP#^0kN5QoMj&Q?=p)bQ+Nm*U1f0SE$TgE+}u9U;>qAATTQ1P`{%Ra1Ov1RVN z!c3xSKv4ZZ#?C27lPKEKUAB$CY}@Fv-PL8Y%eKwGY}@RrF59+k+n%})Gk0cUaO1q3 z$jFC`b7G&!-1)7w1Q&;Fvz%$CMGu3>qqPmko0lxtPgi{xpTu&Q{?_IjBo%8KRA79B z33jQn7B1BpUn&!)CMqv|YZo(AcaVOCddG(az(rZB1}D zbTbliD^s#=bz3S|PwzjFuEPVEyQ3h?Gvz(?DD5(^K4YrnL zt9i>nFOLbCm*xzHWj_ZUC?8Bm8~p>>2v6R|voh!0KF)mcu0N9_Dn8V7*L0Dl z8jbg~A2L3~$0bM7Gjm%iO~lheJcyFL%00xdQsNmqgcRS@m%BGN zljk)^(o5>zER|G$Qz)UUJNQ?c!uq(obCcr3Z|__XpG>h4tl-z3=HqA5jFvnm;}y%M z0R=944JOb%8xsK8?rQ$)N4i3~mj;;~EE*2lr;;s6IY|Mjq$T?1i?Bwf^gpi<4U{>G z`)b2~K-7NKc~I`6D(IUFsmqtE;7 z1e3IXtk5K8Mw$J)HiliBvz0>VbA*wIb796C#Pmshy7z*&I#P%bV>R#KyMFhq*_(Un z6!pr^)vpXK`R;aj{ftc@yt_ovZ-^;}ziC4?Fh21g`Z^*!UZWO`$h5Yz??!Vxi+yQ| ziIus?DI7-yUv3l8Nc0zr?Cha70b3XL)R#2l^m=`8U_SbXU6PR4R9e55!5 zq1l>|#I$8(ja{F>pLPb3p2^8Jp4#3KpByHuZ1QTFFlA!305n~`G0?|vhaV?qnNhZ^ zlv{gX0(2!(%Ns=os+o=$cUwpapPSL%mrr_E#4VsVU283$*{S$8AZAA1H_ZDEq0sAN zsg#o_^VUxlVeZNduAzQRwOiJ_DW|UtPoPa+G@II*Qls7%$;cb$fp0Ao?JEzqUj%38 zoSqHCJd)mK<|aCAYcH5(~*rI+OgaZIkU6calqO7bv%(Phs7J zht24k8VCz5%Z*3qQTV)ugXcTT*Mn#CAlxxg66FTABj)Xq%jN6t-gkV$r9o-JTlOAEgc6N+TJkEUQU{Ag(-TAe%$Q zrVZTb_t_vxpB!ngl$md60hbBg#Ab8CcHf~NY0V`*0L4zT3>GlT@pB!*r;^(spt-2_ zv>N+bD1nJCd_5Ib=V~#U9e+8VBLcpBJC;et>ULpD0uYq{6PY?o*Gxi zNte#zu(c!zO$;m!n<%=ZnL=lfOp5UMeRD(g8#m9n>jry^8=C@>#4cATBGc(Ux_`Z^ zJ#7A*C5U@wjygq<6O;rm5D%uUTfgD%EV|mj2el(Dx0%JTze~^96I_v&swUG(n%&}C zfI(Mjv<>|S)k8itdgr6HcZ&*6k5R{4CKEhZ^Uo&vd&4u$~dCirXKoY=-vF8pUA%Y*}!1e_Eg5;DH zU*1U*uocH0qNVwlQCO}4SgLo4Fw5UV76z=m^!8;~Ei-T^NMf#fQdYSKTyckXF*F~f zRlN|3WV;yrkSD=(7$gZ9SWDU$44XU<1;F1zl_5JC z1+Kdd11YFZVnE_5B>6yqLU->t#)b#DQtcZF3oA>Umb$nOrcv`7MBmg9oOd8;S%neJ zjIV(C^T?DZ7BrK^;_M*WlwBktceZh?qwzK)Ro;WWeI3WMU z;g1RZZU7v^H3eL*dBWp5dV+(8_D%G_DgjyzCDtL|TmKN6KK>Eg5a_c^3&J(MrYajt z&Tu9`RNA;6Zid00=kN_Fd$Ny)0g@i9-WR)$(%G-#^I;@me(DRFuKGCDDX56N49mwilm`8SUB^amVsFm zPxKWZgvQ!kg3fEA@nEG^jaqTbwCP7>bAa@{IR${RJGk+-@Z~~7Pax4Q5rM~@Xcrc! zS)SUw%88m*$K_Fjv;S+YhSV52vM(w`WoIWJZgnHj`fY1qYKJV&F982oJZP2Yq;0WK zc=0PwSE_t35$yS?JYkr&+L(vpFN;`l$>5KkZ^_Z@^2{;%4g^|W9>_dgg8dvQj^j|9O;C%;GLJaQ$6@E0bFwr8i|w zzLE`)v17}|Sw(SKw2CzJiXSR9)gSR4g&zJdjdglZ->AL#!HfKJ87?1Ytlz+DXt+Bw zib7`L-u&%7QU2k(wzx!ouo-{ELLF;|{9`AlzT-?L*H0Df83Aw?KB#Mh+$reCutM*o zA-Z4G{hjxu7uV3BX4jEpGFC)_KgE`$L|ZFDM*XcuEQnNEEjQ4bv@)&kY1EqvV}4;W zc0NW%_x6Vtc%>aPPRHK)vJc~yo)%lA@yNuVOMkLSkZ~buu1rC=1svc-60(%<*pwg; z@+){@pSd2imtmyzxmz9T|%yNRLN!g3jxk>;CXanB~8mh$xpG$IZXA-(bZ} zP3b>KW`UZk71j(?-IKbP<9IQ1aXWHiX)`nT)0A)bQiYWT0&DfDSKxuyAgK zhIM$`Z=;psDylgeCii8p`v`aFuIWtp3LPoL|dO$TvZ+| zzQ^@BC!aSRN6pR0cT>W}r9~3cIR!~+#sJTsjN{mw!A3POzcADQDNT1C9Bsh&k|@9b z;^>}0ZoUG_09c_dMhf_%&(aS^lVPHZN}@nt`@a@Rffb`KkCRcd90 z_o$lh_?pf^j6llC{)Q2NzNM60bA^v|U`3jPAL@by0xPXE)1Mvw!nLsZYGKRizMlA_ zjrAUP{^tOm`ONgp-Pc8qDkgJvPD2BFrS%9}rWUjharvIQecM0s>>JmhQfpTZAI&1dDrnB&qj&}onV;Hk~j0{zE zqUI%VrVT0Hnm#8?4?$)S4Pxav_H!Ivyw;CY5D={ zz%RPBAJO5UMkRb9N;-W8CNqUMo*A^-Z;}#6W~xH4KC7Kjs_8^ggq+}*r?|gVaerzk z`8%BAa+W5Qnwgrp|G}ZdfK6qp?SF2id$7bx{Q6IU+5d0WjOhP%**qWb(a1)LGZM}{ zorax|%>)sORH)llHLby#r*bfR5;;6hC!AU{)_5+SH4MLdq$%V(!iuZoN5WgzSuXvo zPed)w;ffdmhUZ(oaq#FHcE(7=9Nr{Dc1Wm@w4R7-*s1;X^y*ISp| zWre}zy1Om!QRfl=74?*Tm)KAd;5;jH=W?ftuT2~cmXXmKt)vhPkm1v_bA7k8Ev+ix z1gR?nJ^eEYPq5(i%$EX!%yUU{NI)j1UOta>eZ9fX_PFcyWHb@{%@T6@P}` z-5wlicWJ?Glf#CGG|>!4%-2*kr!0R}dGk|!NdVlDKSIt4ePe}_VAFs&nAmUHX{A3$}P;rV(UPML+o4>p@*5Da~R0ot?FyCHcDc zvDyY&P7-uhC2x#6z#mZ>i-!(liM+`~8v_F)Kb?2oTU#AZ<)z3QMczBz6rE`AOjc5` z6^gL^g+u;#D)aHD*-eSWP>;gdIQ&D-C!%Dx1VE)nr-Lu;z@~d=!;@a4qrHAbJ+S=N zkcYwl{A}b|C9P8QSKz(B?VfLe2kkd;Yf#%;kpgQ5rGGPP3QTn_ejyZ+hc_u7A*Vv98T#x?K=}Gt9L#?Z=c3Y$8s%pHQ3r6vSmJFa^45A(n$&iKM7_>s!&55yT5_U=I{{^ z$eaFRsZh}bWW~sjV=h>zb0*kj5i@VZVV?$^Nh}&&T*|w7t^a7|UCXQ7=1FGI^iNFJ zbONOM*;G!PY};7^Wp+JDxM&b{JSg?AUgnnT?a_MMOIY{jvL24%sm8iOCiJR$7V3G- zd18daO1h6WcSXuKK8>h}V*5hc>CU&h2uB7eoFcs7b$Y#aPLF>=8MD_j5Devsl;}G- z1-Ke!fX*T%Pg?;%Y z7W&~!SG9H(z0@TL?Nvg}4Upr-fdnOAn7f#57hb8;;`;`PPeE7Gh@&s=w#1yuT#Og}HVF-@(4A}0 zL~Jw!W2M1|rJ2gq{#VdvxfQnJ-#fHK3=urG9bWJI1|Can<&m;Y?dN7Xh3sk%D--UM zJa%ATO0wZsG6q!z^Y$k%RL`&MKrub6a6 zkRPFsfi7zT+PocLzR#}Y*5 ze*p0FF%Itez3NMNq2Q@NFM#ouk)SuT2v4b!wbykTeSL%fhs|X4)vGVQ|2$qN1nl&S zj!J;-(IQv_+vDrC5XB#gr z4CdW(vpm94$@K=^g)D`HOaP`}Fv|Zydg85o>kCWeC#o||1?@LsUWhzXTp$dzleWR6qlTAuT9&2$n2o&dAs62#MULg z_prdcDB1PuKGB=_J0(&u+~&bZ1&H#>dF21lim;c!_g6c8FF`1b{&4*D?k0MtrBb3I zM;L@wPqWA#Y-5$cvhiVWoW;iYbFVos(<);elV9R>tnmV8+lOen7&aIE8d=%Cv13_V+blHPFbv%6UXMZlgdaA_($B6c5B~w!q;-KQN+7( zwy3erbOuIlKQfpsU*;2CM+7mkG4Nr1QQ^R9UC(zlWQ!Ml=PLurleKfC8F|AW?uQBd zNVEA}2PLB@=O3Cc(Is>D{^;trEw92yUpGsZdLMlClhsVMBn6{~r2^vEsO=@~;{O&CZ;I z-K{76cpCuqLGo>!++m0Smt zn(Gk@*u+4IwXtRk{u%3gt9A!^q)1o%!Ap!Bqp5O6Q3vXfsM_bwRAx_(!G~~vG)m&e zMnc%(3`3oumCjCir}NNz+(1sBtQfJ1rrqlsIk&xQ%t+;_l%W~1$W5@&-A{>Z;TnB} zOqjku5=2InHS%sZD?{I)Wz>e}qyFVuNwZ`uvoe&~(hsueG(RXpPwdTX%|(1GL8o6i zg&44QJ+_q$y(@bM)2cW`Us-D{pjhZHeZHp|gL8zWR}X#L0y9B(-?qNeNba0PBbo=v zx>#aZ)hPvC$Cdf(A+J+k$PoQp2Gcm(-A_V?HfLVs(iw5)5E^M|R5c_7Oh*Da-HPC^ z?mdk?IGgtr2iVNG(iHDzat#Jxccn{JNf=OA1Njt;bs0Zm?bav$3Of;M$ z28PNiK{kG|q31!h)t@ip!veG6;wz>NvGs5sw~fFMN37X8_P)5Z#??DVM>JBo7`5^5^)e|0~WN5FNhs9Xm_76-83L8MhJE3$tg`i>YL;neFvXiIL zg;~->#3g8j!H(+j^6-2F1fbw=k9pR1fZ%kAr!MQke;BIUIQYsbtmSn!l9L8Q-&W&= zJKC0G05bNrDWvPM10L8t282lN5+jv63#keB+fm20;&UR`cO6dm9_{X~#og4Y*haY$ z2xjT-bmgMIKLdLjPBo{5G2#B85>$77dE*>b{Yr#Sl~5Zg$JFAD3aMx5bjP?by$;h+*{@$_sB1#;l5C9r2V zB`2tx*b_LwO6SgHymXo)qvL`KGFrpw42fp_5{5CJ+`C`R&0))~GP0p&iEno#+p>C) ztRuyCN*bP=6b(K;f`7%*A*ExRW*ORu((ZL!U2~c@>rdO=-<8JEnv!^lHp#V!npY6R zB3VsawaEe{*%OVik`X_3#-JLWDyUW(c-5M`zCjSRv-4Aqs5nrpzZOpW3maij`0jXD z_zs5{5|fx($Nh80D5`;hiDEYCwQOP1{mgZ<&hvZiOgbn$f~7LYtJrvX`Z9HNw2re_ z%v6&wNfL_kMW39C&*5N@LQ8m;+&SRjW*qNj4QopTuKJxl&*B@B;40d1sfbvV-6&a< z$BlpEj=Gf=Ba!jahB{12>hvLOe)iCL9!c@9uU71^(Q}9nah_W0$2JBYv(nX>@lUcW z%1RT(B*I$msQt0$a2`@9@^jWPQ&@{vq5TKsnbc#ESb>qwiUQziP%WW;o;V+5k~)@J zE-F?uRnCC6h&vweO#fR@wq;L0KFYHe)hJl`EVbE61a-kj*5>$PnHbhg)JU1LYb*`W zh^PyTfl;jo&p^^Zqil<>tdJa|3Ib%&e7p|*#a#1fe_qt)8iXGb%t-?`BG>b47sh^O zX>Ro-W#Pe8GWZowWtRj21>v<=6xp1v2fFo~9fo+lJvMVHT{zPBy;4Nv`_z} zdozE2-9bn8bw4AIjZsHnAe4)>Xh;fpBC0vS?hIo!El%PX%;-%2wdCV#z!cF=h#bKp zP*X||?W1I1F>1gw>JlPI6jQ{7K;YO28Zb4y7%84({P97jKxrg4nl`ifNf zd%MV($0hT?MpeuQcAiA617h|A5|%q@E*GxbQ+#A(Y~VA4Jv7X1!A;ssA4i1jp4dXw zqX-By?yv3MJTxj(KXI)EKLAW10Qp<2BJZCWhPTuHr)7tv5gjTfD>&2_8+F$(SC!)Q zAGI+FoAphj6B$-wA1@v5MnM2B`gb~-vhT~5sQ+laaOP$Q zYcZ;tioD^;ZKI^zlz3y$u=@-FgC^rWz86%n96{tU5upjQi8sLvM8qv?oy&nkW?N=t zZL^KrewrS!I%yi*qOP1^#VDM;!4W>)J42-6oy~P*PFRyJHi#Lw-(5!;4HlvAE&1q~ zHnYuO;4zAP@bqJcwMQ%BjohhoigAg-MXRknxk8MY?i(0M$H_d#^5>~C{$?W%EytCJ zk3l%H2A5y}WHp3R(@sVv&T&e zSB`Fkw2A*%`aUj_)9E@O;VL6Md#1<9N&}4bR8c8D<__xL?pgGklB`6DKgxzngJsKX zF7~Q589~<k)E17dk7&I-&xSTxfP=(u1iB>|*6cm}RgBp9~Jp zrI<)XJyVs)w;EoaVA&rdcBIb9Er^zVb@%l-NN+`giiOH&YdAIx9^4Ngl8zDvoKQ*K@jX#szfPA=toE zn92@mM$YlLwd1Ql^ti3dQg1hfa)66WvL_uRW7PA7$7hH6hCEA{H|1c4;{s(p0He~5 zwLUKn_z;E0U-80(Jb}(@EX*VOcKV0S#Tt_IlvT(d$n_(#V5_FM%$hw zR`u(flxB6!5M1VKd-^jR^Z=A6z9^|akH6Znq*K&jK^*v#CPgnx_U315>RpUkUKNF# z!XQ#_QBA%-AiuM7R0iQguw+WYE|t~H`eWcW`&`wcB=MPH&}`4(T_L@k_f6|Y$c+}# zqlanO_tMmSW0(|p_NX%~4`-MAgF^7g`2HctEYq(xhXm0xE?Xx1848dhkH=hBvC@YPE6bfk(KNutX)(+86 zO=;0f^YZxa#^pzc1CQB_!N+u?8;NFT==DT&NFR<}Dp&5oW?3Q?Mb-=f{Peh|2s&@@ z{ZVhlB>h_{EDQBS3AZe&18K|kx2)#^X)*Z@S-VO0PQ_q@jL@!j=NmJO{z5Uh2l9pR zGbe<}hBF}rsn^G2*s0hM3RTg7yref(R}CsfDc<{;D>Z|S&x+KCcgthK2^J%K%Ov-u zTANS&^C>FJq+bj+m)Km78nOf^5J=I%iwE$FT=kZu4=Ei~71Pu%)}Bz5&x*RhG#)%x zvIjq`tVzU2_+9F$-gaNFsDSBBH>bgkV{|GIK5M#Rt&Tuo+nnf-s{J8(rS@MK_aqm$ z!-Fl7{=hr_2cd*cP)Zl;9?pzMyDB-Iz_}PM!p2M8Fl;W67Z-vM^c*}!>7${vMAWFo~6uuk#XBu#T z6e*qchHX%_^Lh0#c{r)}omg=ePG*KG{Ais?ODa{chTU&)Ws5&5N7Fq5^`fBhdXG^tRxtMyLT!2kY zwPnW}^2n3Q`8oB^leT3Cft0})3$l8|1|TF=IH@(R=OBP(YR)Y#!z*=ZD}3J)VtUP! zWWK5kqB;yHlcon)f1jo-M7*NgLge3BDImi{|H;`QsN zp6c6@qas(F;mO?mhfhea9+(LW*6t04jB6aaZe?n1Qb>An>TE1<$CI7IBv~dx3rW*a z4Zl^R6GUF&Qn0Qcx*VooL}4SfiD9K_kD?0Pe4aSJ3F?2cIuL@;oeGn^^!V9n$ZpjVDzA-(wK>?ou*4erWzwTMu2+v* zQazu$6$AzS#PF!bnx%Iz+>srTq~{Yh0X3&MIUeUHjrHpni%er@_+B9uErg?h58>P7 zL2sN!LuVW_w8N;lxCbOlbsjMwc0L8d|rY~T!H z*1LFK{7gScEw=qd7XJ+II!2^gC1rk~;BGHrFd^7I4|+|dXbop9R^j=OCmd%tgCSEi zCL4ZSO5^<0Xxf6;=e5hY*6`10+7%`=)nea$MKPp;qd>E|;pO~F$LH&Xrdn23sLRdo z+m`C@!J{!XQmAEWcFpr}?!KPkg+K5yL~2*mgYP_-wl}zC;#5H(0nbD<#E2oZDfa9a zB-Mq*ECqB}j9;hBqL9p_0cy8Vr!dx^(sZxHSW;3QGf6l>)+Pg-e7c?+xl!nW=W#k) zyK8{ZX`|K1>w>5TV~$^}cr0+9i&H~bXGp{;I24lt#FF^@f8{JvxfPO*3JqUQ0n1Ha z*cSJV(2lak-$7^r9jS2=kdkbTdF4OOKF^TU?IY4Iou;2(npvA0F@xa`TRg~dQbxz! z?iTb46n^E#zwBHltit29J81h(}Py5r-rXnkxuNtEU>;^-?R~u6jcKKyLF+FT*Q?O z$Y&EA)k6+ICzrVPqW=UU_eb&J6CS_oIfRhuMNkc%arY zDULjt%k!9bTubym16kS)q|4H|Y^U)M@_L<;Ejom5neortf zB{sUA6kF^P6FGIZydk9Pw2+31lHXMdL-#xTno>{8*j(h`E)&!#&w->sN_fMVnv$Ux zSj^T2TqgnQ@Sa~Oh<9w$iP5{FVrUP)uN-COUlQB3HKx|0BVu28r&Yp21FttKUs4=m-cgxM?=t5bq`sdT?kiweytQ>#^ zIW5>&+k^RuBcDcCv%hCdP8O6V3~>zkUs$^Q0Z&_ANM(@uHZy(%y$>1)S<&1}%62Ec zIQSKh-ET25B9y0lsFm76S@#*Pn?2_D6J7}sR4DF?+&s1L8yq|4ym z0Bvo44~!HQTE(~1u~aTP-5rf8ez1MR^;+*fmgZ;Cy&@bb(LV(nIwqQ0@PE&-a7LIP z_JGdI4R>6x8&51=XC{St#jq)lKGD9ut(Y$+6sHK^rX+Cw)RbrgEoqX6ujN3YA6@RI zH18W)BuA3}TC#-(c`?2HJ3M3ZMb*IcR_$!NQeXw?fHF#SBCVA9#~)(qBO7(zhRjSn zu^L(QmDHR*h!0Dq=%r2GMSwxUqpCpa+9okVvaYjKRob)GeKb!d-yPIU*~x&J83caN zH@gn2dM<{R&e95YqJv?~_Lof(V~nNlwIL|oxH`L<&VWiwEPH-_gkaHcWhN&QDP6Z# zT+E~Z^L3dHFzN0Sj{o&>uy*;U`N{mRwJN3DvS00uMxPQM;R(ecB3)_MpM)*`91J9l z=Gv*{jK1>n^3sp1jKp^sCG=Al=duv#A&3w;+RmE)dV+yGWwT{7i!^+xTCrz+*>&l>2t@sJ`oi&Yw8U{>^jRun{jk%U5H3Fc=Tjskt3 zV`5>)96x-0zZ~h*n_*=z8F~qQ+2ML@#__j!u+FBC34)bOJxt9AR$H+dyFM|0XN`ua ziIVL!)7>apkI;|EGfzKvT0e+uo4OQt#SpGT+YdL44G!hW{&lnyia~KL;LO|StTcOs zk5(6DIc=_*1Q$7Ru2ipso%GRRHgeK~h!@4oI}mG8znjq9PsU-gnxz|mv%nF^_zQa{ z=!1n6brE@*FN^R7b8 zf0JErJHtCgv!sdc+)3eOj({`CCST9b=Bh{LNHqN5gsY3Z4&4-Wa=%gd z>O}=^1k+nGOd= zjW5ierT!f z#f-b?zHVSNdoB-4;swkn?i);vvU7Pgf_q=4Ml=cP`nl^>4%P?XUGLv3*|DhZ&Nso? zlaXgg+<2JsBig73*F0&50s`|*KVF}nZ!2tTO=pC?K6?c1mSQibj|h8t9kKRcmBd7e zc)2W5mdu!5#l>e)S|KFlXMg^2L;~2T#KjepYsguyHg)%^>V&2h(CdvV00ke2?$!Hzf$7Zq z#^15N8WDIA!|eJX^g{f|n>^tMFhO?ZN~T7l6S5>%tFu_pY=(U7kK*;9y{^fZ`1Zsw z?hCQCr*DGq@gCgG$)WGBkNJeV6L>?t>Uv;nb;FJGx|cZT^HK7bvyt!CB$2_$5)2Tt zObZvyet8~i4FICzb*^zw9}eX9Gb0Ir+B*J+-AeX5lhb+Kd}b_trI-a6{84!2fb`jA;c1u-<^IRvIx6fO zBN=8v2m)#RsEyUKgY=BhcM6DAPl+inI*vvF##l<;=y4FQM2u>{l5rLNT}SqrHGowc zOsj9Wwa7%0c*L8PB0gY?+-`=m($xM!q3!x>sK*!ZhZ(e&g z@|5db`Xd||0RdNY-rocr0k9ps7laSjozXPp&fiHnpl$k(fqe#(e9laTuIfULojGT@@#v{goExisxdrh#%O096C69{SKrsX|vP~mCL zGNg8;5ONtc*4$90O^Y@Y*md)0mods0a-LVF1X5}yWba;3EB$z%aMp^W{{_R*EbyD( z8EtWcr;LwGJ-G;6gLK}(r<)X~ac+#FXitQy(le9oc(QjQE^Nd;AP;bzP915kaz)}| zX1Ra`WQg-HK;%DnC);X3{R~`D=m_Z&l-O(4v&8XOAWeY(h1RZKeuUyS3rpl;#ID>w(prMUzwI!WA;>q>g672`~pz^1W-_g$} z4#B5^WeN-O|%=X8Fwax#q0yX~bx$2?1IP?P{uMe#AFu%ymY42i*s zE*YfkNr*HuQ&Vt~!i`x$y9!Aj+)r40%*_p9a*yY+oZEl?&d?HnI-w&YL+D{e0;+-5 zTD4MTC}$}29lyqf2Zfo09QABS>zs-5o#!obS{&g0Es|9NN^GI)Kx>E-OTSEeuycMw%4QVo6N>@BLb~>d#8?zG!8b(_~ zTiVBEu;W{O!80Gju5_Mea^^2kAif7AaN=IlCasCY8!Y7C%DH~Fk(7lWTolhF44LzT=q#?--TjYw2n zI3*c*)^2ijgURA;JMAb(2pz>SJqC}OHa;#62@1>B3BUw!-CE3Ol+oY}eK_mmp)^?Z zPS`#=kJy)askF7g$&#OB2)km(HW2!pF2WyRfB`prbd<1CrY%*jzXKA8*+ z*{K>A=CL4A$`gt)42>Tj`GltUcng{oW(15H@4@-^C<4#YRPAE0fs_Q9;o!pYGW5i1 zSNiKdM_l)3=;7tkq_=Q17{?5(fi}QG-O!#M1d%Iu*u4gFaV zYK*`Qwz$h8vZ*yCP=|D!BP&|@TQL+2Mm5b#Vn0smX8!61_fC8yT%`wIJf0ey*4M7& zbODX1*DgM6ilgGh<48y0bQR3t59Dev1Y?Ojj=IdP3ZX)x_Us%O;FGu~mGiIUSJ0BijuXmZ8mL9pDslv?S1?^8IfPCPa zKy3Fcp2=N@@F$oZKFI1H*YR+a#pAhhAJg5q5dJqhpF(I*`KDo9=!7Ph+fGLq+%PHu z9!kW;1f55m2aRC;4rf}W91AdH>2O(l2G#JeNhCB3-_i+Q|u<0 zb$KRs$+lNiw#^Lmm2!{?Ocr)_&O~@%Jn6by^OO2FpM_3+Vx!r8LyavX-)FDh_40gN z7#so-y4)Hvc6}`EWT#(IRLJmIyQMpPk_7sFubh`fsGbR1vcrBnVQ9{mAPr-bgOP*`}lFniX{TTF?6c+;p`N)E5` zT+-2Qo3)v~3yil{hRJ{T0uaGRa3KgvNrX4u$p~Rjmyquyyy|@Z@k&;|Np?`e6|7~= z2AQ(2H!%AxFU{VBAT8eV|GYtwo~S*P2;1YTl+csyJR(@<54ZvgM8b!-%ru&fD^g^$ z$+nC0M3yo1Mia0EE0~UHJQJD^P$}C$3a8J*dMzQ!Zw**&+g%!WWa59l&u4gGx;oN zXG7WS3_!$>73_F>@W&#~%sSnIFjM1v4h;dF5*Bmt3XOmO zD6OX8)xzAcC{9{-W(Z%eiRC-wbg4o!J5XvwvDPd+@uz7SB?4|YJdCL`fusmK;4MBW zd(0!1ZbYDNEX6A}f`w_fwKBzo1ts&J&ta z*@36f3w<^+YQ0s}*qCdVWOqj%hk92keONjzV&76Fl<$aOhMlSpd}l<&vdps-|JRoS zf@P8^{zeqJU}lKK@Q-#DBXrO+lRRs|6-y|(OMLy`$zQ<AH4goan%FqA1|6Z`TdD}X!YJ~_HF|wVxfGJyPG>p@4VK1gucm`T#-$}7*MrC( zLl^_`zv&0}2&Q$^?(%<&)I_xpCBRHksiHJ?&rsIo`FuP{+nus-d1O*|Dt6rpT3RJ- zb-(s@K6G?UX6QKNy7Q9CE^Z=-G9kRoFy@3}gWl#i=%*(|d|k|Y0m^)?X8R6?vv%=d z9YuIRW9RW^#`T)KbF-TT7VIdG{I>a-cFW5qjp>&4iWeruk|devAbVUK!6%B_cGFj{ z-q)w6Q^DM5cv>pUm!y#rsMv(cVuuqSNA&>RQY8&z|Hy<)-JTbEwNZm7;OU{Hz8K`rm2Gz^mCq{pWjF|!{Zx?Pgh-;oB)ljTq6H&=| z7V^Z8=@i`;dedk?w*!(cKC7HFW=lD^Fi-r3;f3Pj-a~qjNt)?SSLF~tPLyqy+K8N6 zE!pmL7;Xw{crISkdhzyA~bclrqny)Q{) zDXxm9*?M#hZho>~&&DH}x&G||Y^JFwcFK~d(-pD5Ps=phy;PRaw0rKf1i9VA`87H& zXZkPIQt`KgQD93o8QfXt3vTGBCG&H&6gGb|YlJTuxM?JifXBAi0I~L1 zMsPODccaW^>22)Tici6)f`TtdY34H-SO2!3jm&)x`g54JrqRtu1;Nty;sYh+583_G zO!rcTvs9Uz8Zv5SdZfkIG`5rB@2Z4}){(d?F?DSua}D0|ppY)*tZZ?*5h}-RLoFgh zxVjYN5;K@!*8RNyM%6op*V#txx^J4sc4nN+HnuaP#`8#`%Z+qPzGn=AWR zd+l$p|IhC+p5q?Zb)G|C@Y_wj|a0yp&FsOsT)V z>fFmTgW>CACGi5S!N{_|Q)v~G!J^{sQ>fB@dnR}hX@p};rMusnXPRc7u8^Zjk&$JL zW!fi}yFClyJLUA>x~q9&0Za6R1FbeN`mT4s#NAnWw10xyKGc~k7m^Im%T<<@w1cbr zi27?cnM!3RED|3KK8fAc1UgXP&`4s3Bd&hw zfi&^Bc1Hh;g@_kW|3?E=Ocj*J922-UdppVU)vbQfO z?y%S3VmUd=MHNS${hM%L+^+CxBdhE7Dk zJehRk{(p83V&`U|IRfTX=@!$GQ3SnsN%x+{gs1z@{hJ{ipPs2?KpE!*PS(O2`5s|_ zM@KE{{sVo`wv1q2$ctGYj}Q#}X$*A8P;cU{f*fl&ULZg>#)T&p>z z2>{pIU^1EYst!U88ji&WE;st}dJYEVwH>X!I2IAE1<1TA9$!U?2s(3S&#_~mTI9Gr za~@#O*UU2gh9yXx2Cfs^VB*Q|iD>(MgtdW6X|bQWVx#vi(T$++U`3HuQ*Np03T-xcL#Yb;WA2s7})R& zWVH#D;ZZta36GyBmuXffMm5wk#MCqa3!q(` zwRKAz7BTmG=%eNj+7cc0F&58%ekV8Z%}rwpP`G~>ZsZl9S;I?#J|Yw{y38R+s?9+= zC%;EHRR#Sk(fQ#FLHL18AjEq_;=0`{W>HkvVQfUDJAUBMA_J!{cBppf54<`(l-+$# z9=iI#@9Igth&nLl7;&6V%mMys527-H?h(mSxeq<9a1mem2lWcKQgV&ZKNl2}l{hL*4HpNRyEU@l*CRW`2*CVPXs#a(RY(whKHnfC}yolgjv! z0@3=Ka_5?wey(CP#9sKnovh_uRIzdyFKjR!I|U;;rBiPyh@Jxp^1{+ z_>ty0)j`>k6hpYCwj&}aL<2bE(G!uNsyX*J({qR3Fbc@z%q6s#k2u0Zff>>XBIp=2 zf7isLI^AjtX|ub;#|BVoAd-S@WumsxAuh+I9S=-5=F_d10dX$JxUM#@5T0HfZ!gE` zN20sQ`Hfc;Xtm`9-w!)K(IaDvySc1Mh&nTO6Vr{OCy;#=!fV}3d7JAk2bkubw1!sD z`*eElokztn0uzKq7ZFN50c9OffEB!^@uU_J4U6SQA3A30s3SH*_!o+x_|FtKfj&@S z6G5RD$7TO-N-};Z4=SewV<47s@%Gb7-?)9L!2t^x>)5!W0s}nyIOLC0MZ8ZZ)3_NTbO*l!KWNrqM6`O zPp}UmhIF9kQQ@;Y&CNY}$d}q@CjLflQ3503NnJdI0$E{ySa;Ys-QM-2iRJX)&yK0z z?z%l~G=wIBP72oX?^H_Jz4T8iVeG}Wj-cK9K%TnNDjv)2o{+K!07maUXK3-C-78?z z)>gnQB#7Vc$NhyS6IMnJtyqS&ncXMmGzVdyq>BHi-^fGz~2t>+h zwSw{=P_OJnfz|ccL_wd$PZfGB_E{?}^)aSqoU;l#+UYX9@%-ggD)6wZ#Ui30>X$AX z3b=1o21w_UWXInBEc1IAI8q4gf3W`Y@Bkna@`T;fpzPKVcSdmfnJOvD{#~>TgOBra zApV$2T~pZI4i&rX!q^Pp)Sp|KqrlZ^j(LiKh%!k|@vQ&bwdSnC!S#n4*V+8f92c#2 z+t8Z90qB041S@cmoB$;a0lLL5;}yjk~%9oitm!(W(%O|Ia{OhllzB zYJkS_07Q|U^@6B4Tz^Xp3R)P@KUix-F#Id2Gc~lMHkktuPT}d^&lG-BacWQ=#a;^Pc+EvnSsmR=o#Ii z@ud2jt$%o%T0D4oe@sf^9>e;?)fUFH1~e~ktZ)B_3%{`4%W4UVbO}_>SyM(fU7cwdmQ3jx-s!r3BdKO>oc&kR^eR1R zmF*h?bzG>6v!+_|zTtc8eI<{~LVT(OARV@goLP}TZxL;tUBI~gNRm_c`1_0=2!Sv^yJRwL5|pZ=Z&%)(zfV(p zyi7_^sQpfD%Se>FiqLr>Td87|M_K`$@gH794dYD?fYH_#Z&8^_45=_!$FhkvAW2}# zx5>rUIY9HGCufC$emw5)Zh*ENPy)*U%L9AA0d!%&oo8+3_4j4o8<=}Q549*2hr~307NSPiH-pX zI`iTL5f@-&xxto?{$#{1MhYM#qm%exe}7GG4U+#er@=g_*>#TGsb*}9l~3Am+04THHUtOF55(r)9M76!veGh8@lpufn#yWK+;jp4esXPnDA;#+ibx)AO zk9dKW>Xl@{fpV#})x|*QO*U*djA#)OdYgd^pcKNBVoC;=-oiXgj4S2B6-7sQQoJxn zc`?yOMpPqja%TainZ`#^nqxyzBLQes#mvmq99>d($|gE#F+Ymce#7_aRff}z4_|lO zH{0X$m%D{d_<_FH--ES1lE6ysEI7BRSweCnQiF6Az)F_nzmM!&pKOSIt|is~?$r3hradxbO@Sjr{+Ek3J89^P$o~vPX9D;zgJes_=*|2f!aAl4 zcA)~M&9uW4xGb#fC=sXg)93l(y1OUyQ(+injx ztX98OW;&tLSir3#9h3CPPMa)Asrh()xf8rT49*m^Ann0kDox~3%${f+dh(clux#}t zT2#QliS@dNTJJ^XIbpS|a5=sY*tQt$-+qUr?$-mM)9D&Qsu`NGahDSi3jOmXxYh$A zwBB*G0v~!x3Q<+3OJH{ow$=@HqQcNe0~>xZK@|xk=^MF^!Um~4C(F|yzqhG_0Q7U% zz5F;)&thN2Ha#wcpAsluy&OG(mu+%NLqGjGl1(eAhVtohcl#K8^Dv5VO!=76AYWZW zxU*?34r2{AQtav+Mzlc{We>9F>P&4%;dC!GwnE`H30Lp(uZLk}ij$zw5hA+aJy zv-*kO-@vImrGLT6j#~syfbm*(Y7H8jF1vp>h@TcddpfGdMLx?&CVNP^k)kjXD)wfB zPmoiefL&;@o=kwytYIM#t@e)P?Y59@xy2A>5IMGRW;!tPR^(?@eMY2{spOL9h+_?$Qu5Z? zEx!vZH2k5kXltWl?t$r)ts&uT6h=jz<*S7|`DRSXALKyM?HqBfcs*uYVRp@-=;9A$ zVDvz(!Ji(o*I9w_XGg%K9$haXN_$ke8&ZK&Nm=5r791vcB~xr{Rr|MDuDUK{ zh+QF(1#$I}BmZmuGyMF>`h0;#QLQ+e7q=9z9Sn4xh$~@`*JVqPm6MwOWjU1a@8y{7 zUnrRSCi%^Rp_3g{I2vOU{TV7zd*+U+^-FHp1N142b*FjBXll5j*>dt){Dp{k8BmhrhxP9djS_6ba@hFN~+zkgv2T)Se_2ofqID9zc4av`)@$R=G zMkVD9zM+HYxrocF=-};r|BZ^9Dy_0}3pNkl=R{bbnLfG66!bC8pvOsilDiuC*e(`% zd-Z^_I7etZ93)_yT(qf;Mx!K0b~0IrVPbWIrnWVjFLNZuv#^}AOrzhU$=)O-E@&Uk;p?D;w91apwFe4keH@>2|^dGp0aFtv`Sq_jCx z)=nvb&1RHnY_N9KH0@Cgs@B4Ay;oO2IUv3IsL-&Z^x|l}?E%G1y z5+-3?cKnOqgM+YJ$@$nOUz%0jpWSO!+oBl9ntBqX3Y@8)R^C)J0LQn)OTvk>75QbS z7#;^hhDF;CWIgXY_CUXn@X(8+fiu@kt`zSiRp}OuNpv0;MB#INebIk{2iQKVxc+o6 zW!(SQM0!7Qn&h*)HP`FAc2jjMZvygI!KTz=Y;{Dn9K zCow#stT4N^^$>HXvWG4z?}`qzE!g0M+;9)#j>v2=22#dq4qdN%ksm>x9ACLAvmsi^ z@+&NrsGJ(Zc&s(iea-ElfukZ9r!Q9lF_Bj28I9R-|K=bq6kUR?T!4-V+D*kd=Ao;t zL0SC~H3Nef(P?-(tk3bl8Mc~eq-JKIB1}|z>s}!i)542Z+lTUC##OW*|E>T+P4-}b z3{?jg-Ewj4TFXg4%$i$>Y(3ID0-Ot9Zn zDV8*kFH7D_duY$3qZSxYPo{h=d&YSo34aT=y&MZuKpB9mTF*hWWeU3PTUqdjGmwey z;@?I4xJ5=2rSjfYZ%}XZn`MFBtHuGlo)apjF>&Dv!MFs3G$~b1%uK*scR-WySreYB z5(*gX_Yv^pX@lC)LY*SaS#ts&H$AcQ$6Xx^Egf}izO}lXN-S&A>3%#)(r!2t2zj}R z{6Km@iVl%Ni$Mp|7*&?}H|MLpZr0vJyC4iK-zsTY=z7dDf^ekz_zP)Kf z6QP2dDTz(Iahz352sk{3ofxT6yQ+gs8rXr54iR&5+bvt~2+5{mceyWC?GMwOPo97M zt-Bq_1t4WBW!CEIjjAvkywM3`-^iwp`u9cWi2Zsw(i_U95Nd5U9KBmkyvzj+nO?6C z84Juxu%X4>y6mOBo_;!?&fp`Y+KJj4aIxrBfH};mbBOsdtJVI2=b7jl=_?7xqt-+Z zf5N0gHl%Y6W@oAR6v@`t2ifsAjj;B8iLS`Arf7@V3&Ltd-(%WYne@B6pT*}QL%dB7 z!lj*@@B3>dV#zwS>}a*#0F%b{(yB|f7jAAm>^9H^>j>8`IUBWd5h1aE9jr&sB~}y} z8-iL@8Nm5(_g5{IE;CMartSV7H zt}YzC`Tgij=hD%8#uK96$Lg5q5Rf~_!PTTa&LoHNovySt^8e{r`~*u@9HG*Q%DN)A zzrBGEPAnbD;lLJe|A0zA8WszPT{&&nUm$6`D{_}kLNlXa)T(f_)5|WzOMI<+Vf!iV z*B0ki1x}Ac4aUhoAJjqF_c-beo8$M%kUVcdrjMDWnI<_uTCEP`5e-pg<;)qMX>&mh z*&v;l$7XL*7*}5Oqobs4h*PfT`NIA{>YwUyLTwfo+1bca&B7c0(rpe{d; z6@+0VH9}KloDt15oVmCaE+iIPR&B*0g6+`LfS9QaRctYIc7Atf9C7cswS&K+*FrYJ z)i3{DtsG!_*EtejNJdaZxl$q1d%;-Hw#?d%I^Pw{F>|DXeh$ew2$1G`g~tdzZSL%# zFOp1zc6Bibmo(mlRVuxJ?8muWOM{)PSU;g2;-6Pop`u zJ|GRl9!Q#K&pA7HyYc28m>fManhUgP-p~w^9P`vt>sN`^%k!w;Ba%cH7kK#LSj&LK|(BMh4sy3jb@W< zXHPFQXUiUl89^UuH~n)*OTmDQ9oj~Pgr=OR2)CI$&VR7!>6O_MW~o4KaU2Ym5XU*C zw0#wc;{)S&Pm8p3D<_uoFew>gk(?PKO_W9SdIk{%69ia5Wkv{o+_(9BjzM;;QVX;D%}0SzwRo3PBCvy(~5Z!b~u{E?P|=&?#SfX&Lafd1<+lIsI7O4!MBX>!pI-;Zee zpjIEND#rtd_gxLEzA)V3y0;Q%*0s{)f z7OWIoPamsmHAtSuTr7>gDN)Y9;%fL=n<}RscVmZN7qVfU3t5EGw1t=R`eyONcAVybjXSG`D^bkQ=HW!nAqIpbPOBVtI72)OKm4Ng*z=6 zV;ZqqNwKWS*Dv6!Tb|Ef@Z*~pW0@4S-!76vfIJ0zRZ)AZmxk-YP#*F-w_xq4LKbV& zbdN9^5&4U9e!12W_4tbJ73ms7R;h;?pC#Ce#&_Y$kYsc}@*M`b!CN@ooH= zkDU)Vh?nTnOLf6F5!C{KQUlcpJFTX@N{(d%{$SMtF-6f;HOC3n*;27t)Qexdo4J$v z(cMIAnOGfFBVKeq>UvsfWUu>j%%PUBx08hBACRNi&H#Cj+goFH$e9yfh#;X9WeLQJS**y1 z`Z3Uxdcstx6U`p>tT;^!TEF9}Ki+ya{lSBV(HI5)Cu)9`m_5i(MN}Nsl5JuQiVjumbWli;f8*^J2FC4+qzM)L0nA&QX(BU zuUvk@TnLCWA-dQ?HHTFXZ3tzsEL_~kiowGF#db-eC9BDWxHRCM@_V2t3`_S>q}uS<2{t+8(a;??^4p55dL_ikJ@oj%F3vA)(;sFB_(ae@HW@R{ut520%&9%0A7+w4A}$Bx)t@be~`)?C?{4X@H4JpoiB5K8C3(RrRdtr zDJzN~|7bcO9i^XZo9OFc>%d)(_0n*a)MaR(v&Km9%JMVZvg~Y2wEV%v8iBsX zz?sW4FqDyk0%^HV811!%H>n^R4jN7iA&~aUUG_wa-L$i$rP9pHPAb9g>$F5^5PF53SX$r-v8+ zHd;@sTd65s*lyII$T3KjE4^$O&OP%G$>QsbmE+wak+|FWzGwKkx5!bvG;9^i?bm6UutqfRF@rJWGg#e2=P^1-kQL0~btT>(~> zhCR~irI=KzDb^gIKC1!qM$KplElk(^ix5x5tNY6JwvDHdbk;oj0%bod*T=H{&DF~4 zluV#-snHsB?Uy2iCqW%T$Mg`0ISXdc*zas}Je{6?i6pSjvPa-iN%IY04>@o2i!q%X z&>zlt2Y!n{k|Ty!A|(~h?hdJvMM6N4gNinE5LbC-9GPymLcS-(IQx73=VGiv?_&H; zzM?IbdZ@q4ztyYdrxzl`gxF}frCzlkg6H;?cWn2Bb3lx{*56Ld#QvRs@J7H=>D=Hp zxAt_F3CXznx6db*p?BC9oW)-o`g31*fX$!X^i)Th%PVhi_V{4*i&Yn3)j-IIq9s@Mgj2xUMqMATN$f--~K$b|$uBF=z z@!#3}m^5E&LFipRYz%m*Ha*uKbZLiN8c9a{PFWnK=tOJu&oe=g!3D~(p{0{X1*_x@ zx1A1jb8yo(gEsFquE}QKDQ`h9>fKt?WBUoP##WxKamr|LN9NVPM~?iwIc-`W z3+m`6+wr>!E<3D+1YY8wpMD zp$ypL&U+$x_d>P$;b7~q3k>UJ%Ki%7D7qis&^+#F@yB6fp+@)BYUFV@F9rDnPq%sw zkV$b@N`P-^T*jtJKC{o1l`lj1;`s&@x4&b|_chdf8?z=)oz5aP3|#kcQHmYP(>uNC zTCP5Cj1V^k=HorJrD%S+nxc&pYSFXYFF=xcg6$YS69-ENcSE3yq7dAD4|XY!92j&z zHpqm(H?yp+#sg4kgL4wY_WgX(@TbfQ$gEL)U$-frR%pZ|B7!Nau>P{M>O(;>zUmw+ zx%hSx;YUk8w8CB8S?4j(+11B4=MzQDzRIslwuqlMN8j^-#@%n90#h2)r^$z9FpVS>gr|Hy2zta3 zLr{9$QdS6PqJS&4WVs}^2#V$ax?mJj$lRw@G&fD|>mlX<4SfhkSP-_=| zaO32D|D5a^eDdGe9kdLvl!it92b~^c8WbW0YyIX)#7c>U#2|VVDMCpys0;mQag}Ec+JG=FDOa z8~;Sw@96kUS;cvNg5lAV0Dd{50nQp99638$eByx=}XB>ES|aj$3K z_@2AcPT3S6Hyavp*E2VP4C_w(R!4}#Mjv>Tnr1a2(rJ(y`iNi!vZhz6|;V}7TZi0b@U6?pUMri+P?cvq3@pwV+&rN;@)^G$R*+wUZ zi-Dm_(K_jT9XdiU(~$2XcYVFVENBN>hvLeSR#*m$?&zh6UasRe&V>R+*QR?O8wA+^ zx|89LPPZJ|VSxk(wf&kJk%NnlIQO5~bP0 z>k;upPi?seBae03vtxmGdVu1!E`#0RIdjZ2?Q>)0Nz2hNzvzFG7qh1t83~D=Vxl)~ zEp0u?2<$c!l^RhrsQ6d|(yQ>!r$QwW4fnKJ3$6VuMT8)aHAAyIH0?>AGx^o-o|h(qjsXrhPz2A&k0 zf?VEC^XBp~T#7a?IIBeog(bn}-S1wO5_eG$> zR$2gCdgFp<4LlPXskMg3+~Z{B@hDTGz@(V;+SoTR!_nRpEpJXuJtq>y)M0)W8?4~! z{U4z}Wwthsgl^l&_?whGLaV^pG3>S}qQU*FS57tk11lDgqK{8xjx}rn?Iq}MjZP#2 zV4$?o$#5X-@@jKID5Fo2J0X3T+N$OA$&1nK1-B~5sYD==k(wML^I>57)~`Lh;k~tc zXaX6I;$heE4;-lu4EWltqyS4QbICf{q|1A_CMlG@m&p~+jO@~Zy z+tTZsnjFF4eXqimo$QyROZ8SQHgPuI)z&wS#f$7_PRai14x;^4777&d!m`VT9V)xm zJHde2K%50{XX;-j{cFGI`b)ydWaPQMs`h&IZe|C*sk7A_`Fr;i$?0_spSA3Ew)xyJ z&A$8n&i$FcIYunht8MSPrP8n05@PsZ;IwcM z&w3K#GSvHAc-$D(>x-wKSQuLXIYye1F5z)L4iGLe>!E0bG~MFI>O9FYnbOUFv+^xh z=zX6rv8LPJ+kp=Z&Bw0Bi`;+qXm&h@hj%9?U~9(b>37ihLI6S*-?o^;buhHkxL&h5HYrE-CWuPpt^BCxG_y4UbJZRY$8ZenL+Oue6RCP zvmp;*yC}dq5kB${{e}8}Xg)6)Rxl#XqJmI{a@f1TB4y}x&ua8L z(h5{r?Sj+QK4HZJzjhH)4)GwwOwZhG@V}+%lsMe)p%r1rOgk54D6Q40bjNvgb=Ors zrlY+=>OS|e-CYnubogM=H|)Lb?|pL*dKjEPo@?5Mz!;M@)m@HtS&JpfO^#6hvlYWO zW0N32AW4Y55OM7JYKW4!-1fAL<`f@fy5ttE$~PXg+mEL0s<+(#=zPAj z=_fuNB@x{OYA~JM!~??^C`H8&Kh*Cy&n*$rYgQr<8m@MaotJARgZwIxrz?I{dLCAD z$$r>K{_?$-NgpQq|2Bj`xBt)dRhM&U;u^bnWqUM=U1r8VEMsxyjQn`YWA)}?`il{v z^@a6*3fclfw4*)dj_1R()hbAO+qBXhQ`wVZULBiX;8-x4Ob%7apN0lqE$MW1*-EUf9Ex;Z-oI-qKpHoE!?1txv7lNFSh(ELK+F?|XX9X&T5?Xm-e_`NQ7n zum$6>Y^x#D47Ih^__8|kweDQ_8z`YniAC01439+n7}3GSEq|`iw)|r6peIME;kqD` zA%)=+m4bnwF9nybF{9Xy;7I>0oZ=!gHopT1wlcT%(9W$y!s(*Fy1x@_sR26Q6Rb6! zHO$AOnW5N;@|l3VXaB=Jx~uCAk^=d-98Gs{xAS!Mxtt8oT?23dBZ@N7h%l0VWjXGi z9W?EL5nHBen$7v*s7d5Y5bo65AmJF_3ufq4|MqI|oaTHJFC@%Mw9fBfC-Q>8F780K zq}x919Df_e`x&$CtphFZzdKebPOlI{$~L67)Sg{ySAaBrFZfoG##uxv%dYgFs14@- z#l(KGu)DxVV~BOFj2JFK_txcp6Ww)?*B=+L_9u?CHxVJ-!82FUmoyKoznbe)Q`@wB z1054M$Ojw`D+#uz#)qkivCNeYE1740t7j+)06S;`(dR!5wPT`foT;^rHnjSNbHu1w z$|OfIp_8oE!|te3e(5^H^RLpxo>9(tZk-TohLB z#lst(OgF&r@OFxsZbD_XB!r_8dS@Pqt5o$QgJxxp#oZpnYxawsiy&7YaHoj8d}}>k zc?n`;dIyv&bw_e^UtrfChZz_hjP0b3tp<`OWpQf$i^_N=8Ok6|@o9&s6MF>nxD>q* za*5py^daEr&oV-Pt5p=9P!%cMS?G`Cochi4`EfhVRYZ?baDZ#|yrgj%F0c!2u(BEJ zu%k$8iis2uS(4$UB#ny=Dp1sonvP6LUtcB@zO7qxHL(aYD?|hdUGC<+gYq zTPSUv`ID6)IYix3ku!GBp@PiGkPQ*8kh(HfgCu%yMfbjT0%>`ten0GL_Q%@z5!Muw zxF;r2Z8|5J+0wZvv4WfIrC#mX2}JvBO*fg#7xko+!UW#A2$Vn|9(p@2oF46Zag)>V zV!1Wn#9-*{JC*W&o**K4HOLhCTshrd_4uCSYs~i|8d4V^u)$fbZxR~$8e1#v2^FAT zTr7qE@T*JUw?tu$ezIJ0Ow`#zqR;U^GVaH31sE*gmCaNW@$=*6MzM;R_pA9t*X6rw z?xzQ5T31_MJs*^MfvFx0hppb{qXjC07-Q`LYboH^+**TSU`|iIWm`gb!hSS61@sSO zQ$IVlDf!8o`m9q z16)TBKE@&MWQ!+kr+N$e;qiamX6c;f%;zi)^w`q)v9^SQ`E4URBmNL;&?tIG=?llb zohkPmSi%`8_IM@ej@!7bm~Kr6Sz!?5_3z%_klQJmV$Klx{W!APd?AJyihKTPq1xyn z9d26E=O55|PO^0zc{f(16rO0D4w8UJZgU0ghmpPO9oY<*Rga_atka6mvI*MdngC<+p&OP&{VS_TdY!fII$OBw+pf6e_PLNIce~;zE7Wwhz zgLX4tiiqsg>{FXiSiG)DFBXS)tlr)iK>QHHvYauQP?VI_h-p&EbWpZ<*CT~)nrmC ze>J1Sl)L)(O3&TF3nr8;yl+wEVXr`34 zNje2b$aO@%aeB8If})3{^x3kM=>2@^iLG9p z15$ZsKN)NQsL5XR>goDGI$&+Op9F6iJ#@=9J+|h)taIP6P2SJ*+|1$R^krZyP1_wL zKJ9<~mEW7E&CIEbffW1}j=3k*=HLA5X-w?S@8+dVNmwu=jYc^r)miFK@s5=T6pxi< z#IUhuN#*~;NG`9+X5s1JX}p=BzAAF1RhSG0sMYR7cj>RhaPoX^I*uRV3`!Ea#9VSK z@^L&VmWx^Ws`7laUm4)7uWeteeF_r-Ow+mJk`qGXjRcQd2~wzN%jBK9;W^tpd$ghv z>Q$URTODTJV9K~6@wCWK)c5a{9|tpJxVn+6ec@`VLEln)5Nh9^y$tdK8y?uzo$}ta zWPTls17o}!@ciCiUu%&v!?rr!zI!%R&&6vJTi>v!q>|=3_4hh1ct!Wf>++53o7Eyc zS15+FMI~n~aI&P}D`>|JeWZzGU9vpQuVnYYUaV%-fA9!Z(K078>;^9B^-o5M;65J_ zUx#W0Hr_6b>k4Z}!)qOm$Gb!x9ejtMb$n&<;3GYV zjf|rc+wiQQt3&B-J)bBWy8jOg;3tcWZo6E{993CN%gmo|sLoktr5+~5j;zFQcr~5R zObO?KSH~s9g-sNf{u~aa8%OJl^?Jv{?cvut2-l0A3-BoVyk5*@J*nEdi{#b zEa?+*j7tt}pHnIhIkuWZka|^><=|>`v&F5vDN`mDi;81Q^%U1R{W*VKZFU|kzcm1uV8C+HS2#a;g{3#Se2Eep1`l?fUug zrsP`8Kp^p{=den3gUl=^3gWW=&!DZ0`j29S{lOD4n-`#PSW_oFjaDR$|BZOzHN2** zG9{|iEbJ+BhUN>L+oVHz zV@9FWf8-M#_#;;8@b*0TkT>FV4!SQ=^!Y`ieDuGX zf|JbDjEJo~Un73rDrHO{xT7UZB{Ly&)<bo*lrC-&!|si z@?QOn+c({34H{erCIk_YSxWM^S>{?k80wTJYi{@r# zZe)^O8_clPg-W74yS|37X>l!{=NLOkjV3L)Gq*$Te1nYXvj^;~xtO$QyFztQS;)Cq z3e<8tjn3x<8F>lP$}+QbNHyz4x)f1^i$64}zeN+n=)Kn+92*_;TLUsJ>6Pq?sCF^N z7RAKGfE{`0goD!EI_E2L&6}idDbku(Hc1}^F$7F*conYRL35!oBsQxg?qwdDy!0Fk z@s`NLuNj#FL)ZH~(xj0w%DYvab+sQ@WlQt9)S=anclmT0CaGm!2EdkUnbEs6^oH1< z4-GROSH_5^dwqP4_uno~a+l$W)@spzCrY7`Z?eEGal8$C?8VN<=U;7C>2d*8ppe8= zRTN}>)l&LZ-x@u@NKc!v>DZ+`x2&7Wae^AQWb7B5P@&zfXJ{PqP6-Rdw$WR_spJ^r zLHAfb+;ui|?nHH{9c4051BKxPkKzOp0jHV#Db@c2$*|ZBbMlS}KN!`px<#41%u2&5 zGKz$Oyw6>=W#wREs^Hx{fy!!5L!Kg1VruY%IbHOldseUn%@P)3OAj5V^7l~v1&il` z#1VceffThqEoJFpIJtuR{%M*@rli!Vmx!}@I3$4AZ^EGixh3s)LbECVx(pPF$dS&= zOCrpQ6qw}=$V)aXR%9$8)jY_a_#G^TPXi&VDP&s08`k>HWN z?2Deyj$XK&` zLe;O?P}1(T_moAKrdyQFt-Fh?2IZQeCN@prbEf$iwf^?}NFt8G;USej86F)4=kzc_ox z@XEHXO*l3xm5QBIP_b>>wr$&H#oDoLRct2}+fMD+?wo%6y#4jp&)a>T{XW z1NWY5%rWk9qrxihBpRFZQZ+6K?{Kxw0a1^;4lD!T9%d}1(V2f+pMIV8BFNIBW}45K zxzZ3Jg9w2ruvx3s44SLbu3-eDDhL%(rOaa(uGdL*d6%e5bb*tuRJdG#c&L5rHN+s)?sXVkL8b zC#U|#&T$BlFbRsuqnh>d4q4wuN9AxidTKuY`9lxy$qzOuc6^o@ zJG&;yXn8^dbLeXstt?yI)JJzm@JEMJ83+hQ-e6beQvL}C{nI-fnVV5d6LmL@nC^C|i#Bs>V%|I9W?U*N9Cc-6>g>)NXy z1hW*~+5mRm9V`OoX0P8V0ep_%$Wn#?%qEPSwU%OUia@rPXs!#FsMuS082@x9UKNKt zr3-w`>8j_O5zgimkD-{SO>80>s&`A=e5WjW;}Ohg?2&2H`-;0M7R|Bgd=iqhh-{V5 z&A?N+5r+oWfg(+E~wwYBu$q*zq^S1nvSf ze)ZI6uhPJD{o*}5OmM`5JD%TBmZzJUfBLGP8`yYj*-RYE)ZwoszjG3_??1h-!0L4n zFgaY|o?W80S=J@}Pp99Et=w}EgIKs{$kxHrAw`92Z4$;>t?BW#!t+G!Sk(&_UVKXD z(y-h!a-6ZRG-6w=#+ai|=AdX1Xl8!Z`m7jt`E+`f4LlNAF{x?vhdbWrR#FC4SzWPY>6WDFU{i>0f0%)X?hA-=#(y za(cSG@kh6$RqlMGOv)>!Yc2*XcGPOe`Ym0)Kb5hpbz4?d9SW4Q2+O!uHC1=}F2o2V|5Z{BJoAr|u;r<7@{MWzz`-(jk#DIebRpY+;-#Na&P4-_eWeX4LpuLG+ z>8SW`$b|T_)7pik;8H>S*EjxOuk_du5ypI?Ct^#;fjy*LRAIvRu2s!P>;CB~{4ne2f2n{*{h;vQkb(QlbKqz=RR6Os<^QLo|LtA@YyVvZ zkQD&se=ei-m*;5Zkq-YGfuIGmoH=>8=4klO?PDJ69}^?v)NYcKj9N=aP2K1Saeem> zo#|b`zaz0r9htK)>v&Qhx=+_FC zK>GTR7ut6H`sv1Jg;YoA|5zqhdN_!lsd>u|LEa~T@qPxnGMNC^60 z0qg&W%~^lpEZ50c+duS=&s&?Ld`3$%Od{@gh=00J&)N6CFjGQpism27@JD?Hf4xZB zKHlHi@Di75JA5`j-a#e+(U;sM!CHs|9dn`>$A6zsIuH z)>%BeEc`J|giTgak&m;nwl=V{L&nj<-@3K8i96s?DJ^I0b#8dLyrazCvfg#M1h_Ug zzebx*RaO@qN@jY!K`l8RosrRv^%d5UGF~hCxw=jc7g=1BwsLwRMo=u?c`S{{Mt~>P zW@YI#bi?13yV~CQ4{J1uGefaG#XV=1nNflt$F`X}9y~3XjKLvY*1|HAZ2p#BEM;TkX9y_k zErqBavb;$p@xnoo->WnQn(v9u#m8W(Z7H1&2^A!o%hCL*5*4%TM$FdQXh_;z7}9jw ztK58(gso{4%6MU}Sa(C^P+w;2m+y=NFHLyp!^T>5u+6{Kjp;bOQNF=Mjbn4*F1soo zt!V=-%2JDIJDgVf0I0sTLONQ>B7IA7C8*3&a>QL(bC4~&vXz`KJkRuwTebGMXg~3h z$tpRqFqQ)cyE}?^+1nAl5kIR+5;1ZPmnC?}=}q_LhA(RO~vv?_k+j)>bwfKWUTEr2@U7 zKf(HdZ?&c76sNcsu$*Dd;sj5sCj(;1Q+dMGL}u&AzT}VISvp!y>Ujl~vvxbN*eRAM zboX{B`hEVnN|j7GaIe0Rr6UQe7hyk`J{psL~)ILw=e|u{=EQOG+ zhzAy>3~Q~MbMREx3GsrqNTK(4Iu&RJVn#+Mgas5haCJ6z{+z*z>CBZyGHPQ{fTQej~$X#l>@Ba|W<}^2N7C!E41vRL=hy z8KdHdJUHCm5A7lJb8l_WKPjycQdU*=kB&yz$4KcJ>P6Y)1-*Q@$ybm_=R0M+Yp0l) z6fm_Va-Ooiyt)Z!sK$aOM~p)|I4EUav!~|j+kry5_{F5PL~_?&y`5H8np^)8F`0t& z74PYX_bE^EkRv9QE%y|VeJV+}Vn$0P`Cw#xO4v7E0jIx22;;S*7Cf=5^%hoYdoB~ zz&z-;C6KVq(zEoEF+Gp7(zF+~ll_HOh;Z|a`c zT5)f?gN`vM>1vAl{OMshvNtN>Z@{o3<@(V;DGM_mgOF@q})gL9?H(QuddpS zb|7u#DD8w{k^C~xT@Uw^ZXrHCBFf=+Axn#YdG`<&7dDO{lC?9d2-Hva))158cHFE{0#oxm=9zgm1%uPdjZ z5|wmhgp(ZeAZZ9I@uPn&9R{$IWvnrCd{Y!FF3u~g49u3r^X3Tke#94h8My zIlAgP;9q&})47<}&o1)`#eM-?B;j}@H8hxP&aQ`zcWHuIb}US^pSeudG|41?4#XWD zxVxo}rDL%Mpe$2j;(MA+5nxw+r3m_Z`Wx6mth~bBBX=TV` zU3xqy>W|HD;+3ukWe6Uwb6*Md66q9ofqE=!#mHQ$7mQv^sgz& zJkV^tEH7u?1#Tv*cfNfE214()#L49D&DY&)R_zER*L@QSNngIYg91KZQDanwe4ft) zOi|0_@ZaL{!83(9hqUBPp{NQz;XPXNG-tDiDPqrLuJwhj$ApOJw`v5{ea)A_PmM1Y^ohv&PAUt7btyP;L*nP2 zrRCN=OvPCz8hBk(*tVJNO{3p16WOewo9CYFtFz(Cy8fu`E&7uwog+nQ7Ww`)dVbt* z6<#REJB1wCXb)r8g5R^bLZ^9GLE8PltlwNJ9e>@o)gf*hZDBz--uZp)R(G!LX_F%7 zWCt4#jCz7c&y7h%l^$$kZ@E(Pc725#v+%aGy*aaI%l6f02IJ0>7Z!Tm+0ZkZJoI@* z;2Z+nhU2m$MVZBhW>!d@u~YpR6Pg{ht23Ttcyz`JF~d)TKqfzUwLV8RCXf+#W=|&E znYXoGug9xXYmgs$iHz7A61fPt-|*bo^fJp!?q&mcAXAFB`yX6T54Qn2a{SGL^4z8e zZyAEN<z@a zrt5}5qht>g)nIe8bG~50Z!kp^%T2{GF&usZN9@!1@#wjy3FP`Z=}!fP_q&JPeoXL_ z;u_i+>E1a_gZCE&9qO=aRsXY29Sl|nh}`r!BHQo~)VnF&T)C@iN-FW$vaNT$35FsZ zruxi;Da>&w$IVfm6%I0E^8&yGtq79+W>4+Wa1T^_nqoJ+s_P@a`I2Ai7WjbGE%0495`O~BoZnH9u+dMF@My~*iD5oTgyK~z1E zJ#b3bEvmiilEkRr6Z*hHep-hgG^9VYVT5uh<$8y!4LKiQf@ITgS$%cgie8bcB+8Mt z&{=DB5ue6kM6LOR&HSd;dpFpW_V(zw!4@v@V;;;Kfw!~na)#r0X0at(dSax*6I-vV z8#^_cHSHs6E8neD6Gy$o+V;I_62W;a7uVHr;{n>BCxymNduFs5Zb$ZX~2XOQk=UTI;V0opb;QAWHJ+Qh7kyf z%oBNT6V&KX5o%jhhhX8Lpx*5OzjVPl;SE$f#xXT#EiJh>tb7cJa1HICI;t~0pae)v zb`!xSPj7iV@2N9Wf1PSJVQ+mB@^0cn4=I}PHz70KGB9GWIyHV|NK@yCQ~{}Lt4APr ztL)1lnCdU^@uLQCMC_^f^-urA_}t>xex_QAUcdgm*;gk4mCcQrsGS&>9_+Q_>xcf6 zd_@7zIn6JO&{+~2&e)}zM6|PvME$1EVdx~ejfV|GM~-*hhWYNW#1>B1Jh)~p1EnxfG!Yn#`q-aorC@kH1Q2M@ze&VmbkWr0kpTgmt;4@G`3^5MDQrT!rEjj54)`y8hSvI+%Djme&u z1Emf_w(mCefuY&0LbDBPJkmUEIjTihFLa+0yW|!MLc#TkS@^q-Z2utqOgJUeww)7H z|CY|MQ`Yb=7b}PFc@SBl(flh1iQ$s!F)CWbm&-gpC#}wk76BuZ9icQi-63bgP7V(Z z4}H@`tBfnRvsJEf=~%-e^J+`gMXV0m8I;i!bQJum3b3o0h!h5#ZDS}E6r=qi1k>9i z>EjdH4gyYl6UXs(qZ9?W^>pw4q&eOZw}Fz<*lf5A(8xO#Hw-Y*z}O!=ngY{6SX38_ zp#zH7rz)1#43oI9p(l&y-5^5XOX1^3hK#^X2C6bvF(D0&c*T~ zj3fJ}Wm)bwdy^wn$OG$UTD_r5TkezN?jh@zI2xEEDMh$QfD;>V1uDB9Hzki2)IovDsG2HtTf27Z^ z%z9t_h`6cYmOko6>wMeZyV>94rAw(;L%K6xCfwTg28;UAwC626`A2!kijuYK23-|* zTRQ~H3rY?-IP7^>QjqZ|TjG;>4X?TQ$l+ptQxUSjhFZrr7d@Swjs#vYv%F?M;E?U>`XJfS48$os!rA$=2PtGv z54^6~T`dyU?)(~uTRrUi_S@K<-m-s$V`9jp#>gxX50?``6j{4R0ZOLxHU*y7%Zp)M zMU=b(G3loDjG#M~W|#?u+nlp+T}1WHMCIj^6Zx8Piioks=%P68a%M>^tqf35)x;H( zq2z@LIY_-K2zNUS`ZLKL>*+A{lQ$~+H-mf~;U488 z^#|DSG#U|oAu7OH(SE;&_jrAc@U{)|(%Ddy^Pq;2vBgF^_Trwjw`y8Y_>rY*Ov#Pt zT6}}K91|ijeT-wLskqlVImt}Hmew)74alI6B&nWEba9RUdR9nRGC;SH;&ECRG%B%wUb;`_iWk;aCUw z>IPQRajOR4`Gsdk9n<4QHyMaS;0C)|*iCYlk3CCcsAoKmVaxOrQ-F-}lsG0%r@l4iCZoU=HumbxmRdXEocy$aKF* zuXnY`j)c7Xn~-UAf3!pAwjgxIowc@1I^~EZ7w;B!5S*9XMC%8`L=G#;yDid)IA`1f zm74zK>cu4yiHKGhPgqyg8#8x#i6JE7iS6dQ`(^r8u3s2`Ri%kfMRr3wMu)Zv2R9R~ zd@si{EPgzl&dNl)AMi{>>t_`I#In!1| zKhtjK52er9-?N@$XIdF3%tU+Xd|z;)yDLcWuvjU?dBV`7Hv2+1j_MA^>1Ie@QJl>W zs@L^3Y5R+u_z0=^zHWMY%0@$~cL&Dzl1I&W{_a#~FXjq@D{sii zY3YQdc-)~_*Rg<^A40S_!Zp&sEq>m1b-$b$ zrf+l-pK~ZaVlRM>;%H@m>eo#1Xda9X^NR|*I1f4&_v?6%GkLTf)z_~8M{un zyu;A@?Vk{lcazgw8j!WULoL;s^Q?hxtlK;GusJX@!R>s;2Zzjag#hJ@B%&gLBny1P z_xm{F65pu9wc>0X{+KwQrmmrNKWt)?FSg8OTekk`^hT`fwU@&#CzQwqj-<@&JAZK1lc)enJ=lqri zkeqUw!5A1aBHvxiAenO9d~SKW9$j%3eJ&wAk&mN@@H}X%js zdIhGuq2y%FOV8d5OX-{`{&%<6#u7YJOXj(BRAW6y<0-q^mJGzuGJSTwcT&L~TqH38 ztTo-Y*s%i_{p|!VxqB7U{r!1xOMAQaJMLhsaW5oCMEwR7af2i`*@J@v>V>lgRSzx# zDh{l&c8|C@-cAWO^B(S3d3DU|Y%~(9`|#=~vHLeKS1Zl#%ADBrA3L9{zm|#i zBoP+rm@F86zV`x;$$Enq?~jaAgEqq6i{8$N`X;cZKKLI7;Jj-& zP$mA^>fT$p?yU@;t>g4EMMHO&cTvMvn>ZKSJ#7{AT#tnG{x|QE_s3Okjc_SH#5n`q z7V$$z3s_9~o*~fXPNC(d?jq~IUfqe+>_%M})*d99U~(D$5Y&;GaPJIcdZ8uszO}pR zri%`%d4daa_6-QGz`)#B1$*FEOY@+M-mHha@N+@HdzKxv%TAjp=e#TbHQx^9VNRq! z?$M&wuIqk1{n2A+%FMPYIk#K;?58*QYo9pyu}(3jyt3|%UUCeN;eK;i9mRy6_ChCQ zX>%>OO02-)L8od*m`6hs5aJ@WIh-z)e#~izf}cJM50$RQJVZ4e!ruU447Ev(zVhRL z9H(qgk>xTdppHl)klUKK+aHJyBW$Ga@R?JEA-T1hlADtn7 zmu~CZ1@&BhHj7_Lzs8W>C&*R~hcv<-qxU3`Pq;?B?|>*eN`2`&ek{3Y$LZCjW!Ajn zIfL4c4WD#NP#Rz}fvcrS~^57DS`(FUr(cI|$x8n7n9gB-s1 z4w6xjxIAShHY4 zAfnfPy z(|>%v~r5`Fw`FrK=Mk_dL>s#N>9d3E_<{y8sUU#QXpdU*0KD$IeI6}$_Dhv82b^zRm- z+CyfBIg53lOZN5Dr0@+4+5&o#I3C@V=Y%38A22X%_R+^+q2J;ArBXnw%&5h9p5G;2 z7^a-^LX2MoI1J|58}`_pie7CrD(d z3axj@as^dDP5^=+oN0Fl$Oj1KZ46!~>wcvtIwMm!)F;&-#?LGx7|u5XTy8uMJ0+tjrr=&Re9hwYE=6cBRa^R*-u(JX zmF^X^9N&OrVy0E3v=A(kbed|KRdEQH7-~Q;Fr&-^XJIl zv#9X|-cjsc45V~}q6Q%*32`2dsHy)lVe8<$9j^44D7?2_wW}Kq(Kti8mztss?486R z;t4f)y=>pk17IZu$ccMJ=dhp-Bb^D#s)jCh%l8X+YO2eRalpl4F(4O+-WD+Fm7MTg z{YXTR@|nU(n7t&lxx*08%VquQx51~IwKdf$58y7 zp~oQ!j0ttgtLfN>Vee3QR)7}?*a^?T-9hKpXJNxb9^`x;szvsCZVDhb{m+};h7vl% zxd2Slo&>ukLRDi08{X?C1T#H7y@^Z)(5mG*#Yv*cDX3d)UJN(9ZvSN;*l@Wiks3lX z)znhO7T(~wwz5@w_B!b@a63=3F};l~%JHbi%DQB!#lFzD6*C&$U|j>GKwKtq_GQU|3g zp?MW;wqhcd2AE#TQn9LqN4*bSB6}6j8PPbwY!$Jr(r2WK`)_sRExX{VVfM%uu@+8d zvlf%+#MDNVYjR)YAE@s0A9lG1ln_HRWV|N)`%7imCuf%es2jMM8Pm?7)ZYZER+edp z!w!;E;Bm9sxxo8u^gl_0o62TyhPyq$Gvx+|+((?b?~cD!-Et$x%fP?{pns-VOyCJe ziT*ygI%fter;9-yuSQI#2ZukL4#@08QH$kdIgZNja`Ldi&qvY`35j7M=DZbLv)8K< zi$C?)8>wuU-MA1GufU70lpXGcw>UnYgJNZt&ieH=O7Xr7xaQ73|lDnyhOj5j(|eQqLJEm z03^@3TOFo3I=beOqze#O0)7NRd1;=OK1?7WjY>)i_?o$WqsLi1*6-i6wWY_2iub+} zd-ZveO%Vo$ANjHnRIRj29_hEq*G;D-F3M59?+R3Iw!?F#E&_GD=r}{3M%b+oTCbeOL^+zGnE@}g z1a=w>FZhfwW2Mh^D=YoU4;1954DF{cNsehc1$iyMt;2F{eP+_rQ!=PMbaAi`H!z)l zm+6o{uK30^?t_a@;(^uh$4Z&0rR_SZWAmX_@u(xM<^^w{c@XR`D|aV825lMKH7mhb zOiOPJqMmFg0c(cx%RjU^I3?NX6na^8ZSMg&wa88)A?Wsp+qprTlQQikKe9@i;h@|G za93i8H1PX)|M_<}%`O+P!Oe-YQK`2~Aa^`{qd4*;YEFKWeOH8=~(9`fn*1XT6- zl2T0iv*s|&@Wu^hjRDwh=vHiR;kS%j(K+2;-Z(=nI`f;5lRlWG2)(zBJC&0j8TP5bBYV*unyYxy;PTAHrV6fKV`LbS&xZa9gpT^eurI=x6kS&~4zw=~5 zQiMTju5^Nybz0a}6&v^18MsIlh|=zIq^$68zR8@_7#n=DjBm`;K4E5V+Q6UZItVLZ*sL3r&O7~%w_V9c$0M2{; zE^`3^w8E$!mgUA$8yLBz`EaWC)$m(5cMoBEG?Z4}bHk*v0!Fdd_1C%Zx_*d+P%y6)2z}U!$MN`ISqc?8%afQ6ThZkvhs3glo|osl9P3oNs7$HAzWamOr_tbBBwd>+$;i#Oh z7hy9-V#MK~xRm26SQ~gJjg2J%CX)#>}B(lcK`5(FB z?h3Z6Lk@@I)~o&?;Ivt{ofz%0}>gaw`?DA+i%8Z%x-yF-FEe-Nbs!u;8T#3KNeAP zNkoof+X?*gZPo5bP?vN()V|vPpp7^2wn4!@Ei>V#TsYpvi$nrm;ie|UF5q;BeDsZm zUq0|d9;FVuRu0TK{iuC735@MUpwGP-1Y$+@o*^vzy%MG1T&#dq5axOz*Ip|XL>fGONW;=&7J2&L_@6UD+bH;7tiM%$lpRyvSC?A4jc zM%qm)-CKSXlH;bg_;Odk1LwGcz1v>nz|)RN8{hwBjB!HRKCG7uWT*1^OOqK%3R;#K zJ@m_F>Y9GI8zw^1!aZB6zMnETG1NrQo)c7G+zsA}FK+`7Q6V!y9jPP(8eOAzm z)s6)MGa6K8O2xPt&Xqt;Z|n!YC+cG4vtpW3!S>jpk1m*wr#L!$B2Lk${2Yg^8itlk z0z5{m%Y_pNh|u-0L8amP^%%+xGsLftPg@Gf(1^#(5D$D)BgaMKS63BupZy7_G3S&x z&EH5(l3~tyQ03?{fQ;8W&4q93Gh(-X*9h#Al6#TbtrDNL5zWg3OfU*QUNb z=M#K@qc^-JwY0;7sgqsgK|9Icj(Q4u)Rs+vYkV+GR2F6AsP(0t&#oySvB32|6^tmvPG}KXJtaYo$03^MnHMyY;&Z*amzu`%lwVd&x$gStZ zJ*Zv`M39JaOm#3gE`qHy5h84LzgywPPWSq+L@~Hg9!U2WShJDmo+}lZ6j4x43?OHA z^?c5_jvlgE-+dQ2?-=9ObD^{;<4Gs^ccNq@*v<#qa4<@d z-q;GOw)U-Nua;I3HMcXkNdNrXa`6kA`7eT=;^KCNOX<>NTOOMd`d`7KDmL54Zr*(p zr9J+5Ja+?_oL3c=-|0X`9rss7CTm9wIgNiVT8;(3tS z`z<5FMVFA~k#I^COX!5hx9O=k!`fk6>tz?HO@Taj;~8f6)kID`^!2Q$iYPtEL{T9h zmS&rLbWa6D-2NkdO+Es1o7O2qEcF$Sy%{}T#J6_8!Pql`y|(^85=|2f5ogWK_1|Kh zQ#w0#CadEw!SKG^`MeZPR~h;z*zEGs7^-KqCu1#-x;yo=O9iwo;TH|_HFSi(Fh4ON z%1@ZrV3h5AF>3Y;*O?#A*TQ6*G&s`*2jdA)Mg?E$)VQ;XJA)5X-8bqZtma_JT^Hu~ zCjc;t_Z$Ytl_D@$j95!nr@1J@A(kN74z^hpS`_T0 zq`(eZjPgx5n&&GG254G0AG0_k6iZ{*m@B3f<_t{LJnS3~ez)Fg4!Ttm2)}g+t?ES0 zqExzEWAn-jJjhdA>))6|gEVV47;oOIy@KnV_7#ai5~5K|jM6w4Wv72$iuq;GTsR7> zU-!F(ywt(>m#sSF#6HwgcUvetTM(!!Le(^iu~htXveJmVK`k#^3BHc`WV-^#gE89Y zvC=oO{2GmG8gbVTfKJDf${Ii2wFbUf(;#U}3}q*O@RzPtwAwj;SJo60MotgxcDslX zG)>cJz-yxysw`Q_{+kP+Y=6wb+lLjkYL#%E&5gCOuwqP_hBP8`#Q)wfG?)iiYWBTl zjpTR!QbF@0T-UNII#e=Mu`IU8T08yy2GA#?bkK?v;evTA3<*05nk+_u-sK^(hSYF; zvBu%lnpm|)GwkP240`&3sIwU~@ZyQ#k6YYW%G0OSmG+)diKxaPD~HDcv}I^f!xPj3 z0|}qiq-(IE9H*<>0Qfs6=1MNM8t<2vt|ecK88(68W~1-P<{FZeeFa{Iy+X)&eyx1i z9KN_8j^M7@d?ATADg9eCAaWBYHSBT|b@Ovi-e(iG-dPIJ)5i{l{ZYz|14;R&m;MF7El_8)w-s^J}6bz}JlDcY; z(y;3$C$7p0jR{F{5g=rE6BG}NDvkd_i!h7Re_QON7~>06(Yx=Xic9QEUY*(j7uUOR z1G!pqygYd!*m++;MFAd!r~G0B@QB!qzom{I>(hktlV*%1SVPF-gQmR?+#IuU5ZpbZ zv$rcIj+JADb-N>{dnCgEd0-yo&PRD!k&B-4yPHE>Y6yT>!MVju{I-WMFz7H)R`O;m zDWEK|y*>;!&&~*NH%D$E?2QjNpA&$=hU3nKm94*I=NFT24d;q8gbzEIYWrOj4Y-tIXgOcJPm=r2^}wp8)upD?~un0@%!Yla?RysubxsWF{3DO0A`PYex?m^RcTaJ zyzG%?$n0hcUJ;9hC^)g<<*OegVfRlbVR%GtZtc8^i9zU0x7=TYF;hpwGjkQmQ}xb^ zphPqV$a;Em_Zx+_teMDfIB~agT*8%ZPs%O#PIv?=Nh(o!0oe6Px3%cE`9L#H zETOq{60xIGq@DAiLO5Z-6~f;-A)Vz3fKCQXy) z2zC<0nFW2wp+~m6#|uh`#7>F9DVtjn9Z71q|E=N<5z`BQBbpoEJ9rQRKut&<46ve; zQ-cna`@L(vSwY@5q`}Wk8t_6-4S1tB1yUOpI?O_J%_wQem8l z5DJ;v6O&_YIhxD}*Xad#<+9|hmmV(r=6Ji`o68t9C`n6zaSdkl`z zaUt&bKA^4cO?C&W_uwEaQP*NH!=$ySEX6f`nXK_L_u|(Hg%SpGrZ3_RVVc@=e%1#2 z;Ts!=nwh>TRK6OMU?X5pq`MOQ(mFI6e92Uh*M=k+rrNsHb+>=_M$pGefqHVSw0p-$ z|3TDgYS5gDKehA5U}H{KPeq>=coRTz%F0zurvz6gjLr== zxb8ujyJOpcnGCO|oJUMS{7g}*UdTBvBQ``TSZQ9@Vw&wjvrDN0kHLN){E~q^|0ui; z69unSf8O$Qx-ULYt|fKvB2(M44=4Vdq0j`#9(SDJYJTUYx#t);=fJmOn7m1|jHL*E z7PPw;@ARxAobN+Trkp8C4*E-~#u%#lLk)6H6V`*jk_mnelElek_Nn~$GSAN6Y=^@4`YO_?TJ{pR|rd*gu?EXh|HIq09xGKR5Bs_U8PBc zuS^WQtZ_@b4jV(NiXm_%H3*!>!Tb+7yElUz5Y+5~P}=KQA)1J%EAr-drb|Dt`9O=4 zG^Wt2YOr@h>uU3~1}o_>Vg=Y9dUx4ghyV4adu-iF{b}ZfJy|hEmqCfaXOSZKsWm^u zdEEg6>649P&;6o`EziXlkIC>?Z+1Y~qO+#gqpOl`{znz9WIu+L>TXXF*o69{)x@8R zFz`&ip_!W%+zZXJ17NU3R8IdqXrX+n&DmCYR-gyzQ6GB8(_#sQt@Cj33%bE=DX252 zW&tHR&P%LM2+8mRVcZS>G~X+z&nupc0G6@;B@)bz)NRzfwdu=hqZgA4T@=IBsGh!j zA@&-tR>1h^3W7MHN>@Ab(3BTgBD8Z*!F*!ppZapD?$9$Uw_IhrIF5%HvC7(K z@Qmvq;cZn2>4sjd?$oXS1l_+j#qwXaf1$a!a9Rqp{I1k*zVbpLU6m#`f^WbE3ceZi zAG;c8x71n_$^2l_9V;tXwm_A|bpX)+5)1RBilV#GK`6wm7;B4e;oAtUU}1GPo>B_m z#WooJbFsahEDbRbD;OS!4QtjlK%fQ`vQWu-+TnY8aGlyr<9_(zElHUtK?21uRX4tx z0mXic9i{gE2$d2Y5!j$PS)W^-#B)b$aeIS!9^etj8~TJSU10D)#*&{4bh(5ZK1N}v zt+yRKuv$W!Gl2f0(WO!ZseUy>E>6jm?-pl~#z%>e>30PUW-<@K(?J*l~Mcjn`nj{EA;2NQn{c}IbV-tq9w{po1@%g#l> z`kt7>4%8(sX9>LR$C& zUga`MBWDG@n=U}2S&=ZAZLqi#W>M_cmBY<~S6O9La`6+qZAF-qD8tVoWq+&j+`5*qK0oz27)1 zHT$4k(-1=lxf%&w(P*C6X)ZQZmZy(tod3~r;?(F*W3d)m*X=~)W3J>xOnEx#P)HIo z^wOwQ;*c_BlC%9K8h3`E`POsO+1j;%hU z?;ImY2TcW7X+g|v0rDHL6r6Ff&1=bdO#6_mir*%+sq^o>+!A$`LN6X#6JHAM)!Tje zUOKwu`MTm&tIbDgKxNTd-sW&()XW&L*gZqIGddPT0KsU=%^AU7(LU!X_{#s1VJP-$ zUv}wJmexlYY4b+tei{~4t{}d=edT~@EFbXl^M#jaD}p)a`-0z$$nLGrp?by+i-!ZC z=5)dwZS8GlwcRXGZ`7xS3jJ@4y<>EyQP-_msn~X&*tTs{oQiGRwr!hLv27bqoQiE5 zC-3R*Grq5ToF3=z{de!R_P*D?=A5tT4<4z;KkN$-RygNX|0GQF%WqR0CV7Gog9HA7gGsCmi24xu77T+|PZp($mI7t8+ULSpLh#o1@1`Mu?)OqV5Pvlw*X9AKG z+B}Q$Y9Kr!%#KChz*fZ$07q2-IdXwoWJNz7JWwM@gW2tbboMi3)AbL0r_fL$fgB+g zFVy5)%StMd{<#O^ko@MC!mqxWJni6}pjQ%NZX6Qw>0Xu|A9%!J&LYaEpKM6Vm6w8{D)yuH&7deGSZkM94!*N^$4u4&S+&%-` z&8F~^7hj2+%}8;W>Gu|TK5!f?hH}>GY3n+~@>8R6XxMXv0;WNd4(SGEX678Y$_Wa+ z9{RV(l&_=oK5^>!0_T!_LOj#V6eb+12ZmpYgqHokvn)o`Fry4Ou910}_#mLeTn$F5 zMHnq}<{0A72`X-Y4!W_)*-EPnu0O6_305Gnvm6cvb@9g0oVx}X8{H@yIY^0Z`Km+M zV{Zfh7Mp(Js8!?6J$>dC`Q^j&Kd)0XGziaPhmAKkoduNj)_JggDhgnW_u?U8kSWSVdYWcJjS$S0z5BjjU%GA-0 z1j1UC1;7P%WV{i4m8 zIQ!JGuB;m261h&D^-JhkLoL5P8z=I}m9XfY>GWx}UwR%XL(nt>I~W3u4S{X*p9O1V za1p4BIQD9_wKH>vdzii)1D$VPs+4q-a7h1eSw3Y3(|e0!1#(Ol00sFt)q>FbM7?r_ zMYP#cBoqeYmS&s%(z4plF-KP2qcHplBE|M+L{PpZ*}*ZDr5?BJ$b1?`QCt~i>4C?CLit@YvV~rtEMx3FquxZQyQtMh2uySmDkXXsGX_qW;L3ouJiH^_9m<) zC$+kg0oXt9G5~Ks+ASs=9#+ll$@nmbSd~1u7;#TRqssoWY*}s16_@~tNIr=zE3HrL z5yH}>(e!Z<46_Y~a|rlmD+*hTG9vk_+X`}peosa=-VG)(dP9V0BB8!bxm zwtuGpV*E~OUygO@_hwOmZTiCqjw`SZHf&-rFY;uh-$)?M;1dH|yqDFs-{NlVsK;v7 zu{*X!C!u)ipu#@KB`vMr)jZAP zdvLIyfA0zJa!b4;OXu07QJOtBfa)_)WKViSd#cnhCn?b&=ER4C zVg7IuWMW)^wJnviUZ`X(fD;FH3I>_Tu0wZ}kn_icrb48KB;ob&s{4*CI#w5p&5i0X z_6!=bs#Jsz69KJ&#forD_c&b&>c;g_Fsj-hGRS9mj;7C{_X?(+vo$mO>$BKDL_o7geHwz3`=zTDttwtcBbjnUt;E|vnyca9)dr;0tB7Z*;+i-0VG*1}4;Is+rIi&)3A7Ga;9C8ScXo%| z+;mM?J?y#}ELzAXdvMa#WpA>&xVdIwAlBAG+-ms>U6ypq%+U%3LH-1s%7megBf|Wo z%idc|H8y-p0E5^pXSBE%efHvZgVF4SP91ezQY?GxO`~yOt%EWEy&S23Gg8Er|2Q9a zd=5eAJLkIFw&jaanV2CuJ0{!T5&+>p4>}^2D9ooskj+0$Z;j%kw+xhvrb@MVjz{vPULr4iFBoqJy zTU0V@{OM4(H~T9mvzz2?AeMy>b~nlUlkoE~t!En`exJI`-YW>&m4A*N#q6WZR;}CJ z>(oNrE5pes8>$g9pVo8q4Yp^}y(wIxMGVRF#^MXvnwL{5G21lBAza@+7N;h2Y-o^I zX*XiK=T6r>K8e3Le#lRvt_Tp#0q zJ@h+k&M#umqD?{US3j|7pBJa40?8Vl+da&6;ZiPcI}=a;aAvGhY*8uuvM4gNPi~}9erACE9I8=4y{)jfHw;XM zhXr>iFb!Bn_lqxTKhja1i#tAnPysrWg~%qeW}iG~_(;!MFe-+GoR1S0l*H%cyt0s` zEc9Xm^z$=!T$|L}om8`Y#2ihdzP{X+xOciNMbi_qdo^%b*AH;^s9n&_+>k7$WpzfP zRj#HXHRYLbZ4NEFo~>Y<;{@E*%3+PCb#^5t ztmAzn=6~6xF;?l=ODN0ji>Ksn#i4dc;cmd;Zu#Kt+TOW{A)G#2gljfpOUyXokg2MI zwzriqGklE*QvbXxYyHzOm@nmYvvI#8t=a~@USzM@oL0g)8VSNpELql>j}lY(YyI#O zX3@F4b4-rzD-LmS>_A{{@_wa5RRNP%b;I zm54doNhY=n!sODj{rAh$IuYSoRDL{WpYmeLgE6h8X@`XtNv+N4YT6PewkRA)!R1O( zlRdxssLKOpUd_Qu$?(eRlaXkiVB_hTBtUD-cMkp&q)_i(9C(}ygYFh}j(Jt+uZhkx zQsN{L@7UyTq{fO32GKWHZG;4zLXF@u0L-UnbbG!zth|Wr-jta!5dtyVBT-CvotQ?kmEul_S!1rNY=f{6(GU$@ooZvoR#E9MtS{MMwr*}q3k zhUeB|cobBHNECI@rD2n9UfNq6nwEYb=7p78h-k`i6D>~8pL2L~dTU!JWO*mF>**Dw zYal+b{iYm?9vR>23ajUa6_qmsV{9N6WiMsKfgSLhE-8Ax*y<6@ae>Ose;oKR{~j?l z#}gmLO#__QFI;dC((%pVHZEpk`(uuIf|XE%cb)Vul5PI-sIW-Fut(hN-GH2hkSnHW zfllIr8!vCo;djrBP@1Ov<-|#@ATLd5_3f(otzfqZNnKw4>TMq2EiDWkVXcR2u(w_x zS#gdH6}F*#bwwm>s7v!BMSXuOnP2}P7Poo2gYm%IHB5czToXbJJezZ|u7o&oOz0E71}jZDRg*j z+1BakBNj+@gO32x)H)J~C-#%xhzV@0O2uioF1_wH=tXe`-luyM(fLt^dzK5IKy~_5 zb3b1tvjmv$^U0C2WvD!-L>! zM%YIiD)*y_2T92Z5wZ%{EF8;(B#gL4TqzTE-MD;P5V&@Ic9;eRzx69SuoeyTZ&SgM}@>~DnXK7tsM(n}+9o!F1C!{60uIr98OcH-h zA9h+E$ScB36sfqG$(77g&t#n`!z>IrK-0XyIJudsYj%=`m39VTuuW^p_uc1$-uO^g z0yN0lvb-P-?Fs&~_LkCF+zRfJq8^w*G#zdJbWDUby&V_X2Am*X* zY(9mtpXm6Vwt*S6Gm0aGfmjFK9Q!BHYNhUuCYymVf9Cm!(S);t>7`>MARTU3(tIt>bdg)WfgF5hZ$+F1Vbm?QM+UF6lB{Ye1+BG6A=w0I7#R7Dl{nno zBG&0m_~$2MlO3MLIod%FkTXT;0lc!4msW&PgNx6c)>~chZE&_ucbRZ@X;~l|i_3Sl zRtH!1>CB)$WwgUZERYVm($ucgM4-g0B9>>f`F6$~HiD{-Y)4YSc4OR>fh>=Ve;;0b z>8L}r6RDQV-;unGa_#?o=(TUkblzUWcfSO9u-U|IkU^UfNx4ZY0rC2P*1+ z#m>ym77kxa;0ve!Jq!)CHd7GwA8ty9eLh6CxIMOBD99ltotKVZ z>$%5oZ$hG2Y%Jp?8KB+a3Me>K?5y}3-SWr@7oKD&;-WR`_Db}ziY#ltWrw)sOX<-;O-6 z5Xr}CMKdJMmd|0 z^vs8aNLYha4xeS=HvxX#XbIC6!faj38K};8;IGPO>+g-D8!){4_u8RR*a5?(H!;Ar z3v%ZqPnM7^kJ4|AjuV0#^1gpQBk6kXRfn_P3H_d};<~X#P^h15P`QVa((*^@B%TmS zO$70;=&qC@sg-tH5=-o69@|S^xC;>g^d~+0lP%u~qy50LS?u;+N;D4rn6Rq@A93j~ zp?FBK%FB!?6e1sS!~Sqm@3tdaS9(9hHq5u)39+g3Wlqwg(`~>KK~!#TgzZJ9E-O8R z*u%)Z#mCJ-H=4D^3&Q;LlbC-L#`0Zae7rZjYpzhbq==8508VglBubUreSSk1XJVlqlM_>>u2{1jrh zQk!+vUpItVE1%KQW-f=X%G0oLk$*@yzNlhqi}W8DA6K3;nlAkse1x?v`fBty5)`1- z;)V6|dvZE|+a)Qc$QDVDgFUT2+M66QK0X1%<*;Edg^z6&!qyZES}0|ka3Ed$=`)gq z;%X^h0)X~w%n71+9fT8C-5{#`ZlL;*e6hjLv#w=%HsZHuc8 zD;|a6Dk;_Jyav458EuQU$rs^4WbF@t04db!Z|AAK>nVz>qHQGtQ?s>#PR5`$_6&s% z2&4OKGAx`!1McL|1ekdRpPLg3R=sj2>s!|7qUkicH$IwXEw37==!=UemLvIXR8wyA zah3!%&A9j#k88I?);hqA-IuY@M*q$E{7d=gI%IL%W<-A{?>*eDppgqz-7mqeCcrlp z6iuf5#$rG0tc?nIk@D*4pCbqD^tw0Uy3bb*-cx?F;Q3%+m>f*|tF~j93qQ=;>Hk`{ zqx}X=Aw#p_6NrqAEcK_h3b;z+Z)JtP8}o>Tf&nd{wvm*$7w3a!MT8zI@cwgG zdP@Q?1LyO-LwIm7v~O-Wys}Y7a;(bOZ-s#6h!@IF&C4Xl=n6Ns!YGua!5vfXLMJx! zR_6F{FSaz27f&EfvaA@9oUaxPwEUt(H4wfvy;OvhV-GB~=L?)x=O<$lh?38HIy_~t zLPf?2I$DfS!X{_ui<4zbh+vunbZRjN{1qD-{p56)j!)|dOyz~c>QXje#>+F~ysC}J zXF${QU6ZJ2m`6cdHC!6KUg(@=)F_cMQ$~9Udp~=BfTYn;n1_H?;QVjggibSb-Kt69 z@(#Kq5-QFyYptX30{87X$Q_Gy_OfE3m06VtBP-SI*$}*7#LNIQOy*<%qq$S@=b*U3GLwTQ?!Q!ja6uSx*CE_*6NlYHzXy=>~D&v3JAj*2QLv$!uka1YB1@yQB*LrDbrkQ}T_~B4c z!_W|b8uvFH=LRcLLv4Vt=YyCeVN=gkwclCHxZORR{dkO47--r9)Uf~VuK6KRkm;7IMi0-(h8cIeaE0A z=YXb`e^2ZG##*+tW;ai;hjgcp8}1w^>>mw(Dk;|#Cf-B47DLw?j#jy+W{G4-M1BL)PPci%mFL1(Dmdb+vkB5wcuXfr>v6CkJNveVFKWqXd)!3_ zaN4Bn87I==Wyr|}@DH?a_r}?r9w=$p1}Wk;%z&cNrpfeL6q6ang^g9ZV;3CDzX=)c z$Q1H_;_Ub zL^j+~6Ka|LOJqg$3AP>m>s9q>X8x-++5-y-GzC|Joa8TYaw=BMav5W@ze=y!4T7s#SzVHTZgKONP#Iwdi2_icirfeVt-V~hZiOr^APDfdsaD5hdFMk*TF z0QABw8U6;`tXS<(lNGf0)3&f}5>}RVRaNRHjLl-d4o@zGqiLPNrbHMp#GJsiTu+4p zn)+@tRsG4P`9xv5dWwz8vv?mi=&=fm?3&_&RwYEpWg+*+6cen48lEGf3v6=jUS?Q! z+Q@dQKN!VBdWY$Wp~=Z;`)I0t{p~!do!7jkF|h$-Y&kN3pEim21#)AEyRRmX^QJLea1 zmqdN$)E$wH$*jd?rGyzK@hN)I7?yg|I?tr|KKd~4ym)rUW#vGeQ#}ZhbhPfYM5Tl& zM;Rjxvm^Ar=|zGD48Ew5LpdYde2nz^Ok1(TjG8*%5u%lOkGuV(C}i~We~-8i!^bWN z*H03CtFV*lt--~U+S90^+ZC7?`2rQWRbpElpL(Iy^355;MhpV}7joWFNU0PEU!f9w}6o;2@C-g-`uKUe&hu_D7u*?OeO+i@KGx8mD zyY3jR-=+udqReKQWN|bcJ|A5%j;ASY7d9>}4XFCXrw8xkCx!B}25heOmUuc^y6ebY z+CYPt+LQ1vG#}ZD$@PRZHL2?zZwV?V!6?mVXgN_gXT@n(#U_M}Vym%0OY%6BV%LsYeLi4_p zC;oKD=ob^9USFj{pASqo&*4SJ@2{+$cP2G8bC9*kv_ew@5?S_|^L}kCU@Zu!WK2-s z!C9FGgM*VyZe`Gd4ZfWIjtx1dSS^cWcHh4_s<|4~(wVW@Q|3pWiUMeil(i#@;Vk;U0t{ai;0|Da>hWaY7m;p<9|h zP+aJ*1jdi-bJGtyV<1|iFho?;D?4IhW6P_nR|oF;8R%}%SCJv2KW)q4Xtc%B? z6)C?ndP2Gs^i!QD)L=DgCf_Z*rCu*AtNTbmy?3b(T#?UfV7d@SgGwzvJ9}IWraBQ` zSnPDEA}Y7f2vJsog8@_7Z-`LbOAYbN6RG z$d4h#-%jK&i)>4g#Tu6ZC zaZg`7J_yG-r0SA!pnZX<^&DF^w_j}glt#JT;|NQSC-Q(7#0Jo|$7Am{2m9w#yC

        a@OR2XUTLQ?!uu!EZLnYP=i62aOPC9N!1n@};hHeqn8&y3*Nd&N*W0!O=i;QTZ1+X5e|IU=2>)j9xvAEqr!k1Q0=op5pbHx~lH7jh0&bnH9`A-%+z>Sng3# z+x@3TX#8VOYxf;rZ_l>Wdlb-G@4Y;1p`&thoDI(eRc`2py4{(c-peTM@TZJGj;%NJ z^uZfHq$&|6drR<~)g}yr>JONVkb}rgMAYTc@ltX7dqKHoT|#gLB$A0Wl;0VdHGj*x zXJ6T#(;LaLA7UbhF_qb*KuzKdV0~lF%;LnMD7-HK+BEQJbU=|dcWcZgpGRal)6;ES zPIy0$GcM7$wheztEc>W>`n@|hfkCx0Du`c@6-o-MhU%ItMr$H6H=~^v(S;5j4$@v0c_+o$J=jR0+^EX#;|Z_0L0=)9UwG+ehl@xS&;kr6x| z8=BFUgVEUm6uH{L{CMvwleR22bgilOKePDtBH+W4T5k`7Qx{cg$^x>0s12!|!7mLI zePPLJ7jpgtpqjV%J+MCut4p?@@t2x@^XS`L#m>op;MYX9FS%+WyDK`g;ajOMvtJRp zK;N8(u0NKL6y(RxQ0 zs|_sAWWj1a(w#eV&vb+gC)npNtNkwY2a&}fCRvt-70^R8$MP>?o-jrB$t!G;Yz9q; zsU|Op9JRDO!x`fybgs@sFZ1wlp_?fRO-f=pPX}?IF-dVcEd|q}I#3L`bwsh6Q!FnO z+FVTOqlODb*$blD?WzKsiX$?)dN@P-L}}N-z;RbPQy?)H)d=MPEey^@ld_g{qN=y8 zddZ6UWAj;*tyC+^O5^E(Ui@rHB$kyFg?p}nzq@u}r0MmM@O4sTh|3%=R+Pal!uESv zQS4M+u|~_L@!FYe5fCH>)|7^Fy}YT2%Bi>n(*PNiOFc1xeNEm-Nw*`Og9ZsPMm19V zzK)3e26Ko+vR)4l;9R0@dE6R&5bdzu)o#pO8Gamq*3gy1<48tok%0;=d{jGFUkYqXG&Ov)N@m8uqJTeYU((B+(yea9mL z>I?sc*H!yOTM*RT1JjwAj#f6FFP0jDF4W78+as8iX9r-0BOX18s%_>MDn-yMUdpLq z5#6SvM7OM$@H@D#y54gv@CCbhsrC8OqnM{WGSMu{<5$ntja^buffLXQymNo z9+yJQwbAA(wxjV51IgN>N}_aeR>w z+mbBDY*mdLlP%HS;e;Uas#c=Mb_OdZKzTf`byAEb1OzN$7<%traZlU)p{BOD`iYmV zt?`M&4aMDj=hfMoJc4ObYJ`h~bJD`H9m#G$wX(Y+zV^n3nw6b<`@<#sO)so_DDuF- zxaY9S9HQmPru2~%g{2(imlOi%#QJb4%X*qRF5{{Q#c@8(oS;@kP8SIt;3B0?mRtW) zTY1QRagr;ie#w+au_1e{<#7<5ZyD-gQiZ;z@{l)|wo^OEqr;2pOsCGado?Yxp4pMV zM)@=-<&VvU89P7qFYEKHztj_R#)MIjR80UbKeVIcT5;Y(`I;AqwKS}P=jWA<-_3YvhX%<7x*M-b2Fz>E$ofC3cXah|$i8sPu5Ov$xMRY4>Vprn8isyZ5brd24vmx;!R`;&SFn zUP&HbD4kaG?r4egkf4#mTy*bPJ#&MJ82fcWnDVAEt2=fc}lzM zckKC{*Ntui-RFH-SxUR932BI{4H3KF3sp0LOo*B}K{PfOSZ=hVEo}k$K(k%EgEQo2 zGjO*ljle&07{6WU;E_Z~(D>#rTT2-YGPe)AGx;Cu&bI3t6}VHw1RCbn$t6iZ?ouKI z39bzO-U&RU+X54Dlq7BG6ujInVFI7Q)UiT!OUK6G6?L8eVgF>TXUuM4>iebZ^5re5 zKnlZ91FBmLT~35H9#m;)UdpXSIQr(-X7eaMUe|iJKL6`VUl_h0s-z z!cVJ<9P?2|+2jx-?W=A+J4Ac#JzxKL&RfYty=|D}zWcH#5SzGkI&i*V#)!Pb?19k6h3^m<4xX5Pu z9P64Il&3cq3Fg8gxuROcY2=DKZ8=I^UEaGMb>6glQ_~~-@ys~n$*_KB#MN52J3eL} z;@Mm*ET#5wZk9Tt8fm5N;ToNCsx>t=T;b>{ZgzIl*=XZb@vYNG$==WCG_P07J23dr zjQ=V!`S?uD>Q(O?#F^)E05k8`o2}!d2QJ#X zoqgWBKB};4fZ1lwO%csyl|k6&r9?8ZQ9SFIxFHpK=hTFaReGHwIY`sLN@AiOy3R2> z0zaC!B3zo?9mZTSvn;Bo3(p+?)25s`YkARdA=YDNPcBy;+j&)dslFQ&Qoz-}Zx)W4 zb}@RF2`kgyVT2wH4ytrd&Pis#n%K^Hd~R{H%GR`umi8wgtC|WW4mn|pwU)(F-g-Px z9NS&&zPJR%gqZjo4#M^Hz0v?I;zdIfn~JD#`XSo4)*!4(^k2qRes;>3@B=#M&5o(D z0-W#gp3F5cNO@;(CmKT>6&^4P8RPc~zq_zk$3ovV<**u=xl3!!>5K%M&HNs9~Tn7}WtE=-x-P48o^TG$%a!!8bJl^pso4T$e zvS=Z}%@90SyU^nHK3XSf9Ok2!#oX+xN6{fMO>)`+75&1V#VKxl;X{x$ztl6reeJWc z5VwQ3PUqL= zFrfqeD#NFQ3fbH;G&|M*oJVdU1P}~6thyo+H?^~};73HXg(DCy`;>Xn6w%L5e=K8H zkO;eQ+XRkBVn<>_n<3?;%fXj2v$7~HP4}S^_3<%TYFAQ4CCE*j@87k$lf@Md8>I6a z`6v3ApCY8!|5J%7ZU{s6Yl2`(G9~E`n-eM)KkKB@%!=lHy5xr|GBnJHmFKs2rN#gP zZ?9p8YrB~wH}ax~rRQC4^=XES(M>5x7&vh81A-V$4xygg*O?s0ej-foe-Q`}JONgs zKDdr*zxsaWD!<+VKa8n=|?C7)Qqx z41vgfTAkr&`CR=4XP^t`p1f2qe9~=LPvE0JGQ*)aLXJf>zq%cIw{5 zcXp={;ZIqV!OB&a<>iBA$qB;+Eqq zbG>j00e2YNijT5K%{_6++DeldHqx%VXPMmV+A8=EdYwlcw<@5LWpV60KB=*6 zpAqaKN$)De@S67d<|o-{RR@u$I|JAq4=&9q`&@yV3m>kQYSqvfG3I?bqcT&iTdTr4 zq!tCkVx9<)6E29Tv{34j5v`(?8bnjP z=JE)epTv&zdWu4NfS#X;lSxxjpa$TX6gSb+RJAKEgEf_rRTyb^JoWXLk)ZMY zGRtC@6+t!^KJpOLg`mfXY(Eq|;(Gd``bnE7^*_b^|DW~#5B>CCc7rb?m@nf_GhkxtU0cvlt~HZ;?7D}$fa{~o2g8zRzcXlJzR_-A-=j}9 z0AGAxxp<;r*C-0?&9w(QllEXt%lIJ*aAGs$x3z;9*PDo!BMwhq7m)bl9XR`L#@0)Y z(3X??kl>w{vnhC>UwyvIYTKHt1h;Cr=VEB-@xC7`{ldmEK~YVeE|)8L%zK*akBptT z6k)3BpyH{@eJ$6q5mxSIFaE zk^P6yT~$S^V*d!OpnG^^UGQikA`AW4-2vhv4ht6rO)=7i=EpvAT=uM6VP!DDNLN`^ zy{O{+l3rN7`t}7rnf3p80X+0u+sRu~jt`Ig6$tvbyLxT+*J@qB6HtL(+KzEy^2aI= zL20OaK8S&YDPfuRRJtx;^=K;_AxeQTp-aO)yB}~aE)OoYs?i9uArR0BC^8alM zILGuN9=0|Xss?n~F_O!|4`G&}*7DGU`vhuN{ILiz)j_S!<_X;>R9bm`>KoO)eAZk@ zP?#O=1ALHghHVF2VhOlV{^b274D7Z=FdrKo+KN-z_4MV^tSme}j_A$TmnStN-?^Q0 z!ooa(fYi#JSqX3D+hX(@#W6B^QzB7%sABOSuV*m$c$G)egZyRGynihzKqjL!YZEDJ z?qiV+1UenNe+gTr%^%bPIf}SQz}fD#+t(G24)t(uGk$9-gZt>fvz#T~4C9_SBXJt+ zKdaB?`PT>1eyhrU+I=OkHbv?6MajlPTc#&uBz`xsdBW$0Vq(k$cjefmVIOeN6aIGv;(t+r|Gye{KI+ewF@f{O?{w(8 z6gfWhSA-4%5Z=$hLq1B&eAJo{9A9T`U0xTIc2#)ssdP{ZOF7s2h=I8&$fRM}zQ(aG z{W+C@4Xf4G%#7IcY+j(|VquAx#IP!8@F**-4U_)9{fZ@@a*4BM_s8c5XM$$T3KgRM*g00KrHw6Dm>A7B(hq9S0IM|7 z?Dy~Txbt9JtJ^%Jx&mja^J~}>aQ^dcPtETQRoBx1JC)80fuZMC=DkP4wD0#~q!DEE zp0OoWW@gA~>4q;16qeTg>@udo@_3v*bsgTL^R`0FE`uX*86V(c5}1azEQ1+lmbMn`9fG$ zhYt>@l9Kg*R8-&cCyW3B+C8OX!8LV_zBEtsovsd|he4|B&g8fg(9SuPj9ApLL<3A- zSX86-zWI2Mfj+bL8&y^gw)|^L=2!q%WW(4AWlebz2@)ggQbBbohIkQtoCQndg2xE} zJL)Ev=h{J-xju>-dAn42y2Pk_7I%)0@kc^&#qT*GPmZhrm8ujn5A`lfvfb+a-YP8y z-gbA8K;E2uQ*tUUa}S(_w!!t+LsM?07=WBC`me^u=3Yr#P1q__m0uC(q-HAyUs(i_ zA5@eW834;h*TMa<1twQ@y;k#()NF9=oSf}$dwe-^Z6PHGY^5YvCdTk-P*bPzi;+10 zT!gz~qpaFPNtFBR!CkZ5!RVVV_3kCcdR<-q21pwIf6>VR0s|*^QjFNR(1w4qnT7={ z=7e1iyJI@;HbWRHIF0E|_n7Hiv@i%j`s6Ipd;kdF+sa@OZtbfiOp{ZW#&T}2v*NOf zsn_}|XUR$MUO*?Srpn$rglaN<%mL{7!L+tE!QN=jPs@c?Aa|LT5S3^ACuZgYgoRqADoHB{0a{S|EzaJ3rc)<#1S+SXqVc$&h z@GO^zzO}TFwkJY$n%eGt1}&@B8j0aHsr$M{|115AN*48Po!J9S<(l)4VOu8m^Hog7 zQnw$C&Xm+ytvZ_x?))Syk@NHzg^J5eXLFKqrsx347;0`j5m0^kVjcmLw=TDLI}nL9 z!4+=z-u_kG%};*&I+7t9Tya%)cKdroAwM38@)O!}Dk`C%xdz;~aWlY_B#B!B29zQC6!_^{FvjH9PZAt^Gh=qYwkK%R!f+jUFJ zQhqTAR@=4*FTc$XULCy2$3#1_pZ?`mZ*nGurK>nWrqct7;HVnQ-nPJ|hz1P(&*%b$ z?*}*AE!||*#g@L|TDV8Mz@?SYkNB)Co_FsLWV|KVCpu3M8MC-h6CFBN8~&L2Q*!90 z|I@m|2_WDEkE-T|wGxFtMvSX@B;k6%L&$C#nX91}0jy;qOew6ast10f;JEO+RS=laV=?BQ--;KsHFNqF+X zPpFOx7Gry7eTm!-SXtT63cfXQrEUVfm?I3mb<66V_2&-Jr#o9an!7c6fv z(W@784hz4+exbr21bh^Jk4zvim5SFCi|Q+wam|!1=hmcnruwD9Au;s^5zb?VBZHnz zkA#X%Sc&Y%!Ghbr&a7ETa!TNv4mdk~*_!eAqC8_T@qLdC2Ejc1JyOie?F9MsxO4Eu z3oOR0i~PeCz?_{#OitLcIn(^daV*=h;mphbiV7Wq;0a{^Oc{qaf(%)B0uDK4^3}g+ zRdooIx|A*asRK)s0}M0;GAl8iR(MrN0CJQg94H)08}U*fAFpBW4%kR#E4h3H4xg5a z)m#<+8dw1L#xub7Q-4#NY|YOHns&vyR6%JJL)uz42aE4Pz*8A(oebK}Et7DZkn%tO z+j8jEj>|7_y1qT{voAVQ#-li`E+2qU!jW0*BXEL$`Mmt6vMkv~_q}vAyd~ApYiEz` zs^Ndf#H4i^OmviC+YOhR0;4BZS{(ou#w|ou*W9PLF48?_vuBDPU1$|j73G2G*1UoG zHJ<@SM%@CO`x*a#%l5yz;kLk!0D|5>w0O=J3=62=?os()#avLDE;~A!~fAkM4wjUoKIYiIZROVp-3Fw%Ki$EPTFv+7G=>#qT9WE=((H4~ zYW)R4m<~ARC`W8`Zf%DEGun?A9Q5>pAHDd+ggs6rctJ;_$#lBMdZum?b!x!hY5bhotOEXirV;a6k6mEXiz)5Sf_z+v*W#(-)ZjUZBIB(Y80H&y|}ml1JbdBbew? zYkVL(e0Ctrrz-ceTnjDp)Jw_t^|RcwlPD@>LJ_ z8T0s(V|Rzo9WYOi7JXF>fHogwDt&Z*8rcwu{~Y1_bN=?Z7rB3FEBWZ-7Pf%cxje;x zOFvRc@a(&PLiV4Z(_X)yKern8>Zf2OMo3;{P7=|+xW-7)3zmv7OXbgkW)RJUOa!os z3_%OfS2@Llz9E>m7DvCLl{~qOO(qgXS9d%)GVS5D7L?k6wb;~R@uoYdp-Bs)I$sGG zDRsM7H1KrlCOY`@qKY_oxV@|>boaf3yd51G*n6gd7;r{p78B%4Js!l%Sk=S?p)13A^1sH8 z0u5v@(_q$k?;5ica}@Y-UstH^4^<55E^UxACU}XlNZ9f)DLDKf$~WhB_~QOWWCrG8 z(}2!!hrRzn)j0)M)Dab9woYtxY;=;2osMnWwmLR$?yb6dThFs< zuUdP~`Hj(?4Q_J7QTyM#kE&dTz#dNJkeYhw_0^P3_Ub({lieGx1qea|V@a*xhls_$ z2y0uSA&8bCR8_6}7NQix2@wMsHq41<2{l2%fMXy-1}WExtEFK>ZQ~F^Z=@6Pu}Epx zmO^Y6mjIOHrER5BMy(Z6u7#nGqb8D~<2yC5#h<;fhR66d=i9lVJJcKJHyM+0Bp5k= zGm{D4@p#H~aPbM=&08zN8(!WGO1TW5=mk`)3TaY#^~Wji47u+s3zGoKGJe^MYPn-f zWszHAb2K>rt^Reypf*;Vs1_dv{L`%zY5+|`8<|dWNz=^890RP@>{R^ zfQQat9g&lhv$ozMa4_PpZd-S&!0|GgP^`Ok$SSob6?Pld#0n1o>~Ypg4e2(>!CY2g z+%?e2L@;p*Ka--mFbysq=RK#M>I%q&1Gbso&O;Fe~c+ zvm4iTh*LP|JR#A9AV!j}Ab#ZVr*%gpN6RlHFC%i((~LUv%$=-+#GkioplC`N$+60& zjwRDL+%y|fa>^G0Nue3OEoIK(l*EgJkH(jK^be_0VjZCLX68>jh69=cV)zUy)Q|xl zlM8KxoTOV-!&1zB>4~&CJ&t@wT=>p_%BO`fX;w-&Kc$JUh2s~Gs^5!jjo!yI&I@b@ z3vAq11HR@C#+0$78L^8qq#H?Qc{7B6?Nj{vrSCqvzcFe}u2}Wfb|A(Au^7Gn$UBe& zS(@7C>=MOAa#F7IqK-UdAIi1DN%;1cb*vP!@=hi&X%x0o;2VK(o^03O=WeOc**mwl zjekY4B?n9<6iW(ObNgmSqCWkK+jWV!wHE>nZE zIlb%Ne+7Ep8IUbIoX27obs859E@e28%ZGF1UN=#XS-*tH)Me~>^n!!h;(`xv(>_~M z0G0xMGum)O($j%N7p*)1f=9i89@bDqHz+8yBN4ZP|?_A zBg3Ih%x;bWi%2nipV72Nqk?#7Sy7>M9^z7-$b#jxr14dD`Yd<3-97Y2xX9Tf!fLwl z3tt=uO%uIHH^~;pOV7?uwGF95nugsx8}H;Smb)ecu20AYw$EX36O%cp6TFo}Ol;Xe zt~V}Y+=v*ZLGUJOla*!HvDD@ow6WK&?q2#w8szK=VHbS1Jz85gKStwQy9okfBpBUu z5z6JhHKI|3tKqzmy;|FCk%gvv(UK)513TIxHA|+RfdQ*AgGVJ(%XZ_ow2pR&#X%nK zWc`qvY|DN>eQ01qlVn75m`%13@hOFTliY#lgyp5xN9Yen59+guD5n@;MP&y%p@8gY z{_zoEpfhWvG$Anz*V1tl3yw>1^QaH+jIu$*z9pNAjz(m&1Jra~;I|T)3|+O@|GYbp0$Nux{G`V#4PcfRPR-B}@#lreKda2P8H?e^WsJd4| zM~L(>dg`CBqzK<%Q{-6jk7(VW5%kd$1Fg_+eeJ?7%nt{`Tzq=Vd%zB*rOb(jQ*|C|IVVNAVlO1bFZ0PKK^ZU;>f0N9nd$Lmi` z{J3}%uz%prOAJX>92*Hm*e#?cZx4-OpzryM0Hv(EYPBjz#%ou>W}3t!C_VYtF{D-n)XJy!tG5blNOg29!w2b#3LG4qVYX#QJE- z_l;oA-p`pZC)NJip&COVS`^gfL2&$GIia1+=!y#_h4w=5V8-6WSP>*>X5kiOxTT^tez zr++Wv4~naBp@Bj3XB?U{5ebHkA@jUr%&|;u12_>WSepMy1sRZ%Bh)8k7{;@YjTlj_ zNCWHRf%VnU$5Xjt#{OVu6oAp6wu)P@(Qeo%zf?u5K zm!OnwXEs;EqV{~VrD28vBXI_ zCs_jYnk^jQDG+w!?todUvyrxl-#pGiIn;t>ZWF6W|v(xYdwBST!6eH6fV$ekXpYdBok^b!UL%H7+0Q#&T^7JA|{5&CLO-&L_Mi$<1RIFfI2C4Sjv&QUYjND<}pZjVAlQak3X{(gkA@&DNCszN$lXd@d(0-#K z(h*Faxdc99(p~9^FCGyO!u<0Cbsp3A{AZB<7Ztp#Bs9(mW5w&rGUs%-HnYillQ48% z9KuTzYpJ^%j=T6&9sLEe$9e&TlS8|>ggNDX~*|fE+ z&zT$SOpS}i`)%=cyUJi1{I}q81PQMMVtyvh8?%7|hTYLhEd*lfYSbHh7G&%U^3%@c zl490OU7nX%n0@emKcF8PSTo4rRv>qrwf{4LAxP`{PtVg;!2j(LdV#%hCY^GMbfTjz zWED%p=?-~t+W$*Rp6FN#A_W$GN3^388lTHIhA5taoBxSI^%QdcbX&}CFrk@-yu#($ zV{GKzflItp>43Y4++SEy1IN;6^Vra2yw^opMUtw<*C9Oy6S-gTZS#5%c6}7m^J#up@0Mj=2@06bhIa?tug3s8G zH!Z4?HVr+LfbtW`rGkg&9m94f-U`^I@4ED`+RZrA9?_UP(z|y*Syu9;QpZYV#A+=& zGpVK<9ec#3*B;5d|GXsAnzg2+Fe4^p=iq6-dEM?BcnPMPGS@?I`zty>$M8Ou;6*&> z_ap$~bId{ngy<|zIH^1=?S*vv2F|qS4*erCfVYLNM^0-FW1kvmSCy>4?|Eew8sHk1 zt$m%%BQ@_>VKggNO-gL1Ar~r&sdTss^Xan_7YCj4FahiqH>1?DzCyW#Rsv-$Ix}0j z`%5|r8M%WtrQ%IiF}epn*i3dzDkj0z)E?dSMhATYo(1Bt)f^L_`hyO&y?vnIF_!P+ zuF~C?r*GcJTs;xFX|Mz)SGtV}fp6f~#r&p&N~z<3u>rR~N{?;!JoTD5(`J zlv5zY{sIDLbwV8lJ-@jw2Er}D8OhDfEXLR_LYLpk$`91MR)X%Yj~u5oZ6rv3o0&ze z7!<<*48Hh0ks7$1393hy`NpE?khA|FS{(yUXg4~I!0j^7sGgS%G@~Yo9Ywk|A+~%? z83|~mq%4Z@zuCak>-7QitLZ*AI9u;d^m<8YvOv|<#V~|#7^`k+MRi+5LUbQfsfoMF z#0B3;iaZD-WKsTP=FEYnFMNR#IqgSbPWL(wukh?vnAk9iv7G zHF_Kh;Q7{2H1f-9=f#V)w$No_r3(d$hFsU?x@KCaCpG!7^}H_6*f;5spAzj@G2j;N zGgECUv9tif@WfS20h08!RF{-iQ2M7Hyg#Sr(f&Njc5kW}4kbG>&e-^p##t0W=hPdr z(ctB@@xv`-E6_)yaq2s7{I%);nzZX@CerJB2pwc;Uzpj9(e0-d7N`fx>DZ?I@kQk_ zAIp!qxGd{$o;K{aT%Teqi?!v^#)r#9WQ)%iKD zbp7B#JySIEEgn*fP=F==sRz9NQ$>gHucI3Y16!|mI@cRhQRBB}Rlk&v&b}Zyl!FEQ z`$^n5ah6tJt%D?z)PQ(gZPw|gY{>8W?~MGR$#`lkjdco}s1>?u(dO`o`vTvC)EZ`( z4j&B6AJ{@`Qd9qwP-;URZdQB1A+3JK_VDiz*NB_EED*TpU(6|QKA-SjuDQM)M1Kwk zT-xg*@%VJLONcr0>TS--<6o^0HpI z^9@*fBEi@(tJnxgqSagx`Lp8+gP_eGEOlkQ_Zy=Cy^32`eqr6~@%8#&=b0z}cZvqd zte#m!)) zybsE#iK-J+z-GkO;K}^Itq;@^H#%NNE0Tj39R>Rc{Kx98^mY@dX5S}v&P$%&%#FH< z9w)$+6+Z6|b%1k080W^8MIa+H{F=gf2VlD(~Vf!uT_t$^t%CmpOSMC>| zU&QE9&vpY-qvW)n#_(74v%|YTy#+e9eE+KxaWE?efRkZ39T(HQJKT8@@OYx_iyqww z>NKMD8>MXN$^z?gq^w35%TIf=vBuGMdShJ2q!Tw2yqx&9KF6tPPL!n+JbzEXTL-Y(S9!?zuPe3&#UejPyAs(QShm{z zAm<3XWgu7&_;>jiR<1CI|8;I$3&??O5#^q}`Nr}2c*<$!@PfK6DYZA7!CKr6$D3KR zV|dP$JgoqWFGLtGbc0FOaXGu{1lab!2II^%J59diLruetJV4ddT3PAm^CES~=tS{B zuIUeoq$Thm?O2Gf4_-JQ;F_T_H`Cs-CgAhOGXSe#u@~i+>#{O8TOCyM{5o8As;tg2 zi2^2}dXt%&(I~~E$M08N^jUX2P4Tj4@)%E5q3qubWB2LIM?U0r#v}2?nZks)lWq8i zN?`OA|E=gON7vo{By>Og(G#s$Cq?9k)}SU9w1s`K{Z6^HU5i7#`~l@GQe!-sZFTBT z)LYm3R?WlxIUKM%F)NmiO9+diYeQFr`^%B!?5kD^3z%f?jvR1DI%ad2{v9)Z-^k(4 z1)0Xw-&J&*&KKx;U+LFAt{&I9s)E~1rf|DBA#A-NTY!*w;k;)m*fc_>O3J_r|tezf~|`!bQ!K6C+ZCB zr0(wPy>np^8q^#!`Z8uVBe9%~U`jJP7*@`V8D3Lw=?UU(ip-!s61c=@gVYPTOT?Cil0$J! z1FIn{>b53qggcJ(rq$z;F6Pd7XClx|)yEv8)f3bgVqSSNDN1a}$7YoClh<(;Qq$H;O$H&G0Id5os^~|D`G2*=2 zgWYkrihYp19iil2ymOK!5cQMd1j76o<1*Yoi)>_di zV)_O3<9GF#ZBy^;3-d+r-rp_G6h5AM3_`<{t)ZD%5yXo7?#F) zkoNH@WMb)2HjF3mG7h?M@}IWI7F6jmwKpm^RxZ@4Iz=MJ*Oe-s)Q@pCv>zP2J9Y*> z0WF51>IZUJ8@wn4Vz-f^!TX4xx3_7$YC0q4^WR4}B`E_!JI1S|<@4fvh}*E3e*FIY z(h-)1_Cb4L`uJ!+?o>>=`)FFmN>0c%Yo8j&Md`kwA$hLa?|f#z{TkZejlCTZDysp! z9mn=|7rSlY_j~qdp6^q4iv}$xaiFQ#mX9ZAp*QT2yLwGGFzP(iP~HQ#dGkc zoWx`~aZIBWB^{gD(M?teTDIMUsLJ|z@|DE@q=eq&sCbbn{|jX z$8)^9PKQV06wvREpU%vW2C8Hr$!J{Bw@jC~85X&U+dg++_Z>`B)Nso2(fFIhm{!88 zohCPTu^*zqq~ZAC=YxXo))Si6%%%ct5D7|7gv6*wl88ZGZePA(iWhno8xuN!4DJpN z>}cS7-Iw~v)psnLT)T!Jvh#(y_Cu7zi)RB=7_gb;r;Ceo0Qx<_>w#>pfG4ldiqkbU z!H+hzR}F2q-34a}Cjm%rhJD&!8dob5+^V=NDjMUHs|-sL`Cv2 z(Bx*6P!^>!?p)(Lyg+ujz6hy6#XlRqIgKJomDGBR&NC4Oaq;S+m^=lE(!QmtuNzH< zy4?2Z?)3{#z5iT}1*jW))xYf~>2)Km>(UP`j3waZ!Q?i7OnZ$eh+{ zJekaMz0LkXZ8{7q={*-%ob04c2nEGp&U74#N^Y^@2!a-WnVDG5>MtpJqR^z=zkr6(%&;fLw z@ABdarP?^m1+Bkld(6$SyZE-HskTK(_@DWgrt-x7 zod~=}kV$^SF{Cm%G4SZ=%h=D2bWah=qWX6_=M6 zZA?ox<6<+NNwKtAVA&L{pr#^UnI?Sj6JXdrh-+hB#ceg% zaVD$Xf#1#HpsLn-S-DHk!WO8-j{Y5^AN#|Q?@UO&tl9yWMnU+VSqi0?DIG=ET(J3H zyEN`2Q71LKL!2a-icn+81$eV1d;i)f0E2o^MbhSc96|7hKKHn|t0sfq8>B-X5sD%6 zlu$~d8V=)d@Tf|ynUOSp!SUh5(1Ihld!ylE>;{CP@!O5)^WfwV;hm>_Uk|cvpMh9P zPft1p#p^nW5Iu^Qd6Tl9x}(X8HuRT7QX-(8o46d6g9o6J`<*_zAK7zRj~H?$!X}N> zav%0fF(>UG+l`?xAigBJnQ}9IF^0?U&(aaaA9h`%@lLH3goza|0}p z3PpWu{$vb2VB4#KKKE{xKt%wYy?z=yysJUFOn@*aP|6~;F0Lk`!!nlkAyB<t0SjHld&IWMmjcZ zSd~`AbD!G4^?Ecuq3t+t7aaLKE<#ehVkDQ`+1ia&y&!=+M^Tw%^l>78%zaZb(mmlC zRhrQN6)F-^XFR(US%TLyT;52y%4xNby)5zeaqa-JJk*-H^j|wtFzFHArFC(r$(64l znenSZ$IoNdiTqu?1vnx}A9T5eij*h!g4iMN03|+J9oh>Ll|Lc8nCE%6sLg+$z(Cw8)+UJo{3=mw&(L53 zxR#cRe6c=yLB}h6?K94!Z?5I(*#niQL3f~-`$v@iRwEDB!^@#Tpjc|60{8Q!3e5tj z86^&c(SQm0(Fm-E@MkT5ewp@{mA=iW?DlDjSwMAPjcXDgE>)7?jqN0w0{uB?MW+<4 zJZ*$8g3y)>moa<_Y{(#ANi~_GKo&W!&~85c!fYkMQ_?T?9o5QRo& ziSe5V_Kk^*rG_l$;DxQq(On;`DaJhADSHMiaTs<(NV)G9e9u4B*r7k z^705n0pRgVN=RgXkJCDNYSbiq(?WY?^mQKYj7B|B2ybFRPIAokFVJc*qncQ}9HF5V zS1VOb%2HT-spKdTWie(>HW6Lb6c>&xn=*l;PQ7Y1-(py5z7Sjd1-`U+LE+1gGv(T$ z#rtZ!Rcuv;5ARIUkkXbQ=pVG}q_{va$>#^HFxe^YDJ`K_ThW~IUub$qIkY-FR(AR* zub&Csg}wQL6r>P{;BEk9oqQVDEa{~IMMs|reOy;IV^!!{OeP=@_H$WhuYh*i+5Ile z5wgL!++|E6KOEIRNv}~{7u#GjbJZ~BQU~*-gy`>A7I{L&DF|_POgiW~ zUxJT~cm^T-Ud)B=@Rx)ax4z!D&c#eVEBim6B?0zD8Ws80OKfNiXbA;TSy37{Z&uFJ z0}~bntv)mPD1 z@<>oT##WRcB|pzvdXhPw+6s!7xGD9+O^*W+5D&daArYVSs*D1BLfy*H$2hv=h_1rEMwJi&^zQ5Y) zy-+F0z~bfKx@!t>E~G7h!mD(Vd=XLa!W7k7xR0XaeveJ7=_ek)1T^EgSL|9@|GoQy z>DT(dy&u+mNyiL5g{K&0C_f3E7U5EQ#U0@CS}{KfDoR`OBpR~Yx)N11RmNKTYiU@> z2=*oYyfB`{hkB!v%d~*$Y^uF6eOoIrm@KM2(qQCJ|MOsE2Va)1kfNy)j69WP(2%q% zO%AzSnJo>6fya(k^XbRQpA$L@TSg28J_$l#qUX~jBjBDn9bEI{i|g?j`~lg@hb|i$ z)>R)p!marqhOH(f8~sd_TPG-FGBdgkbUzwVM8<^JlC-BdXwXBaaZs-~{Gup%1=ABW zQ!mznC5ei6;V0%)9|OSntFFb$G3F+qnO%-4!cdNwW?A6R4&IUW)fo(1Wp_7~~n zVspR8<43D~6fx7Ki68zZE4g4O1|v&KhRKv)nblhC*ity!;gDeeF-Id+HkJaG7tId% zg1;;{zZrJY{wyerTn>hK2Aj4INsLC7?@xLcd@ivfUEhaT$bsFN5g!Z?cOpspCT!c<=z{mB?Qu-eVJ%Xqo z>N!LdKP4}O_z>=>#d~5O{NJ$jN2aIxUJQ8QhP?U~#fH|n+Scm?S*GtE;D%0bWz7!N zOy%H1N$?Oq8n0aWjMu)fQGja2%tUhzSnUo~Q6)9O>bP zOJHP3F>Q&{8evQNPhw?LBU%JY;huxx8+3A!lH=3=RFMu(>0cPw1tux>wdXwxja<-> z+gED4qyWU)9BQ6cXO$x-h%AUQx25uOd87W zkuUMc7GxMPDTzrpE2H{*mb!j?bbm4vb{v%kL%k7VqYG3Rdj5X#5%6jR;yB2e9~#f% zAXd=gZX~LyL^(XJ)}F$J096$Lr6~wzh)6lVj#Qo2rO~WOU4(HxQr$F*g4`ZbZ1B$Q z{jNvum}NN$<>U$iY-s5S*7d7mTUEU?#G6eBRhN7)#!iJ@%uq)Vp`HEGb)(m8mgGty zVk6CmIH932E2wA-O)yQWOYiVRjl z3sW_gn%FHl{=mX6q^f{IBGhNV z5%~xW;+}POc5*QqMFyjk^_y86g`g*T<4I-3?au@RzV0_P9Kuf>x``=Bm0+1)?KrwN zj}Y*qqN0V)1#=#xEt_h@v|ORZ&bzAD|FxITHAq@fb6&FQU~pVBVLJjcacEg3<{wh1R9z} zoJ(mOX}6-{JsixEBp3Uo3HurD6MzotzGNFo0m6leWPq&i|G!pu-c<7A;}VWC;k9Or zSc9~A#SqkX9KaOkN5&uNOjD*;Nf?R1xnn|FYaYHlbe3 zr2FdxtG?;3Rr_i{@wz&%Ql#D%YP=jAK&IPpDNFm)MQ(O&K6;3rhr9Whfemi5_IZjj z0`3ma!nLI~m2@bNgL`rCq*HI4M!F{~j8;tTkT3IGJF5(Xh{^vNi$w+E0((^pnMkCT zd=awTr@M4kk-Jr#uvtusEq%Z|Sl5D8EmV@W0c^jmwIf*D>(J7Duj1n`LqB@paO!j# z&|HG>wl0(9l^4TddTA8v&!gf3IvMLmXixnzOG2z$6~13L%h+;ST{T<|p$ynnQm=Yp zc0_AaV`wS$uKpmW2Yh`zf$iSM+gr-XshxRhB;2FJD{6a|b+$Q6VCL+#tOfhH-nT(= zh)}D`4!z-i1%bi*+SolkLozf|&T^KQ4VP&Ggc@d)=(V&u8?%oiH(R8P8mtjoO(eO< zhoG;6t1Q2JOpL6nKom80HcO4Y4ZZS;yD_V(OFKHUkWUJ*?$gQFh%qO@5vyo{>h|1eqZUKAjvuX!0QC22=IdJwQHM)53woGw@mfZ3z!g`FL# zu#LST)5g;HWJUE|?0LX?l+mD9pem%nN?)}05ifwJ^09^}F@>xmuYR0l9U z0dg6}!KpQBE*I9e!W%mnyPC|=GJ|tzt^fUfO;EqiQ~L0fU(}+8AT`QrtN?5NK^44~ z(pkmbd`g}@W*g{nVK;&3L5vM)ZoVJ0@&h`;%rd_z*@ZlZi&w1Svngg>2i_HRO>Hrkk{DsLm!nV_^8gn^~VMt@W4 z(coV_-lltA=Xk7W*7QUIw5BWU^LuC&wPX(w2H)ga`Wb!VFruzl)pciJ3&W~(zP>Yucc5I)3}=zt>|Jl8o*VjcE`8oW zS9m^6k3X)elxZfBKfFW)yZ0AZBKVJUA(83FVNWWu+~k z?=w#F%#>krZ`_DdDaelfdQLRn^&S@V(W9e_qsNA)@M>*oZT2_vpF5$bW*+SlgKkhU zD#|?<8?3rVFB&yGVP9s1`c*I53_*>t{sL!N-{#3V%h(WFG0>lb1b%}(P7Y!(<3>I9 z|Gq(=(P&73^#@iJOr_4l_y&q`V#~!#42XB8CktZ3GE(WiT(kfe`hDkvoDlB1Zprra z-_A-R;iZP21V>W@N@DpVI%v?{1rN9$gbQr{Wj~=tmx0%qd&Y{i3Q&~duu?;q zxBO5hZ76w9gfFj7L)y3X` zyWF67QW9-shyh871Sr{ZT{RbpJ6_f)I48)O9AHw+?(S})q?*#AljIc8V2Ib;GN1_N z<_lBo%b$3(RprFAD#;!M=PmzI;+h(Fr1u+SWZ{@euS}J+v|)6|lz0uqJ0(Jr|w@mlM3 zk;b~u8l8Em`Z+jB^z(5x5doxQ`Q3!St`}{2#CB(sNhWfZ_>e_y}X*%j^Z+x6w zL$d)*dGbjxc%vrACtnhHVT@E~LLvdZXgtAHFTc{j-f-bK8x&Pka@&lYA0V z7KMIWj9&q30@>U(uS%+@Y+mkeOEYg0oPie4?iliNVSgE45pneq8*KR#i3HOPT% zqp|6sr;iH#iAg`kNYl2u3PxJy(}O+DWIa4fdC?&(ud5YXMq~?dLB%i&MsGPw2lZ@u z60%7=7*%9+h!Y!1dcVzP%JFBb9Psw_L7LTr_F$8{^T}4v=oC5&bg1>01#GI>g2ZZW zCDywDFbfTN8=j8A*M^xrUFM^AeCB_Fj~l?Mx-(Q1IP2`S{|%%Y_)2>sd4@*TBRQLKEe;^j<8$Cdof#LWfy!8PYBlgkKRY8?ky|UN=?`F zO3W5*Z{ju08`bV32ApYXB}f3~=k70em@7lxj8%rd)!+NW-9>Z_?$(WTloPphI^qlX zIv6<`*9tO1p=_i=88Y$^eGgm+t*cUG--JuJ76H(rTG1|rgpn;O4pyYnmLCTGQ?A5N zXmYv`8(0@;E;^$FmtGNjXJ26W`o_?pFDOJU0sSnlZNrb-aBKWR+py71e@Spmx+2+# znY^3BYtYIxC6L@uY+BLxBR)VZmoytqR8q2k)7cYy=AkQ&rvNj=6rJUXlit-J6pwfui|0&%4{U{(H5>|+dm+)=oha242^RVuU?sq7+j6z;oPQtECh`i;_`Tbc1 z^M_sUSLZ@tn*Rg%9{GFtU8paxV6?8{3HbGDECukgw_SSZ8Vz{6F|n8*lhz%RUL9#G z656?WI~n*hAr#Wx@dm}T>|s1s|K>D4A&!@SZ)^s`G*NdsiI|rM5wkiUcG5}#25nYc9*HoVNo{@@e*vB% zJ^8wco+UpkgnDyuh7&!Us~0z>DJXKZ6n{OlYnsl(hy4fpjK#9S$l4YLXI+Da=6yl) zFXf;F9M5-p(=RJZIW3Hu!p*z*?d4l0SD3Qgp%|Q%uMW5H@znYHDkPK;TE$?MjhFrq_%=7+Z zWjOx~4zs~fx^EaEw-gs}_iDJK_6i#Mbu@tknTI1Mb#^%=QP!f*f_Nn^`?sR!Foh|V zady?v9G{dq86ogpDaJ_>!%FDEdW!gU5vhzEvDV1|e_aa`6_bLoTq?6K1ehek)=vOn zC^g;=E!g>h%d7$dydCoOmco(|88^>eqWO+pV>QiUgugSeM6YA`8 z)!T97U#VonR8WuDen4aBdfwdisR{>WnIaa`4o%ZTpjD9GJ4KZVmPdZW^(m0C@Q0`| zg=hfBkSO$*Bf9_VwW{b{Jwf=XFvST66KOd$fihV>drJbW8e5;&{ew_HhlBC^sB{p1 z6u9K@A<`hzem~y-8IjOlldB5DJ1#SYl_ad5cQhukM6G_qBQ>dtE-%Hd@0tB0;cmcw z+-loqX26}^m72c2PT!0kK|H^Q_`!Z`W5qNRk6mDDV?H{3ru&@#9MxJ^I;kBxGr3rO zW)<9aMmu*mC*fs>?P6NX6n{e zuN7(s7>q{O+1XGOd1q`ES_WB>6(6Q;Uq~j8;Xg6tKuNKQ_BWw^>v|qJEigt$&vUEY zU7K49`b8C_A`JuvgAm|~3L$Mv0(QSHW*u7_O6Fzj%Huvt!UHAv^jYte{5OzpzdBQj+0ip%6)_DS%CJ{<;AZ zt@ZRmPkNuc8SeHM-={2yw__ajVBBvqjop_s3%ez1``{v&{pyFo7E@S=Mn_C;{t7vV z)8c%Z@nk^4rRKSP`_PjoNnd_Gc$6 z&eG&f$cj$%ABX1AUK7`6Fgx1wh4S5MwZWBMyRzHFt zLfDT&C5Dm?T3ua&k_ihkWNckTZNe4WAecY;px)dad;bPw>y2=eKjhtOw-~JzYhC#4 z$B%F}m?9a$RLK)ODyJlNM4uppJmEdIgZU;}$tA^J^F{8kPe+V;UIFATwi__Qr?B&B zsOA26S?4wX*P;{`0<--;;RDPFpd>#MyUQuFfxor+W3DsY6`O z7&=+*QrmBmB_cbZ8?2-Nc5~Ml{QJWAZZtN`_V1cxwIucT-O}zvMYBlu4k7otn&hDw zHGbmD-;K|K-OKaq(U$O=$=~)7@$ez0We9jbQH<6T5m@S+doE6`#X}N`)Xmsj6t-qJ zqlv1~ydzTP}WyAR94h$QRQ&6F1%|q{kG%UVI|Q{r{907XttIGI5t`CKi~}DXx3)kCjWgg43WoT zw$9S#;{&)}eQ@)(|94yefA#mPlflV~dKU(Q-CwX>$7+Pei)w0!7u6T7u2@B8`kJ&@ z1mY5EO%f~oQ2AhVCo;J3sgWv;sI6`mB}*=0vm9tVKy#OL){19m3cGTNqYLh@cD`Bm;!nfMovib3G|w@jnT zuAPo#@0J!Cpf2_%lOtekAcUfvGa-T`1Rs)z?cExOycf8E}abcFR1B zckCVucPj14K|8b0Yq&Wyg?x1Jb%`@JD^J*;BRX$lG<9nE=;`Rk@so!d4x%6@1`V7b z>f@d1tb4F{Qo`y2a$|Efj;I8BsTsMJTCcOD1Kn{ngL2md%4aEsb8%F^Ug7NI1o2x&{N1pi-CbWpVQ$q$vn;u0`f?~*D4d*fbGAiFGNzqm^Xb%SF$rFA#n z7hy8^ZyRV@8DM=l=JKo(fnscIN5bd@Ec2MqhZo4DfO1$fC7kQg3Qud^mjoi!5{_FA zO;1fXDYB;Po!~`5AR^a^`O1(7!(Xok>Bi8t>o!KDxf{?myf6knihPBslXf2h;%GP|+ z{Opig@j}=O8p6plu1v}Meg`n7#qsFh^~-Ilfz|m+b%U2JfIK;UKFnFkL(WYt!(Uz?A!9L=XG8@?uhhY(V9f!bl=LGZ!`eqTp=ccsA`=hI zi;#7`C=@PP$B8oBtE|*tac-^Ucotr2lUasc?i?5!wOvB|88GHg@0j;|4grRK1)jwwbq<`-|mFoy9#1K7ZB5CQhH4H{IBIM;z*!#_i zsJ0guLY7XF&m(@sw-X;ud3VgY7q}@wL)NQWGs~4zfjc2Q?&4sb+_jdGXqrmsdh28Am4C^qh@;5a8g#E&E|% zEWoOyyWZ&#I8xt#@`#3tepZqW@tB&E3%8F<=%<+-*xo?d^`Zq$pf4mgl)O5%O1>v0 zeul*ZnwwsYY!8c6(g46f3Mwg&H%TAFKrsKlIaG1drC|wDtP~S^`YHki#j>oh+%64! z?9R0PW$X+4GT&Du5Xd^sq?YxI5p1z!^<@kHTM(GdDMGUvuz&~@AtT8Ja*SPrwGTfT zL7J zbD$kQsKpevFE!|I3I0*C1W6M8#UU=uoj9Hsu2T?_@gpaDM?WU#Od}s}_jwj#2HoLzPF?Io8~#t;Ab?jf z#@dK0k$sb$j}LNE+Mua8>Pj{|BX*WMR%ET1hAdlvy;x$5`^RZS{a{QJ z(9!}oX>C5K=^^TY@K}Ci7VIDHNi;vfnaDiZ%QrQ8+q%=ZBY)(LfxcQMy{zOg@xO4m^n7riBl!Q$U_Y#+C^4iu;nvrjH}8~Wc;FSH^*&x~NZXhq2Cwcgs= z2S>v1%fihh(|v{ zK6n4#su%0nxzlav)zx0iVrQQ?oB#jPeVej*N5y~cPXT- z0|Xf|fx$WQ@@w+IQ4RJ=?;?)4@g+UwZ#9(3yDO7(fh;if`dHb|miTQHwID1?J;M6o!he{eu^4s&%`ra0(-9UG4(&;^~g?xZx7}PI$kB z{&`rFmOiLEyW_I-JeFNZkPmT%Oye)r@HsIHF9EetK%!Xaqv-ABkUy z7^u*r1GH!A*{MTrrqKa-c1Os%nx(Kwp{@A@SW>?h%O!)bhy$~oc zGeVIfK`ZAXi` zfvB#`#N26ql2Bw}944hlmnnU=4AuX5MoIYbT0ugd)bOhXgQP)JZC*0|Ef%|hEC`=l zQvKpt+&Tn{MPfv);8|i3$8x1L229obYgB5eE;M8)%K}*FL`>MYjt2x5Lwrr}vU;g% zHA;Qy>HOl@FLrH}mt?de;e+2?;-<2)+|{zmLI!(CCLSing$Pigp67lx0x(P5ED<3x z`%A${wTvDXNs(WX1Nkx>9?;V0%|&#NEJq^lgQB#fo=6muNgO>9R_Zx(DjDHD;C`H+ zvtpGyGP~3&Z}$K>Y>P{A#yU$F?KC$w%y4n{o|i0a(}uj;IgXdyzYH3P2n=j=_948pF0O^l`)Kxwy9+G((=dR0eq?H-7TvwzlmCm&!z+ey zOcvLoYf(oqqwV?b86e-Ieu!qW?FVD5U~-rT97s87EwK$&V?vt8 z78PIfa{~*V>zCCJZ3Q1ib{#S22xufUFExhPLWm{GCJ+|$uiR6uR=8F3fTt&`$4D-e9*3~hRK+ET zQ5d2#)U>F3Rj~a_I-2xqE8f?C8#;PT37u0 zHwWi<7wJnH9SkB-cZJcXbyHh=#7L1}@zi4(W5Qn1 zzb|5N2OGJrPNBwKACz8dYn=p_{DT$}o~b{BUSZzkenro^NcqQCV{p4z_g%u1N1_0Q zmPCX?mY8Xhl^xFmS--ZNb?vo!w%$KU7HZdF{%|ynPg@N_d>+ezs1Mb6`e8|I2YSi~KUfQzC^UX0yOwwV3RC7HjrUVbBG(dfdj!BPr2iWQ z)eW{T(8+7Nj(_tiCkR{ipgmR6l(w!Cl7%e9h zpBHJ)gcT5Y_L|H=HiTt&J{{G!ijV@A&wKe|lbr08X}OUnmNsH;P8hj8WhkhdR+Y?C zW4&l$j=y%aECt9}I?)HSM}!Eq#I$**l2(Y%MfNyswra>eT6CO-H2tKEdRVqQJdo3p zh{2$!x$GYjKMyJxiR6jgS%~N=k7V|g2r-$>S!N_(LguAYTDTcyzbZw#J41Cfy)_i# zwP0BmwH24N;;e~V(v@i3E2P35q7AYCji48KxF!$C)eOi%F*OUnF0iL2Q>ttr(h@rw z!+A6l1oRxt3?^kXVwp&OzMRyk$nn^fSh$-vSk9M?nWqwW-GY?Lz?7tntmjuDc;k86n5)HV8otjEp=Od@(W$7)m&fN&5)VO*c&vdoN6_XuwE+}-tk+<^lE+nL2Ae?ZKJ97P&TK9ZU6o*<^P==tGombEDH4KJ@HB-+;3b$W8!(P!%(b-`f`YnZuK~Q)euTmIiPOcN_V| z^x$jTX-w~h%OvhzuR|eh*+>03XFQmlpQEO`8&}6jlG2~?oRWIctROr|@CUT}>#Z>S zA7V?3s7M+jofCGP)?1Dbr~FZ+QwXiMS2~lf=L#Z^3)!s4D4->8@Y8R2k*VGs6!Pn_ zDDtZF_UrXd3qH-*Z4&T~Y^Xs7^Az~`?hJ}Jn?;8v`8orEoT|(;@w=P=8@sjRGgARK zHPjfT92A@bw^;{V7lZ;|@_lC#GPe`E`KD4V(KeBQOP}gQY-PP%(>!z)acp5`KUE!Xwn?TON3NB9E?1Hs+gN zHg;y3!1tSMw&a`!n8cd#94)KhI8&X4h}$}6p_V88KHf@~Yq2l8YfqAOg~hsyehAb_Kd7a~=YTc>G6{XjF1fg4DLpafYN;` z$lCmE$?m2^TJE+1{R~7eq5Md;W#RMGTIi!diXx~E^DDr z(6u;!_n>g*56J4BM!0Cn?&=nz-mV){u{8_0(=9Y$i1#!6z`7?4jI8n2s)`Qb*TNE& zPkXD6uo;>$V+f-p0Xf0YZjbSj0DEn)>kU8;B=m%wWzrZr37G*J+2&3q8r^AsjEV!Y z!b?R$jDKN1KEHnH;qZ4B+~l-?ZM*l^`_8#Hypsh76rxt=&*w-3-15hCc0-n>+HKH9 zfv~Y~+dRiFP~>A>ys{b7%Gs@L>!zD@X#nXeRmYaYUoP@?Ml@_X1OI%-^&BNz z9(c0}VPTPT%m=^YgP5Qki1*4F&8*{9o_`au8nenZ` zx%cBO!&H%kv9Yv^9#n$Lok2qPPg%YIj&iydDP~7W!qPLPeg6D2SxW@ang8DSZrxCyr1m82D)qVC44?Br0Z$E^+;x# zh9KJ#a-(1UXUX3yy`@ zpmLz?oZ}y;)-JTm5FP$7m-V+#8dJ{OkNh@mfy1-Y$hjsf(J*^Jzf5i#r;Fs`s%C8R zR{pseyrv9PwPcODOi4T~SEd9ou3Q+(t_(poI%(Q-4_Z7maXeIJXVR|L)pAX(H;}*Y zRLrGU?&ia&c^b{KU$>}BR`gU%8toCM_zA})AJ2ke00X9XROvaN$WzX+(bOxchu%n` zeB<+(#zJiM_Qup)$WNSuBQE)IWEFZgBPi8AU%l9P?l0W|8L`&7U zcX%F)kI_in^UQRwO;-OPP{Vd_1V@prx`v$0jUZY2VE-J30okRDbxYK2CLwW4 z^+_hpign|zsDXH4_;5>n^{ip74~&F5cB0>#{7QjEE#-oThqUz&ARjH5h^3@(HwKf@ z`G-4o^JtCh+Y53`UaPOV;W2IA0C!c71^f5?Qd+3+0(K)bjEnuUa0&f|l88FHFY6@t~F+mU7z)AB> z)bU0XO9unvvx*XOj@%%yySD7P#q;y`2UldqD=msi{*l3Vg1U#SV2h0Br`_G$hkP-Orql8?Qd*|#`1i5N%}BTV$5u;xaZoPy3pkuIW|;Xujy=LH^?n~ z&3#K;7^{Sf(K!8ls&1v|-0THp&V9blTna`N;`fSCO%+;D*T1zY$|JG928&>q^(VyP}GUT^cu08+yX^slW=xc$y&-J3Y% z-{G!FyX(iuVX$mQ+#V8>hU|9s?9{}Unx6EhD&7PCNZcJ5xy=Ky;1?D+Nfu_OPD=Bk zhUf|@&)jYj8_kw1(W^7%t>8VP6vlw%gji=5<$F^6(ikZd1EslMMck8FHNAyDSPnj& zE^I7ZUbf7&U^y9%YeQ_doH5pW)j{WOc_yjNYm2)>QEE@bAOQ92m5+OW=dF4gglz8n zg!as|ZUc0T0*xFXELUDpFmb{8ahI0wekj^I_J$_&3!nVLNm(hzlg1A&jssz}dU|dg zA4=fpfPC0y6t@kJ#CGD2Cp@J1Z;Q$GYZH^s|hAQHLj^~p^uRLv4dxkj9)}sO^ zwT4_9$%n#P#)DZYJCoC>GEK!E${78)r&YEaNf*evO+wXeJ2yr?D(MEqZ!Zm4a%x%* zN|sy+bM+1v*${m|zF`X1xXdnKcZhti_VHl1Sl95W{oRm~%hV{p92c|W0D4b*9kh7E zx#(DJ>V^zK(U6MzA{Vj#jAiT6v#~P3PVX0nhg^O0+j8!9+SD9Ww~faaREYd)d}FX} z8`Y(>YvlJVxj^X0?mJvS}HsX804Fi zWQ22R@ygTVI&7R{L=z$`D*^%tC{I$Djj;9Lt%R;^xM%#s%`&UFXp zi8OWmo+g!HQFEJ#N0?Gcj7z&GfEb6z0BR)`8Q_E!;e$RLlJY3 zalo5U2{9iR*;0mYz2&FRC@ewbqsd0)XC8BDEJ&HGFm{6ma;{IT9^a;7M$AIU`?Gxn zKJDLXBV=rA3c+Q?it?89NBf{x$2s8|j&#kxEs0qS4%7Z7;;c4B#56yo8 z7ZQMl1tTGbTxw!^<^}>^M^aiHI%H$3;r|rsis;no29J&4%%I&&WOp@vf+5 zNZ$&`BR5P+vq~h71Z%fxk6U75ye@uB)Jhn$W165kJPR7YHfAtgE`;^ME$3RuFC~W8 zXxQr_V}#;B{Ol);CvN)u>ayz+_p^J9{0qk@cBHHdksju}_YJ&}8?n)Y*==vxf=Vw3 zF*>Mhjivq1pnq^=$3dfW=1V1qtJZ{Sk$1T!t_4j@n>XiG6cF3t=V#K^h4#W+_{|~2 zmXmeEG1{2WwXlQkle9>dmP!VX$hj6d(S!Pg)Ur1CF0zr}LK?fm40m@Emq0)hDHTuf z?&n3aXbqa&ro8Z4kUfM^;Ew9`6?`BJ1iC)w*g{H_K_q!`Z6-|vV|LC4w(<$Lpn)pm zmspt2sKwUwCE5xeW2~p;QnEJ2y#&;S>m|~Y#Wwiqeu~PFrFgEXFJsm-CRScliQfxp ze*{F)tt8!|2q$Zuxa6AT2mTgr$rfx{Twd@n=mhc0Ve@hzDBgC{#chK_xGM zwy}Bqp$)b+bSGnGV$kxDyG$&i^$v?6zI&PiP7WI_y?RTejQEefw@dzJZlAzA*+9h) zMhf@TsxmI&r)H6a#%uJL1V~YuMlVndC5vxAOY{wKPOFcN_CYIV4t4=oDK~Mr2+=|U z>jvkn>h`U#rb@}k(4I(0j3JmK6k?og5r+RvR^>6)=ja~WIuR80m0~=nK0ya{5A(+X zO&spR$@|qqxPCv^8KNZS(wt|r-UW;Gnq8yrex2BH4yw^e4<6D~YqRG#NqhY@LmAcv zisv)+3NDkWk7#cNMB{Tfe7ysKu+55!lK3bx+TxTljhr5j^~Rs$)5!>Qvv~`L;|R$d zj+odeO<5R-9o&adFj3t`XHph~?-Z@suVgSfmN>wF!t~a{xwq^QIMrZ`UJnpr^Rq!+ zbL=<}CP($a@al=97G{Zg$ohy}?yF<%vf3Gjolya^Ll+>+R!GQ1Yjsh1oz{0nm0k*k z*E(s_s7i%gr)aOZ_f9P#$%i2%98|%rgL^FTd5i^(IoP`Qp5{IA%(g_-7-mgX0ye{adR;%~yszvQw=h zGD_qQ|4}#9rZbc;=hsuk3UvvgDdJBs!P;Kpr+nF~J!7b-mzSq}^7l2{hs!Z)qv3>1 zcHhs?B>M9jEtmX$b2Jlx4f8u3Ixf`4zE zcWSegne5K@3|aar^il0rvU1ZccZH-$H0j{VLbgulBHR?T zerMaii}iUdA@oHqVg2^Fq)u9p6)8ina|abQk(1$bV9OGXhs#!5m7n%%MZipu?$7K` z1(AT8yY$o=yhKM!q=7(@kw5ss}lYEb|NNaT9R|9_R^T0N1NJ&EPxxyN;9m~9ZxFN3-9-Kmnvpm zonL!fT+17`F~Olh?zH0{nrDN4D*B$OMaIKl@P0JB^TvEro8gJ;t$$QB22ZaFuypD+ zW;UhnCV0XpKn9G5`6MGra>*=M{O8u~qg)D{-d5%`zhZU5li~~$Dh;W7l%~U!iU`z#=HY}1I2UfQM5Rf;F*@FhYM5?0X2-0X*go>Jv6hFGaVU%GOuw#Q zcfl|=0)}srDi=rjM2sb-Y2~?qc*_34(u&~du@7WZQRz*%KhMchCSu!1BNskfn&GkHsgW}D zn|q%y>&D(m-l=!ZU{9MJERu&%HGeKO!i%6*h`7YRb@7R z(yeGS;r!^Oe3imyZUiwuPP~8L7FxcT8fTV0J-I8+vQ7w`m&PYP+IVCX714e)N4Yyx z^&VN96M*o(XTK83=vi%Fqz5fx^c>(X}AUvySs- zn)IFMx%!2HZ!yQtZa6(>d-<*dQ#u#Bvb1e9_&4A zR~Ii*0Bw z#Kg#4p)Ik9tfYv{%Bqg@?sn5e$Vn6`Qko(GZSUomm2mTO%>r_h4E@+YKbft$yPb+D zHp#(tP-1g3HMS9D+Z!1>A+o<)b@+&v*_SHKvoo%%_Isr2-%lTYA0_;$0ijKrhBmd< z`DK~Ej_)kvcf5^PE-Fg@ZWIL*r_TRk)xSNJ|H-%i`@;kjoa4D;5IvRLqr`u_n*Y~t z|HJ(av9H9yVLW+r`?0@;WJP?e%m@pu(39YAS7uB0<)6(^v2mLGEo7n>Hgvl>0!|SG z{9mT`Ka2jqxBjnh8d2dwx39$FQqpSv7IL5u*<-iZ3;9h){C|3Z|BYS(uq&Y-5H~Uj z=`ppxg*Y;SZ#ACigU45p{afk&A3ONZ8AA+dE%E_rMa`qwr~O+<_L!3Wp6o z{jC}Zz`h(n-ptOO+%!kpaN@-K(a=htv`hT=TXhr@z)wt{j2ZuL|M9IS*BXFY@=8v40FQ3)fZh-x^mG@cEz*epbL6bB6vs=3}y0zmbXK4$I$}ihl&9 z9R7o!+E^p@D1VRPBKsip*uj9w-z$M8);~7+{}08vnct=QKQ@yk`H>{`{7uHl_q14m~*@U1}Su+a z;0i->6ZVXgYD3fsXq`^mbn^EoSaI;=v^0=^I>6K3Dp$JL`f67MoPAqkwo2`To|&7? z4O>=KJ^HAa|3uT4v0Mv1ww)?vT38WMRfNOU#)dGbywYHF9A7(c&Knzce9a&!sl~yD zNdxf_XWvR!AA8;N@Nt{Rl%gGe_iJ%3yWi<{+edO6lxA~4f2|?NJ;{N zmXh{+uM-AuA2#Ee1^Ml*Eq3EqN2~49rlPRzQa|@`k@Vt_&OWi}0)nU-<0CP6avG-* z8kI&@p{o`|FaxrFAz_FkJlr~q)uE+N+}zwlQf8%hk`YB}(?w1*Wur4z%J}?q(0B`~ z$IDTuY{4_GKMj((3r-5j=r}1VenlM&+@)2V5;$dA2(t7CtIs%9oIRdmUB6bAiyh92 z9Z8xpv&g;UP&LqCIklUcGv{R7%jEg6;kdBQ%iT+tFAKS;%Yp1|T7Darv~v>48%VD% zN8)2~rDHTLU91^9VI=!q1K9daMd%vj8vtwtwVMmIIkZcog4FQjHDbUziqEQtKX`AA zT5eq>BtR79KJv!)HWuUs>4^?c_0GG(Rq~E7~JIYI-z(I-7DLc6dZ89M<>{@|6R~rSTn` z8=J5D|H;gJ_za)G=?crrMK&+VzDth>4=xeL{19d$?s}f$iwhW0RSEbRa%Zr7cM7mA z_U%)@)jjqn4D(_#lw$^OTh%k@g{hF#0CQqf0yxp);c-|Cu7wG))=wOm+KN>0c`=$! z2tYo&3sw+TXW=I}0pgm_iae@qL9Iy88k9E#Jm6t=o3_rCZ0GT^#uPu%WC&%t&q}9# zB>~oii+Ik^CCcKbPYlx1QI4w0zg3{8?u}zkajNTl%i37{yWNQ?s))9_UpGu`9DWcY zIw=8uX9SnNfa6l6a0TuV#D;cyd2|lyhV?y}qZ9B4vK=?%{K>&ot{)2xJr2l*K--Y& zXR*3onp%(xxngNjZRdoCj@CuMNrQ9|KXwj&MnYZ~U1Md#lh;;=jJ)9mI1=Lp?GKx7 z)cIx@kn%9&zW#n9;o^~Fsnub{#3`ZqRb$0F^_fPo`bUdszrd%8i%#q*29!H?t{J6x z5*9Bk=X|4_>_PCck6Eg*%;;VQ{(43~V0NLJW@i<*M8>rLoN>cN7lL8OphI&X-wA@B zA}vI%G0rJ(9gImYc;zUvRBNA>v_JbVfC zvURG=2bPMgZMH2ml4HcPi7jxokk!jFL*GVaSIzHD0W7lK=s3Ml@ahSI4RmS65P$gc z_M2IFEl*Z8pL@`yuso~`;{P!@>F!K3prR}J(7A{Z7ZiPudm)8&*G5k6u8t18x}-RW z>U!{ydn;jeW5O$$^htz9b|e{CgeU=_cCb*m+qOR{sYe#_>W=uwbCz9)zwK*P7duPD z86t_MhzQ3J#+tIerYdd7sG%yCFMm$&MElVVO*XL#67H^YCcRoG2S;?81$bCuu@_)< zE$BmupKB*EQ((@OrA3bRxhpEg>8@w24lMb6OfV`^(V*Sj*a@#p(*FMaB;bHpmJTI+ zlADojZtG!F%5pU$_~Pp+#D97LbX}<*l%^N?JnQ!ihya!ksX4B1CoD_5n|``2fSiyW z=?TB%a(w|S$J!%J9Fx?^NDz~@Z~8G53C&cxX`#ET?^`|hFcn^&h_}Lk${5mB- zBL{&8OO35ms<^*{4Hl&MjX*`u*BjtPKtTHDl~?d~L2~SS_14dYb`FSwF^-Pzc1i{4 zzOLTR4ud2B5l<8XALYq#UK%#fE+%#`C?Gi7B+H|u$sOK&Xq?Pzo*q46mh6u>&#fKu z!EW%JjLhk}si3eH-4J!aDfhJesv+Ecp*Qw2rHF%N@;v4GkU|<)O4Mn~G$&{*r@SiJ z57N0cQIs^_M(3+oPiT)Y(9G`QvWvH;igP=^#`fR8M!cC z3yw|2bU2dB*BBI>iM_YNGOG7~Iq}xE|2R#ZP*er~F@hWXWV&O&S*Ai=bdNb)->;1p z61-8^&%7JA7b}&4lI8udlh^&s7i$qVy035etjo*Ye#kA~ggkEdZWo-k_+AeEG(I|0 z^@qwExiOmXr}0*!F@T=H?ma@0)u90k%h}b%pHtb(JG0RoE*HF9I_HCMzR`)zpr7@> z^W5`%9#bN%7XNx>X1SJ-5ftM6Djs=GP>3TFw_Q-a1JL%ogyI{)VRG&3#%2NJ$20Bl zli3+qiROx0aZV++MJMi;_5Z*%OAdfak7bd<*k#3B>fm*ReY<$>|Aj9}xd4^xNf3od zljs#B`q9h;7z1+Wsw&sq%I6E!wqvrboXajgK+*QtH7Z?pLb0ooF9f*ZuG4CPO*>TR z@*qk%D|7LMOfSgWf7bDQq2H?*5W0G3Vg=k8wwoPNy1s7b z6&9NLj*Y?51Es0BHAW*LxC6!SyQX~}h;HDVjGPnI_-=Qh7H-}=9_BZvM+%iD=d5a? z6rDwOY&m-RAE!3-{I-+vE{=}h`(n0DP>DwE96fR7td_&tK^I?gcuoZ;6|5=Z2**2U z7}~?NU~n&X;JprZSi`oj!Xs+JWJ+Hn_`6cMWy}}3%lPN26XRpjR@@OXLOU8kwly^( zKb7E^_k8?RTnGioqOJ*|Sl+#nN%;8ufs!kOEptBp{DljMf?w3@|&p%5VIBX}!aCb2qX)%;g;Ex(J1v zmq%v4D_4u+u4bDOHy<4-s|E*`ZofW?Cicv3!siuy3kPllxV#1BX*kHw>mx_|-RrG{ z5|$wxhM#kmzMmMqSWL>q$yd}f226Dgea=5JS1nfym^ip}J^ScBzI6sGxkg75;ZU^^ zpmMPa)NB;J6%*dHgZ^bH5_P-IxM>V59EYO-$ewRg!BXhY&S>+V1C$>tFePhqh7eP}K>GB-hyrLIwp+&Jp_2V-Bcm z26#~pUxf7Ub*;xQCe8#&ubf}r(7E2%xyVmsb=UYD51&XWA?Cb#?hl<$uD-F)scd#E z`D_xMTXhs|eRinB5EmaKqX9(M28nk7(@iB;P z(LdvLkCd4{4k&5S2Q_0b&jEk&WU`(GM8aXvf|9A2)HAS#%J4X04}5GiZJ>1;-O0D< zU1k6U>!+g;KHuupHPT|Fk~h?rXoc;as4Asz-o&ta@}0AIHh3dPDkFVQdvriJ#>wKO zk6*JSS%<*e+g@c*CO)+Vs~J+;>|4ZXO`fDK3b5Jvv^GrGp9H2?SzDLS(u@kz;2Ac* zt`Tk!beDGBKy35)*>cGnf4uz#{((!~-m|D<(l~8IGekw(3mIpZ@4>^;hfY!jKT#z% z`OynwWpYK;597SmFDR9r{+_JCJPi4B$w%$L{wd*YEis7DV_w~o#${<4ZUT%zxH9zH9S8|nA~%~$%X2y@0) zP=W1!AlQKV8`IhoJK}Xhz5y#iqIs8s(ue!G=7t7iKTgKwhJKdL8Hw8cZK$P%_M3SF zPXC={k40v=pq?np(?>}pVG0YL_n136fpq`1r!Q|(4bnIRLjSSsgxt<)ai_fe6&tGz zpvhx%tqGZoZzfiZ!ib`|wecaiV_G}@7dVo&M&QbJ0h8+#K9Bc3tKzpcj=^#kAt9WS_97&ko6DiH6LVkl>_~dW{zSX zPBLvs8?rugtU4fDOCb2?M)7>n^mpG3>KsJt&V1nZH&&_FE9YaMQzSlL7In4g*XBw_ z9C2n#T(-(Nqp^fMc_n4M2QD69?af+iV2z_ZG%idO%=+`vtO@GbaZ;?`OY*QsmtWj64@Pu?R2MduRsE$NrO>hmew6N+b|ZZmobF>6|uEyZE9T&=V`ug@9e ztQ+li#p5LKzptJ%*4t2}ScF#@U1B}VC(=}Utd*$Z;D@#Zi%!E1T)LeQ^J&y01Svry zqyzAfS2-ZgW5hxhTEK6lIz~yh91^5b$~M% zC6!L%ge>l*2lk3%l)HD$jdG~=-qkL<`>y-TtA(cZeGkus{aR?^U8^!sc9uQ-I+^|J z>l@?n`WG+h-5Y`?SJ_@g3kfRl4VTed0hIZqF2nP#Zvi$j&0cW9w{D+gMuc+Waky2y z!=vZ+yUXuKT|MulVQq8I;`0dnaE%`sv~Mu(@Tr9aq;z=hW*AVh4kDkb!D-)b$UN)4 zIY1h9=v84=*to|K7Vu=nWB47Z@2<#?h|rK?GMzSWeUI4%5XIrqTi5q%Jx^_m4_oQ` zs2GACGqaE+p&FjQtX@R1GICDcfpk2`cR< z@z=uxL)18U2^&#~d}1<20du%Q{a4v@ScX~k$~bTP_RgEJ70TNahxR?uY5vdqD`D6^ zCNpWn{p%SXIO4K~n{HfuXxrYMUW^;XetuClo)PX|=x-NOKe17k{E^pOq44fXI}_4c z7|GX=>+u_AOP?;LxOFyNlRq+YAQ%Xb2MaI<6@H6H5mdS4QJA^WZhyLE`8CVv!+Rco z&guTHwW-jR)#J;RcD}wCI!Pves?Y6B249cHGCH1Vs^w2nBlq`2EN>@5p{|q+feFut0AB=r2696rOkK=}rJ?{}uZx7ZWH&*OJb;^7>C$SQ7%w zNRDnqGu)?Q4Vo9R7|r8WRC?0*glFXBx3VOfq#veHdGAuVIpDc}yUK*f__6R4?y-|C zHY2L+D0PoUW|`2Fiq7Uj+*gr_?B3>zfz>JkP7}$^4b;&7^LF?)A&#nBfG=4ub8cZj2Lt<>^og3mwQ1kyX86 zi+N`<+4Ep6&Sn6`_9iv;g|ovPGqDzah~CmeF2M1H20f8bP|fI+yx)kohE&2e%ESGU z1ZafZ{j#z=AZ-M&u>Xmpdm^i-p$3z#K|1UK)ipazfVws|!DP@}Uz@V%CtYY(+Vv!Q zUHK?Q%|jNVySHOE@>Dx5!5)#e^krg_CZIg@XWjjxHaOat|X zg?|@C%b+G@5*`o>ub<cX#VUNcn(2gtuJN zms!wyaSUwQiH!2d7gALGAr>hod012Ym}s|^?U69zM=vf3{5c)0MO}Y2u+!t8KyPA2 zD{h7Lzhf21qUZi*zO-hl+!Da(uR%hkjX&5O6hLX?_tD6$J;Hw$sPUmgd^CwR*5Bm7 zv&~;ZoIdl0rM3fOrIomwkN;xv+;9|Q(h~+@rV66NsB31FYAb@NXKD$Bs$pQ5$;)rn zFI{RRsvAwf2Hf)TFnaO4@MtF4>4QCkpPky%U1b(Ed|9Piq_4s1!g|5>1s&A#mCLl8vt#nqRx|VEWgfKp@ zK&ESv4TXis6Fk5@B!+)jmU&?B4(42(eK*PB4PlH~pd0;kR5wk1$67tIJN_0{5<9@e z=bq6W+QW3Y5x}_t63-MEJbwq4c9B{x#Dw`WU8;YQC3DHuo zl%00ZvwJ|#?*Ojjb%VsdubOlIQ4W2-08oB1T~DlE#X3l^sm(f3YHNbAsp=xE?@&aV(CZ?sIR4)3KbkYz2_9&pyB!5v6g|pGIcGCsW>x0=lUg{ zI*srx-1gdU@ak^*92p)Se8s1$G0di{?rs_SmD0HPwu`$ZPaT9SnZQ=kvHJk*?-#H% zMU{&6=0%&Ed;ab3n>ej^T@k8PE{K=~<2@@RF_p|!FE(0an%iC|eN&Z$d~_F^4oQOp zchDG4>Nbl<%?h;1?|d@pMFQWiT%}T3QO53qC9m51Dkc$Cwiomk*P`$^vRdV%A10^m z#t9IF42dH#YE;wvSNwG*?OxtET7Ee^1S;n@2p-GZ1SWP?`z{iR(Unr=BS#Ig8vae^Az-~F@Gv{T}Ogzsmu&grFItXS@3( z?W}feF|JrV^AfXA*(^N?*<7x4rj^W(D@Q3WP1{hd;bj-RY}*m9(Tq90%^#*joLXA` zKihham?JkiV0q<#KKb}u@}^PMcQr0LFn;q>^EfG@9-f6OWHwiJWE2m2tme;-pOci{ ztgo#Op&=luAIV{1Vws-#hBo(|%$*7GMw{N**nn}@xS5!PpeC0tekBgU?2~-1D4w{I zG%F!{F31leT#agp2>_C6h5mdsebLuiXIc;Jcy%zztT0d&rFg@nzJ=>G=4Fl?NJzL#pkW8X+fFf-`^SK`ytSk{Qgw`|j%r zuLoJz__jiYumzrmYp_UHsz0@ntU5VI-GSl{DeEGCU))2({NFn|Sbe(iHPPl{o6GNR za5x1Lzg(yBmA@RoaZqwwX*`}EK`Len^`50-t@XvQ)M48#3u6;1MR?J+uedwsq$%}< zUsEZP>rqc<=k|AX&p!S(SXi5cl5aypCESaqOEwzuG871vd5xJC-mn!-EhkiaH?U?;CY zEK1(#zJm~E5R`Ey&Dp!&HHxWNxDu&3%zvz1VK?S(^Qoyboi5v4jux}J2KP^+a)5&o zj-_N+`Se`@NjEUquA-O9UrPs9(3M=ywG~2*Yw;Jm);W_v`)nUe{XJ zpLurwgst>Uv;UR9mwBeA2Lftri8N_&x|e$DdZX9=IC`GQNlz-`E_1w0yXqNTztFV= zliMLN!(AY`{0J+U_MXbuWGSVg0uB^nB!VTMopg z2l$*R|9t^i?9AY7q2UH^7r+=ouL@de*B~eSfZR6fm!~fH&ep8nDbGb|ZVnvL_`Z;Q zD?=8@7x7R5lAvwsXEtWOXw681>>!8iWP2>{Dqf?JhOaR_L|Rz48A_Sl))9oE9Ix;W5HbA>a8?zRD~n z=BQFcxJc{BVkk|ig1h{>%DY}nlasFZ5149Y?c;fY>F>`1ZIYQ#(O1lSbTOHLi%eSk&&Yc0G+*o*e?BXB>%oTAlkv(vm-i`DC$45)LWI*;U87ymJxq~HjzHTlV<^u3#=;fS`LSZ$r-g>774kMrBl zXBv$XA7Ntmnr*Qf7kc~Ce;6rBQQl_X3Vv>kGU-GVU3P~T1n(d5c`+Zz5I}e)pCyGJ zs0Fm_6#YqQSLdvVZm+Remy^Ne0<-@jIp@A9_REAoo*iqdDaOWa_}CSaT<+w1B>epl z!DJ=fzub%!StuI!D>ZOQLG0@*K*7M1?I(KmoM9{~$Qz|(MgkRRJOM{s%}#a$t6lNM z5%-Vr&I51eV6UF_bcfQ!AqhVR3!K#t$aeH=P0-k_HNym7 z(a&_dF@^yAw#LH5xMOlgAGZwKqUMmGiOdkK&_VOn5zh9Y_ILQvAyGuIcJ3)<90Fv`CF7+uv{td+1XIWd#(KvMa<;bg}{TG|6 z&hy*>KVOJ*d>ttU3t`YETga|hk=bZU(<<8VvY%ync$g;G_7mGhxI*OQPjIU583M!) z@$HXgj{g1G+FutMKCYsQkllLh+Ul_iuUl$)E@txrXqUjH@4Odj!ARivXhtZ66znGH5&Zr+)z5C^n# z_a^Z@%)eC!%e{Z+&Poy)lY-uC@Ryz*4{?4piIsszi>Xzw|K4L?>l_rD0PAviKIrhU zQWezzQr|r2A=UrZfsJ72@F_>P(uToCf6?+9PdOdgD|BGLem>}k0fu>13^_(d6~Jsx z+uT-XxL0V?*94IZ3yDgwuoiPGLz#ok`&=T>GdLzTW=!8zK{_#%yJkg2hV1DHvvW4| zPw$rqL&|r$j{{=-HL|BJ!r<04H`ims)QWd^`F3RQ{&k#j=lz%}htc&n>xJ$OawimB zHzWLd64l}YxRRu<_MP5ni}=YZi%HmHrv}ASYS){6F+MU*=F79h(|ERKlowno2N+Ni>bIP45 z)zZ=)|09e#O&v)3*gr6#C(WqIqNRNQPOH%AK<9atZ{0zaR#?`?T^DkxGzk1!YzX9jw$?P{dYcC5_NQo|6jOd{5Cas|BtZu`!+kS;4*~?WlrUJ5f$oMqt~E z2T(7di0ZV#YP59EzU972D{Ta~FHDc1?=^bpY@`U#Vug3$dxILSrFO)T7{5r}JG|MB z3jc5xSU)Bxlg=M#a(7lVfApx$fr%bx`vzEJzF{0=u?4|TaTHLp_@5>2*4?o(vmp17 zP{An|%Z3smVq&GNoHzhS7q=uqNFDXI@FzzKGjpU`T3Yb5z_tNUY~MQaq;P5q*-!=> zj7$esYl*;M)4ZyiD+3}iy$d#*Iev%-hyJsKp1x@%9HjWfU}~WmcSa=xq4@vEc|wq; zkFv+c0}(ytdTBkViV+YDa@NOikP*kj)ZU{evnTpZoi6^>T4iMKmSykC?stU@A`KF_ zZu*Np{C3jUn*k(GW~Xn46=^+ece)}^!rsAYxTn5}B!q>h%nFoo=^$gUKZ3)9R zw#68?a)T!!mfhvjq^M8H1m|a3KZPy7w?Y@;6Ttw1Y*+P|oE~*=N13fH<_Zizp}$|1 zcbwDuW@YzgHli23R`|Rg5hEFF!xN)DFAbe`@F)ISAOt`7*Pk@7X(Jw-~&Pmd*t{}eoN)m?u6Ub+! z-N9&b1^_;AZjWChW#Tyn;2cpgEv@9&hP^+|B9o4EGq8M|Q4!rbD=4K#j?X<;>?lOH zg*c9zHW?Fv&}A~luUnmX4muxy)vHI1j}t`yHVS17G>1E&Uxn`8_{!QxGg_|KI4|!{ zNaA2XQP9|>q18z0`eo9i`BEi=`Dr?}r2Jazsmz^zA0vq)@ZCF~cS4RP5K_3WY|Le` zj`syFlcCahSyaMW?q7w}&txpag3&6Iv`tVQ4Gdmt>heYGh%#`?Y65n6tqyyc2f)@R z0EK3XAQ7iS&F_AwyqAQ_bdo&8SS8O$U|E4c^5Z1Y@9$!+Dq1eu=hcSOUJiethY_?x z{i6}q{sHO=EXyShk{;z~#LHy*t;{EJQfdi^bZ(*=tU{Hl=5b&?!&cm6LgNhBeXx$u zz|!Jbo3n|~(ulshI>q1LTaTFPbok!lE(kTopy%S`>QQ$?VxkWh$Rgd!T|P>MId6Xd zwoVxJ9m?+d=5UXnXvp=v|2X{p=#GQ&DBNN9y|(U+*P*V-FRR^PAu-m1d+JWDD)s6N zjq`d&*tp{J2dta4aV=nt*XaA33}ua7FR2QLhT+kl+-kz*6G3GgDx?+X zxxV%|6P=;jXz8krH^0oPyXPO~riN1^%Bp z^7h}G39g69!br4-U%jcfdo0*-8KD7xzAvqM5;HS{C6%yT&6JEyo1OZnJ#Ivpxk93G zTLPLQG*FznRv7aE1SRY4jXY*pZh>n~>@i{R4}o}Dgk^YLpi#oE7HgHvWNPz@a?zF1 zsalfVLbVN5oFA%iycqF1Q*~jD3r@ytIpTbVcDLhcUp+qkW!__qzQ^oVI@cI~HHxX2 z?S7GYoNu3@2El#d^n@xR3My)HLydR={bae-OR>H_UDzdmW!w&$k@JZj71edhpuJCz zIh7|_$Zum1XmkW2KCjgka8h7ADS@R(A&+tobt7y3yBdGJ(t{VLCi+G(jVO@C9FXNl zZ+7l7z1yYaXBX$cO5^EPz5wTg4OWCaLYLuUqnk+b)moPkN={^kxA)`gE7MJe&*83? zLvvdM-qb(`+(KS&k*$7&@Q(Ay8YVVaHP*XL-=UwJT5|@PVsjX=w$|1K&^V{l@*A%c9=wA5vl*Qtor z$VPzt2KNx*ID?_XpZ6lqWF=#II@=ZjZp|Wjz ziN`bdveNNNE18UMGP}0MMNq*GJF{|>DOLy~6rP6hc6rlKfk=^C9IYxU!9&!QLr1;f9!z|&GrmTYLGi~I|B|A2U~ z0_J|Jd_QSpT;d`2Is)+aUOS%w_(!SGK6@D5xEu9112}5=PdR@q(Oy*HT-OvP z91AaN0&r2djDKdw#0-9%;m@-&=|^Jlsca6YGJL>fFvpTuTp6$NQ^UA;pwZCKaK<=i z1_JuiF+_h#=A!jQFgR~KuCpbIv`M%jKNN08prwsnE&s`GanxpjS*uB4AsE3a=O5z0 zL#h|09?QPP5jqy@+`uKd4!quD5gKpf6^C_U>3HwkKEDWy!nicFv;hwk1#MN8kzp?_ z1^ku5JU@8GPMxT_GhD4Wmg^GXc#~{|F9m+;H164I#5lURNMbW0u-FhHo*v+y>9(M3 z0-5KWx48hEuRHP7kT@zwVSc(~SGT5+@hQGPOzttSwzT=<_8sfgef)|t$J*uHpz0~e z`xo<(*x2ZtCl3vrbC|CrOjbw#BUe9?g8HQ?-Zty{=kou{*R>fS=+x&6^Q&7MuJT?+ zODy1DSn#rVxAM786IvkqpB}^TfcFQ?7g>~x*2))lz!X7OK>S8y= z*rgLs=qge1C!)H42X1c=iL1EED^um5S8kw6=Rt^1p57kU5_!JWx3c zemv-Kx>W_4ECwigo+LhGF&Hv&wvv$M2?%R<*q^RHlKF@HG~u+BIuU_f(0sdjoZ8-t zXfHkY&TGM74=x!J!+y~N_PdbJnRf$ zai)JdKf4aQvtMx8Dn%BbditnAkm(6@l@TX?b)nw1{rvd-4KZt~&VY!N;T^t^;2iPu zBff*bVr!cy3OxsGB5h3=(q?;RJ@s+Bq|`aQp8%N;ZWE;K^*Ta<&!Yt=ZK-ufv^^1})sUVU#^@+hpLLi+XsWAJibABSEh zg{i48_~|2gu?jo&b}&UXsS$$V8M1W^2dA0FPl;@Mhj(U>jKwlZGey{Y%g_}8kpOGs zv!`+A@T>n|P&(eeLXzbS^fLyiH(~trg##G1++Vl9TW)Zx)P#J`nYs2cK1A_@^|_{9 zW3ECb7^hP&3V9qH47b5eE8EL>Gmc0s0H>2nKIBIN$fN$3$2zPkQ`2N2<@Bv+z(DR|XKK`YX0!6a2ZAIS$A+r>8OVjhi)hw*2+5l_)E;aAYBfqE5LI zu#}{>=1`m)YhL|K1_$UW?BJ%64so7T$gcq4$9)Rk^xLhTp=31*wiW&sh5#XQv{s-y zp2OAhCx2jNISg60EC#LIzCO#V-pg}KPeI;~!(VwO&d8I`UV#N$1lVO^<`bA|T!Ykt1y?0MEF}cFLS#>tQzJ2Q zHkClUVR{0Nc$tWq1y`U&1}>bhrsN;C4Ezyd)eJ*-Ox~z{e%gGrqE8{B{X8w9#g}w| zbE897qTPLlrrt$Q;_NhjDA~c#>x+^MMLCK3q)LuI5;1+#P(b~H|Yia&{m|DEdt;oDt=heJ2SnnC)>Qf55SHn{%=(sV_TFUkZM?$y(0Ne?3 zs00alapXgbj@RoS{6ArPexLqc3Ft}v$@_B3c!UsR=jdoy*s#2v{{sK~h#vt5*$w)4 zygxin3u^GmGnNR1iN>Wd62jS|m6|kIhDbi+Qw+@=MonewB68`vW$wzGn?=Ed_0?fY z_Ijp%z?k1Al9vfVlZJOiHoalf29Bq)E@Th#Tuu#bOKC-A^20xFFu{Q9o{34BN=Zq5 zf4R_(R^RwU;G)Gevb{75a;t6l2`f4Nl;&o^=$TGqMEn}G%%~+zPw(V0@OT`$!MA8Q zWdC5!qZ)v@-?AsLU@CJuBhiYnM+926?TpRTWJm^OhNBA*@IMa;#OqoI>8a4Ae}C9H zkJ3hGdqa4XlQPs(o(OS9)cc!JnIG+652$nC!=H zeZhZW;!DgHs`Q1R8BtiHc{V3y3ru-t(FVedziC5w?jD!qh&#)_dcssU1&*qB^z085Mg{7_3Zf>f5`xoH{otZkpZ2+R9cN0viqoe2cw-xX} zEPpuI4iwXhU+O152rcA?I1+HG-@|qYqEzk zz9`erC(ECYtSLRV&UH6RWMAEnq!pKpP(AadGWn0O(h1{#Jd-qk5-&;q&j7haNt}zX z-t=bvmwU`i=b;B@9Rd%dXpFK76Bv&}J7yQO-zmeYveeFN=IKF`Cdft;4GJJv!!MxdWIB3%-w;{1l`lLFI;YZyk#-*N{Bd`&i{*SORN4pVncu4^bV;&KN@bQ$_f6wJe&GRi<$oSsb`e5SdRH`uTzC2ff$X8&p0km{AAPS2S;M2B?9O8n}lHAV?RFoQMi zm2g)!!jOK5FIf3~%)o;SUXB;tnh!E0n>{Ft7Yfghn9ewQkzN-$se+{+0OxEpkm$H*Bo|3pg(Y z$}#}8xo3I@&jME_bqpl$;J@!222^!mbI&TO{SmIt7mj59D9f7gC#Z@!KrrTL>0e$z zgoGU;*={#l>bT_1J|P<8?MN>+t=zgRw+DQzH;>QW33i7PiiC>ObUfe46>>=!j2gw} zdlH+c9r}b+hAwUo@6Wj9T8BAls|it^_+gY}4L!5c)7aR^_JK;uVVpzICvw?z!vInP82P>-8d%qIX>~8OZ%6RcsO^J-b zV$fLL*WCE(FGy`@Ev)d4!P*_1Son23Azu8{usK|yya>jw&KK`1-WL%W-tKW49)I=H z%74H~#1e`$gCqCQf+V-B)qPPli6KP%r{hRrSm6@j>)0BsR}T&z%u4)V0_~`HK!D zqQMj>dC(EkYucTZ6tAQ;gqv7cq!RyYL##|$%B)m^Tj7w%>4ged$>To&X{vawpdNSJtl9nK z4mQXqLuJ@Gdk!zqGqdM)=Xt}nh>j%Ew=yUxNh@&+OR-Z1$7fHy^qr`ZJ4QBiGS_iV z@VMp8-Ol$WOlon9@d&Zd3`ua4q+li*fqsx2;e~j;^~ln8Q9+)w1~s@<^P=Pp8_a=a zPQ0ksuZ|Zcrb;)q<&@lP)rj=PIZC{@*4FOHiy6fGhzoj%7`%6Ga&z59J=1mvgZzDb zZmh`fdvlDtk+L|?5hR1|BMMt3^y?P~3lv0K?3aI|*0v6G zWd(ITxGS(RHY4saRlOPuwmG8`MT#T7|0LiJ(UzSMm*G@#nEOSxEle6c!f%FiVKdoM z;h~!#6nVC%oZ4;7B(>hh1boq(p_qpzueknVKuZ)87=g_0BtJ?2%nXpQ#vJ7xf~pKy z4G?B`wcp?>G;3jCV<=~y4kLjR8F}V&6yMO_7zwZ--cMvFBgERe z3Kg^CxLdd@ag_qbATe=LPSS|j68}ZXK)cFl*KZ+Qit`0U4b|Sy+V3(ae}w81P!BICpS$eQO{Lb%;}` zC55=E`Oo~ymMgXbU|FZ(N75Cb%jjQ|`>)Bn&`0%Y)i z|Mrj0U;ypRAT^vR zkA}3&P|MGJH?Oc(t@?mQyp1k}&2YZ|_HqBH&h)w(INTj zCT~yt+ZunKqZ&NNt?icW9qRLzXsSMc**ZaD_}dw`Pa0n$UeF*+sNXIKFs zCMGiwpRkG>G3`?_OTh!@>{1!YyJt^mOv8%o5OF1u+Qgs%u-a3!X)p>$WLsK1;4U%+ zYMIM0zw9&=s$hZmB*s{6<}_%M#z}kmMAR<|A%MIjS^-;&P1 zuN1sxx}SPf2#{X&6S?G{SFl*d6zR`m23_w4WbL!jIG?Tn^ai=Gu$W+Z;(eV2svwVy z79w_~4SVZESyE!uxNmDB--eg!Jxf{k<>UW5xO&#bdtwytYQ5Y0%@^3UxoU|rT%18c z9|YTG5*8hXvw>9^q2n%-n~NI|*cgkclSc2XZR8m?OQMHjL>*&S&AOnx#P-@C?MqsVNqi9!d0BJExi zQ|9cYhEnr7GXK#S6i3l!(R1+lU$Db;=bVO5RIo|0Od5{8s8Tschz7rC+*#tlnKlwc z{H|{IUH4q_rooZ>@V57L1-34CQmbRx`zzrnKjOsnd?H!pRq`*?QsjB=9D|0BwRpw1A zol8%R!BKEs2ArEf#`QvTi)fQ4q2bY*w04M_SP!~QJUS4s>Ge0|)RA9UjM6eH!HZC*hw zHU&BO%-VZ3*bzj~h+Hk*;g}KZzxCV_|9c ze-OMJBGdKz05e0IPJ?|a`x09nZ)+iV@PM%9aY^YxW5Kkjgr~8+)_DDcBZT`-%zELg zq4!^%O*)E1PJ3+Q^)l1Ps=0R{a-h=W(1MqGRQeOth0#HH!6+1u_(|lr2|@HFDc(v~ zA&N&=glF0SZ@I}<+p|0FNjDUQOPKj4xKGC8?#bZ#Qd2u>N9cWMNqPOBi5&N5bTx+6 za3x@o1p6@p0}y8w=PcJiw)&|ha5%d%^fHoRHXLJooNRRX=7+H!UraO}Qt#hcq^t}H z>Pc*WJ928d=yYDIR|o!wY&ntk-u~;OxWy|w2Yuo&7E(@HX|tsTruLDZmqxBaIdq^j zYAUg*)&zDV(3|ZsY|`yCS|6ZHxInlF1H3B&UG@eP@x)_IL)vyQe)F&BKcG=Ms*t5> z>A<3O%?wmsB7eJmF00r-=x;R&3^)_D%JXqxY6SBs z>dh5`e`GvI>sXoT_zO90-V>LFO4NQ}!RIREEjvM)kqdjo!{OqNNxR&R*!o?iA>zBr zOJzXAxOvKy{!~-oG|TO5kvnVVU`sdTIIFR0YmFzObJnh%vEBOI9EfCmihzQc_hi>|+!Hf^0mS2>ortBs5l0H)MD z!cKoz0`U5d!^z1un94u%))B&!n;+G1RMKR)u>v{m-fU8{TmS7=McH7!XHa@xryGer z%a9bil#wH;mjeM8W1QGoAmUAhSnz#wf%Y4LhpX7lqk1iH#4E^?Z_EK@eh{Y+(8ss9 zf_$)J*aR*o`${_BpDhESGJ;b>VSJP(V##QF|LaNA74#GuO42u$Mw$YyNRn8@r3ng5 zWVuoZj4(tjB@~|EQeC~K<`Mee5efh8O`aen$uNP!g;dz!g&>#GEDcIFDtk1+jR8UW zdkQCF;XQTB_b*qf6#{E|1O=(xIiOh_Th;e(_+Y3(8?R8N^1oUy%)GdS6``%4DqxP$ zq~hyuIZ9E$z|&n+3pj;?sRJOxd3C?QIQ!1}PISU`Y1+nG7mP12ZCB-Z>WEzGE1L$qv$J z@=0HZ8i=Y9S*!L)?RrZ&V)>4YT2rgu%T~u%%!eH#rZs!DUcsTSdW~=RuG-?}2A+J; z1=FaGP0_^)!|#;6>@c1x8IC>Q0UuI(Fwa5Bu$8f#)4RwD7wuhP@3O90(Uu5u%)g!< z?uz==s2=4nykRb7t3R}3!kLm8jroWOg6v%Fn+whWvR_V9gxRU?KFuU>t~HV)U?J~# zIQ&{O^z8k&?lu02lDv7LHqVov(JYetNr>NWN2u4uh6ar$k!zoeYudyxcwnTS>v#tW zlMt_qmE`-0%c3D%kWpZLs&FRYicv4{3})Uhk!w=JqqO6yf8HcQRT$rCl+#t{cCU26 zM9JAuSjF4$!IK#!9UuXqqmXUU%iG&m68*lZ&`{XRNWul(-YpP-UH(&>Gakw{=i!?Z zmNyNwKaQ8bfJvDn3Lz!^O1ren?m*k~ld>8T#e+4x9wm=)CQr=0B?npSS{TLoYJu~Z z_$Y6_idZ{%l#74^G~A)f&So{b!A@dt*p(v5_7kR8^Eo=rR@CBqajq&r6^0m*B$z84 zaD8zoIXSoc@w@*@@Kq96StAE1`o2Lj6L#a!dvU*tTC|vF{0XE`UFutWK=&M`( z+b-KtX8aKF>+*ZDRaZ}x3hdfui2y{HUREO<7e|4nv#99&Bd60Z@W_qVZ-Z#!xzP2a z%4kSCr;VO*S_YvYK8ZYztaPbeURv_=?FTWvZHUt=wsUS2qfjT4=r8Ep548$h(d^_r z;+As9&M$4@*FPlUT@U`X_qP6wHE+zrl4rx4ZXlE-6V4*0eC$QcmskN#D|tdh+y)h< zDOwPYg$&EL9%fl%@G8FkeM z(Nr&%hpgsh{FQ3`d6`oI%R;w zU8Mcs!4s?3!%9xv%~gUK!GW;UiwK~>5cZ!8M}ryrcO5O5xpy#6ZA3 z#j2BKY%XM|eSG8X#z!goZYYw<3A-tI&S0PIA{j?Fy3b7K)zwyVVP$isr8`Cw@AYEfz==clD>b5lCY{axq2h0c&3|+= z?k2mUNw0Lm517-KLK_8t6?)$%>p(hwMHQ zb&-ZrX)3sXD2uIaAVN`sMFD3_PFm+VdDJj{uF6{kg13b=zrBkxw$R)Nvt$aFQH^Tn~`;uHv8)HWHck)y_!}W z=qGeqo@~N>WLwhxaZZ3YrlCJ^w7?-2%b>2+(jFx4Cw>Z%;&VaN=+H-}_Ump;e+ zYB*x+otrD^b=jaf+wjHD>9j9A!Plx{=*4C>Kmf%OMhV)Q*7NtXCUo50K6&jkC%WDD zh{VcD;Bo8IO)PRj$;%K$${$qqn0|VwJ#*4EL4xEnqU_Wc|5fmX&{482sl$MlOQ(JI z5>jn;{fcOth<#2;-%R>DEJX3&`(`Rl1!U6SsJ`;ERk#W(P?j3-K0cJ752n&$Ax*o7 zy7h__oBuwCvV*qll^Lxake_DC7y=aZD|$W%ENi%_;d--(K#=dAO4XGPXSX}D+b2qDoY@Uh%ZsAHYDC*}SP=Ha0(WISvsFfR|-tcCEIX|n3B z^#ST;bh#-;~e_IFKUGC3>=+^4d*ekN0 zzd9(wkZL#qfT8&U)M7cj5Dj>at!M$hyfyY>WdRu<3kZmLe07KHg=LJdb_KKd9_6QQ zUPw&qwNQF^fxVn(1qn)y0T*09%3@iu63_?G-PAgi5y(33cINKYXv4ODqS*13^>E*) zie%yRRfj+kMHF3}$FJI=&Mdjbjp_Sa!mO1cMBW1*QZtn4b!@HTT$}02b`JINItB1@ zYzY{R?WAshjrPSnKPt436RyME%<&Of55n0V-H>oTjaU0-ay7e|{mIoj91f$Eyi$WPIz*Y+Dn^8frLNm=}ihP=&N&cYSB{ek5Uc zoV5(NdZ0}m^HVf!ZW@=v4FKjDC&f8dsZla!gyA?N%V!A1UjDG!Xo93Y>d;nn{%Y;A zng8IA!ezoI^JrFrFcba9$^EQ9`}qzDXuuwG{qTeSd=C8qMSarS>H(`iJWZV{YFa08 zPZ)G}3!@b4^FjTf!${IJW#pEx=Xk=YYKgZDr-_6Yu)&4%WO3ij+2IL?ufzM>^qI=s zCyfcmhP0O@>j-Q8iH6kk4Ue`@I^wYZQH*#Y2!y*WtaWh^ic}<<<~ycYwB8IV!M-yP zVUM`b#=5-RNS3QhKkIJ0pcZJy0pC8tnEtm>ui>ay{9qnc7Rx`%_r`3sR!0zNI%EX? zs3#oqHw(|`=qWyx+nrN8i(tHs!g)vvXmqjK0FH&GG;J`RfW6Yq*I_20glkZ*AL9`on_i8$<$w_qcP5VZVgDjU6UeS1{*A1wI)TLVw&C^^BV)_UN z_Vd|G*4wxpL|Tcf)=3rQb!hS9l`xnv=33Jc60Jgi{h+mKiT*}2pW46n*Oho% z-Ki}RUJFcy;;|=sXw+d)$0)vV5=p2FawtH)2!PAR1_W4)YAagKl<(Y6;CU`VHGv*+ zGkvk7LO733(euFWCDf+!EeI2fUriH=NPLC5vc~N-oIdQ7q$QGFv0V{*B8^ zgc;O)WB^eNLDSCzP&Rm8ETGGzt7A<|8mRb#f~%k7e!;ZJ09O$aSKV9OKt)t`7`q)Q zk4ZYWf+O1|2Rfa4AnjFF-F1dOAK9;$xY`dek^DWi-*~@A2b;tJYXiFIPm~}e)j=&& z0aQz!Kx1xwlvY!qW9<=N&+B%`i-aZ>(5^&zV=^Gx9#|SHRS|^G^p(0)8ZPg@pdo* zPaKc>5y-KC+F@2y%eW8__?FLHEglXWA1uD~L+M|s{hpY8{e>%Li1h?I~ zgD=7R6{@}5l&rH$Gb76#(t5lyrs7Vy&7{8Lhw(Pdlo+8LK9SHFNO>|MD{?U!=}Bfk z?Ic!v6RwtOvZJ(JOP%HJ3HCEKnIQV?Q!}!{^Bo}9I^VZ0HO6p;Ye_A(FtF+-O{9KQ z743%;Fw(Tj1xtvM&@B=Dnmi#I!9qPFTkN$ASC7w(FPZOcXtEdPgro_8^xi($QL(10 zC}inqV8p}W66>gf?-LR2l)*hH$21IHscgV5qaKl*qV{9EJjX&A-F@38u4BiGvqC!! z5`t>p1Cp@tA*PKJ*5!luCxf5`y1}061=fzVYq84WRb`DOX~CIVxrQZW#l+S4Ujq}Z zqdFcQkwx{*hfW*zY-)Mli&>roo zVrCgcgSm$+3b1mm$q^MDh9=`d;_Lz4DR_vl0yp2+?%oiuiAZxmxD7Isu1iD zf+8F%`R&O9Fuv|?140uG?n4a^b_CX+nk>_OJdA$MW`WxyV81aA)AFOQpGZ%F&5zT@ zNAE@Aa$uw7*kTk-ri8fS#j%Pi3Vl+og1 z4|{~J;c+`;r?Q!&-l;~g0<$bzhrUP)^7ne>dtKRKNoz422b)5&XH8toPYA*;l5dD` zK`9xUqr*9;J%2AT*h6-_j2)0H2(*uk?@XF3CSDAal%&!8T{-G*sialEAs__7rxDO?M%d??GTMV0EhWc{JC%Wjug@VQsjTbS zjW7QkDnmYKjCIM&TqD2VBsEf!kr9}PGuK=>QgSdXDk1G?->Nu?3K>ux#PsQt@BKU_ zh}dN*i~Sauh+!`34f%#Tz;9n5SN3B$QHsS>8%RNs2e5^vbhU+wvrt?Tr;~*WPeDJ} zu`pzg_#J3-c@{l=rSPPf;`~##-HL;ZnEA}9&{FkoIEr;LRC^M(ikj3d$-@}zwRbgv z&96*P%h>pwHu1+PUSj1UQrA2QU#V#-6Z;7#R%^BirfN^hX;w<;@Jaaz<)|pRBl{~% zph8^)pX&`z6$w-*L#!wA(D#=HULts zX)f=$b#;;Y=;K6A%&$+-Bz}#?gPOpxsc|j3l^o7cjCH2&peUr?d9f_Z2gr`nr74U< zpu=a%BzH9K(o7Y~0#(3JtDVotPk8u?`)=m&P|u}~OshhBfmt9w|q!=*igj^Q(b|KvVPT}`7Ei<$jJzpH!*ESHw!=a)Q_1IA{Y}`N?jQqg|DGT94`xk?y za%#jn@~YR=GF}m(D7BV>>n)PCHNqZ4ZsRM<5QCTRf`9R9Ct%Xdj4v9d9IGodI73dj zUwAmW4+5S0&t0MP@Nl_cDF2rxe%bunW5JJ}I~2a8>wt<=&WCp-uaA38r5`2*eTxLv z;+;X&A0N4O3h^?r5nbTsaIlQ8fv&ZW!r)#3X|?E*7Y^LW4z1>s^rV}uT14ZSJOt5h z*07N~>aMb{{5aS`vgTs!zsE`{4&tALT)E67fXio&N_aOCi1;hSB>ap|}wXeGbVuwtLq z=-mIKCuVW9JLOqfPI85rCyMGr&9yS>=X_Fge?=tMZz0ID!x%wPm z-u{gc`GbT~G|6UhN)KE0qTlWfMd!x zhw9@pXS7a@+Mu+Vn#^}yS?xX{Zp6@fkI&aW=d%sAb!ZCnR=UFbpfdsN`QOni1YIqA z#CuL|IOeVD!)g{HiGS7)NhuO?{e~m5`?2Ad>T2!Cm*6F7D)K1{=)-y63^I0~HO^-3#WR#_pz>Lv9})_|2}dTIsN&!E@k)noJ_6 z%WWwe?i_;}n;^=Dns>_YB@_%U1DWJ`V@Cpc`bQ$}6o;C3v()(q%HCgVo19(WHUrO- zj>u(wm@&PsFkSGwhb3{aSfde!2BVae+|qK?o*A%k1B=0E1wNnzw1*}1BeUI(pG{}j zxxY0PzHs{*G6221{2IHO4EKwFxdFX6ZgTz^^USyX@p|Qv5EQM&(2X3DxSZ(Qo>Nz} z`X=at|Z9y!9Z6)-|Ub7tKA+jq1&v1hs+3- z9NNG1s(4!&Vu@|!9QS%u#t)Ylg&We`m1LX!9wjWLrnm9a*nr7$Y&<>iwzZ;sp-4d2 z>w;?_=((?u9(2ry$7s53_%qJNTsBrW)k}}8W%&;w`#URpZH#PfFA^D{FA_5Hu|F8H zgjs5Dz0bDklhdGX#ghwxIMS&gH{Xy&`qT;D7>BPF)ByL`#9zMpX502~N3+zL!_8{j zgT{>}c81yN?jxd?|Hs%nMrRrZ>zduMZKGq`#+Qzhj?FK&ZL4G3wr$&X$F?VXpR;G} z*=uIj`T4wmtKM2w&vV~b!Gzb}BGi769W6k{F`mwQDr_&`-nAj^WYiFoZDme{>e|a{ zH)Cc)2h^Q6zRtArWW#D$-nek2t*(-JoG0srDr6sd@+`>+QK>|B$ah4*=3qcx#kR>T`gk~=6D75vWU77pZ=i1C$7 z&;tE7&#^*SPA?{Gvcm$W(nZ!nvG+^3sd$sloPv!E`~tec#&T7=JtdHnQ+wN}?W>I% z92(WCfhBDLA~HH$?xhD*1?LBLaeVAC`@cFx!bZhaSR73h9olAEMb-$F?2?-pd@tV}=Ndv_Z z9IOlVBp3Yky(*E5MZ~XHQ*r_llUAOSP;-t#SGT?2mYoUUZ#zjT%okJ*kthqUO%-BC zfkhV{yRosIvjq;CYRQRIt8k6e$yZ;TP)ax{e(BD56!FZvTUJ>7>h~BBj>ba=bPG5* zijw&4abTI5ECz4U!^hru9I^$b`tZ;k*?PRZ#xU;Ak%9aVAWe8O;_9sH~o0I@Fd1G?jHTH#n}KAg_-p_;zWXLsKR>b8M#5~v4GKfa*aG60#0<9U5Ju2 zC?1QGB)^PT4wQn&MU>;zzZP+$ctI8wR}O&5aF5Lo6OgBFQ&b}#J7{0=;g66|{C_Kc z{v*o&O#t-Yj(!l4J)6*=pFuxPCFg72GoO%-mQifFuTvCJN9{}Z&VSLq?uqVI(5*W3s)1;bnT*AGXNMktl})98-3kxf}o-V8*@-5JXRP)UQWE-iU;SLxYHtcygdkapuG;S zH`pNrE^Cx&AaHk!l4eZA8W<#8YBG)E5akis@8hn~t8)lqGQ8c;K9Mu>^~nr|<#&2~ z9?>NNaFFJO(gi*jacyYXh-s-shV6kYF&gj{q@+l=)^&xD0j83`&pO{f)G?#kOr0?8 z+z9cXwMlo*%@MoFD;a@tsOKMsyS=m=$bMvLBU3d%K0+oPx_ylo;m@NjG2d7hb4=ZP zTy}_lN}mqxbKA=3+9KoGCqs(49Hv!L?mRx6xAq!xAV%Go6XOhaRo?pvrzbOaR0>>T zJkNqZd%D7`4pZj!y9HB8gR>x`#m6_UDAqyCE)+MFNvj774Hs8|GzdB9iGJ)h`S*XR zyZ&2n|3mEc|2X#e{Pf#iKn2d6>d{h(hlxPnPbgJvI&x#UnJmc)g(0BhX!Mz3RU45T zGR4Rl{#)B%V8}O|_Qv9B;j*NutF^xnFDSqpNS5M5MrVeXS>SJEP`%q)!=R2~ly*Iz zO`9Iz(a~KX>cI0!3o3<25`$| zrolc$pYW|$?B{D?({ZcHR4maNe@$^xzkZ;#&rDU6epG|R=#3V>ISf$iFcyG;iO3}- zctkL7I9Y>4+}f6-l4m$$-PkbpDezHW=O)P+MB?2Z3v;5XL^pBOHuT@Ha?}WSw{rwL znap^|i;lJB4nJorBtSn?RKKY%IiL+6eE;%#&wj|vDa(wXNSi_u|snTOZcI)9>MjLi_YSh2X4 z8(s8r>m?KZpG8oP=sKO7-e<4X`<5madLeh2)OGg-8S244kF*HI3ioYy+Gav*uaNZs zJ$5hgx0y>{kM&G)F5?pl#&l+gXC@?fvuW6*GE7SPDamZNhVPJ?1dM+V%$ zU?#A$UuoiHSqZF-ThNK)_vx7%{macJKydV*lP!5F&9KOah`-am(zWfAZ|ym^?DqKNkat>tjhwd*I z?LJTdsHddXHP`~3&~bSoY>w@hKCl$_6|+fByIEb`WcBkCsn$t`l@cs8pVn4;EEc#PhlUC^GoDx3 z#lKnWF7?xr8fj=6DVP3{=9odk5waju=K1U8)AW^!l;7Z=0fAW>VKy9tdttkoyCP{C ztDm7z8@x%UZ{P@11jpN*G))8&J&AYiOh*p5aF@&AI?*a4a&6(A+ctt);TX=&6Ttkt zIq8{~{c`05^nJ;9AWg-& z<_PU*7S`Pir6{CFToMl^K&5gZxF29^aKT#bi(@3jr;^?*Ay+zon{8NY5hN^x($k^1eJLS{4MlM*8lM^QjIU zc;s^!DqBm_9KEJQWLC~FNenMA=`jZ+j79hasoYrlpO?y`aebiX*5vWpnb9P>6%|I0 z1vS&<=P7Yy!`z5*|ZuVUbqP2cw|oy*bRky2+=P}x?cp?Ic_uEpP;zM@6RpC29ayO?w*KW{EpQQp7V#p zTW=-6MHn!gnt*G8m=L-uqE;l`{(b53Fh9!ipF-dNza0LLl%Dy4TypdNktFa5+#a{$ z3yWJ+n~vSHZ+7^-yF*Ji(YJiZKlCpEa|anK4D<65u>tC-i%M!_Y%JKZ#(4%;F;=&z z^Q0au8jFy0yXW7t0`fCeJQ14l-)sXu%%{co14y1FL6F*c-0O|iba9)$!mhMWXLSldd7sj(phS)I?& z$7>O?Gp#gg%^aEZ=xaL_S#?yi@%fa_O`(rq9ZwoulxF7Ms*OCE# z4drauF-e5C`*O6~8<#ZIJ!eNozYGuSs%s36umf4G0!rtz#NH$bgJkQ6Zp^?F*U+;* zb4yyr(&EF^kI``_DkSgfRd8rxlfSnZQ*k+3Lr^X>HZ_k%nVQ%je#v7N@)wOPv!>#l~#Bv`%PF z^o~fdlyyt7Fp2IA*Pu73l!Es252=ypbs#$aKk+eBF^n5scL-CXLY>~mNCVC{KrWdD zY}c9^(~M&IZ&A1m~4%2^y*-7(ew+1LD!`utzVcUq9U<5cu#yXX_gV+&iNfWJy(9aL zjy-4x1-ZTpDSfqW&FMK#bl>dyHs$TT^oMr+eS%O0)S{d}mROb&%rwV_5ALAZL9!y; zviRB?$mm;O5S17vPxxX$g>OD;6yr>7St%u;Z?wNfLn3XZh!1(!`HgM!Td;<`*#)oU z$X{4=w0ru1TS5c@!g2$bRwt~*<^40w9-mh$W7l5Fd2@3}Oy{Z@&{IOwKUGO{^LqBx zmuI}5tJ>PPEZ~c6$8YPRkV|>TJeAD_nHdF>>E2{8M24S?m!BxkCutvFd%At4|Iq8I z6#O(05fddc%xJS>pLFJAAbpnj;`n5;U=x$>>Hj_BAMQjp(ShbV|FFMf*yld15re=D z1ns1Rq+rk%(GP-_J*6zczd&n6^D&~}|1`n?(tY{~QKQ3g6SFl#u__ofV}K8eQ5MF} z53MB{Ru3V&AIVWp|G&>3|D!$i3l8qlo@_x&^&0fu)>hVgzc-Q_sYdI6EN$A>Ud7nzVj5}jg)Nq=>`S*n@as3LR^B%8 zM6>;PNHh4haR0#VUJr!ngboN6=$jzrDbSTtIo1(d{B41Y%h~$YKRgc|UEt7J?IA_G z?u2F{XEoTNyqoeWXi}2f8-&_^Gt@y>V}CpRoV$6PKvsEfwRUQyRy`AcpN%L^Kb%H$ zqt>8Dd|oUoIFCfQaw3J)ZGzhMrY{g4rE-itpNQKR0$WP;;Ma}%k|E6nO!WYmJNi2^!P_}cNok|Ojv^FscwXCW&mnld$eHw-z&SvJ^hI{ zdwPQGcgDVVUOl6kflqjv@9i$$$Cc+Z2VK^1S+;vEv&4PFh$>{OEw*~}|0*4NM+hpB z4i4c&#E_%CyFo4fRR>S4A;*%g_pdMBEb|xf>i&NYlm9)SI7lEai*{ybJN_weZ}}gO zq?|G)hsA_lQu!?m;nifshvG~ixXq$hQ&u~oCJ{LVj*jrcUm*~Cf_wHE8*OlAn9-y& z{m4OFqa!pdeH=Xz_vOmGfxJ2yBErgp$fR3eEn$e2sVwS8$^ZIgAdX?;7{A&pCFtlV zg!0f4EI)C=+AY=7>;8o!MAILyn4k>tt^EA9143EzA;m<>_UA2n)Lfi#jc~QYQpaN? zS8M&W}{T-i9)*e z49Q0iv^Y@KItZ43_A@!VdbE^VmV+OaXDdm$Wi1atWJ{evsLKagF7!w z&5*&@Ha5ci(f#_NP0AnF>Aa)MB zXqSaWyXTX)UM5g}RsivunqpxfL-Nc(O8Y3ezb>PHpbOn7z zGl|4pZ?E4Q;$7GA@HI>-ZHg&tLQF#UIhE$@^oGUC(pJN@s2IXElyeF*gtup9@Y`s0 zY%B;{FTyX!3z#Y^tv$89ehh@@Xe~$^8@jTj{&NE3u4Rq>gsZZHQn7#R8(D2H$;%9+ zGBeP80k_jV;6rOgl1U1mn{{AIT7R+5D8B~W#y08?c@0LvJRtCvldAuHB8_7n6&ur) zw}yK@u|Pp>P)7mJtrtlLT_8HAHf$11gtt*Gl4g*D#^l?fwG}5ca{)_>E%trkh4cwr z{h$->(P~v?_c3jabiY#c&6$%KJNjTiN6C_j(lLL{Z&E zC;gYz3*sJYg-0=`lh{QMrcRP8OUf%WujwuXoJ9?)3H}OAHVOgr10k=vLtStO&FmLx5Qa54bS6W ze*SCA27(Rb8AS9Pv%8+QChy+%QmmxMMYcqW9zJQ5cFx!wBb9*p9Ak0kz}-GlzdxVA4FRK^uywR?ozKUjd$qU|eC zE3>9#L&zb-(l~!_=i#^bfiXsQ-t^(+)zn*R3i=QE!8EsF#ugG#l(!*}_;!}>~R-YGZX6#TE}Qd(Wk*c@>aLynLxy1bk>UyOOW9?z}N{bhdzmgGGk4Ob41 zkO>;0b!oN23j$eMNZ4~bvU0i!n9aDw5s!lf<=B)SILmQRK7A6l%a+aLWly&=CWKuf zApfSV<+@iuiNvdjj6H4Op_x+e9`8oz5n>LmSig8B7I0t=J@{|XYH?!56eQrzC|o{= zh~^uot6UqZRK*9Bnd61kn~EOXpjSj>X1PKaPic!Qq(V5u;e?xQVoZ_*>`rK`=J8h> z@2j|y!Ww&G@R-=R+Do(FuXXmh<}51{2^0se;Eu>6m&o6wbJXTDQ;xfyjZuYVerM+`8tVhyEvgEM4GG|wn zh>lLwo&(1oT?KVd;p~smRR$eW*pvtNvU(CKfE|)RYoDREN<_(wVWr9DR~&+Sru)aH z@i>xIkLrHAaL@>&)t?0}e4K7^j_5FY1W1R3iwfhSYEm-dZ(v2hOG$C)qOU_w{cV}B zcggYAH)-_>@KQE1qB1^+dNnp;GB(N$CL`zdq$K^FY<&VMwpFVuC25<;y_q_`oHP3; zsqH1#s#tbIprvG)jDfXOfqgp zCSC$RYB0T(8C9LbonHQ90sS!qk50)We<`uER#YTyQ6%dUDXA0{sRKIp$#3ivO(}60 z(EL~nS@qxiaZ_0XyLYOT4(d1qTu7N$Xa1pr%-J)3W)zXv53Ix-;}I~isj={MnQXFn zxLw}-i**`U5wLYOIa)TLsxGQ6cJwMT{q9w+S3Z`pu^g17_}bc3q}VewNeE&|CoKXD zeGfF4cCY7d2`9(D>Rp8P|0NjD?k;F&P>y5u=%1j>h77bc6hM_GPwySqx+XY$G5q@- zLcfWG{}1poq*q!d_yhIO;pec3cPc@bUzdD+mVb&=-3Kp-uy_&^C2w-w7|SoD%c-)m zTFa%Uv2n@RBu8iy1}y{iFqjptR?;SA4^aSw(us%gXKL+|N&*oDAFdes8n=z5h)JW1 zDcANj! zv9W)CL`})RC7eD*Pc>M*yeJ6NR?f`_{fYDHbt>;*B~<4sv~|8aWFjyMMIi^EQkR4r z)bPgf8Ry_GGq5?KuU_K$nPaC)VvpWjUYzA7K%lhwLdnw|w33 zd(71q0xKRfeO|v15))5KAZ3Vn%lrGIMR{C;QV8FmT023c6;NCJ$a>WnL|e}~+fDK; zevWK^7ok6>XScnB#JN5%Yc1BPpew{DfVs30;O6Sy<0nL%#Mf|$bF2KH4dpvBa?M`R zIp80)q2)-7cf_RIu5U-?5WIiMS4pMd&rAyR#J*lrco#$l>r1T0{>%_DDs)v;nCKD< zsz^>8x;w&?47&qzp)h+iNO?c>*k9o@7`PY&G^aXi)U$4Eu^J@Ia-zWSP$Nz8zNM=J z7Jy%v{T`7&mzxwyFK zX~ZNrY5gas{3yjoBGd-!lBp+hDt@D8<9UH4&1ms^E|aq8Yb$i>WDatzf&%lkdFC&p zT&!*l8EhC>n4HQ6<)MTSil}eGLTzrpV%R_;XeEYu_f<2v8D_2PrE~>%R><_G|;I}TiwyO%~+uuEWG!17gj|^L&(iq-{%h_ZM52VWoDZm?J7mtTg zjYz5+=P5uAmi9vU1L^D^QXj;>(j$EXs>XHYRl%9|6F&ws6>!Mvx-$23tK#g#6}T2} zI=>%DU_wOx{({ddP~fB-qb&RIxVY#CPbcpc$UQvVMIa03;WXO#9 ztA0P4KiN#@d*$~~>q%2iNcL{AedBfJv_}{gdj?8e|628i`^lZoET!BG%wxIgIw(4& z9~F<5h!7k-bs(DPukX%N>d`rQc%atjl^(5?A+2_RX8D|;JLZ7S3)#`C?v3SEJ|Fb2 zwp^}&`?xz%Uq;k!d~IE2zk2I?Yk+jU8jvq#(e~h8dHG&W_M00St)Y8%JJIoL)j+t9 zjr_~c?msVv=nspe`v)92BwfF`m>X1k69JiQCSPhUMsDtdK`_UJ!}|I~E{7KdRW}vd zT7*?BSzqgLf#O+bi*FAjrEi2(up*_Vu-_S|voIvbR17`E94m zxF2hGb(SwT9}yLl)i(!8@_fhc+VlWB+|XU4@nG-S;y~Nac0-PbaQ0L6isUrd9=rXe ziQyTXs#_&UT)pAA)9?B*&4P3|`GN0L7d}UFuJz82Vbkgx{k^pili;c}vOKtgKk=W5 z!S-v=^YuC?7OypRRw^%y`K~mR>EBee5X4nN0le-Xm0M6A+|=-JGqp|5dPL%wV|7Qf z#O#rN4nVLV&>~s^_`9R2q1z}A1(m8G(#%PJY~e!Zq2uCD{~Hl^lE5kNSw}MUmCwVF z=cheR-uEe3a}#6axdP2~kG0nh&; zzV!OT9D$D5J3VenvL7>BLQx1G!TYfk*`jAeE`zxYHKQ%|Sk?Sbav2isYz-Tc8Dq21 z0iUfurk|pN%cBU65Gr~h9aMXpM!lYQ7^ZvT9d0T1pfAOqeP(Uu%to%A%qc$aUY02x ztOF6Z-Z((CFZ5~Y1`Zo_cWq!*NyxejrYzn>A9}aHZZI9KE|OYcf<2lWMj8+*Dj}b9G0Nh z&yfPTfIp`XaFYMqiT>V<^z78tzwT4^%Yp%laTnZOMMt&ajTKk$Gm2${<@-_UL$op1e3Y!o3+}Qby#|r2D0@@ZMocs&LZ(*O(+0^z6fhlY^5^efT#+n$xMd?QwEGy>SDCL#5oy=Kop*D#Htf7JbHfO}H$w+p1b zjyehQ<8#*U-`!kUZAB%2v=}=Ng;+BIaRKTYlMfkA7H!%(+k_DCwFQjA45$>>L|dkO zp*IZ;67vri)rYNUI5jNQje72xIpU=x(kLiAe zHzD_hng;cxG-0uqpz0JmIDLI$u_EF_BLaxG2V+e2Dka5-u3jP9O&zaRJW$eRj!j}? zm#6Uumvz0P2vJbb!`UU=(cC={!P9fbn0JK0I&0YCd*p($FXTDm^Tc9#_)zXPYUaslnD04@~yOev8vF$ zfLId~W6e?}*vo@&V{F>_On0hz?r=6;^7_{3^O=@3fQ7Tx@S_T-jz}o3TxoSeoxDt? z+%FIBo{3_smja+!dSfg#0unWw_BR@FH(b&J{#cAl#8Q=3j`W|p2o%IJfj?SXT_4ov zBORCroI!`E*&AUl(Pbf0VA}AmHDgEhY&36z)k7Vy5ph`)N(r2cN1B=N0$t6cU z`Hx`F1rkGdR5}^=KDVIqX0_teC%78m+T|{XI~e?TNA)6rLIg0Lh2W_x&krCKYwlQO zJwPmuccBI-=l({PZ;Z(};!1-NJIhoiqqV$T%bbvz$YWYk03iP&m(xg14?Ohq$N|T=17U0`zbw=JntO-C=hCZZcMgs$fj!`dOr2qm}q@wH-9!zTCsKS zE=tqxu?K_J-m!mNE)qbRMUj@IKGn)A5b4ME(bmLb^0BM|YlYRbRbcjGq2YC1Z9Uhn z+}d>cn24QFD`GL^P2Y1_d}62pEPCPV82&Y#8oSl-!N3TkZ7;T%FrYf^j8|w@VSAjO zIw<<*PrBDfz!gR>T4^JoW}m?0tdBw0?;`ywPn&cyL-O+7X? zgMOsY@cfSJ&UVlIXPOJ4Ke1PP{Levz)&-vB>J9(+_3qwcZ(>&`dxldY1UQJ0w#z~aW0^;O*P@inMHZUI1V{BM|$l(Uo>zDLD z+d54#xpZ>(lJ3r&xqlOoeygzFvyh+FIkp+RT#0!{wF)T(zR7?3T9`aspI=X#&lxK3 z`d^hqK>g=r84^A5o^zvQ5#i{NFKCbiD;H`XwBlkk^{@EL7n@DdQnR>h|HJm_e!YEP zf)Dq&t}w?lN8iMv%%N;ukL*=`oFyKss97(oTC*$cs(H4EMq#T|AN(^HLJ{{c2&*@~ z(BdM#VB4hNMR@(q+r>nAPp7rIt#-~u3xeaJ+HW(Ps>A!l-0j9?M6ctrr3x-AgxEF$&0*u@jf z%+TY`+P<^VY#C-yj--oeD8K%?qcC(n65NJ6CB#}Ww#A^cU-1-=(2T2LuqJX$WsCH^ zII5L<-7LT|9`})g0cSu z_eG@|FFCe3S>els*EH8$IWp7ZwflZng6#H<$ni8nwjmPuDu?j*w&6oE<=S|(%-H%^ zcu|i#Y@StrUzil&4c>ZDg`RRi^QD0~$m~5EFX_zHI=Px`prgF=ucGjAs%t-WvZuj8?%Wp)zBz%M-kGjkS(@$s$7x#6-(a}WBdGSVSRyV+}L4{36LyeSjQ2f~0H80&1# z(j>pl!s))3+T9&t&GgeKLD7yniF9Kg!N$7_B@7z6B}qB-%jAmp$9mb+Ldm{0-IHo=%USPCK49T*znaO8RSX*o{_Qt7}`oK?K(~u*hk)NT{nise`qo{ci{mI*DP;=kZy7GP= z(UEeq=@GaCCYO|)$?ih0^Q?y$jjt0EZ~TXmWMc_qPisHn$OF2hQHWxk(!|(!*HwY9 z>a$n)SMcNM$lS(e2A~p320OsU`;0#Fci3!Qisn!OX*jx^_jP&k-)?~=07AexB0sIB3nQ4#zqGd?nN(fj_{G9q3ca{ z2iaow4<8`^rj961R|rz9w?dn8*Kre8%LDv>ZqJ*@8ig63Qa~VZpkL}YMt+HDOHquQ z#2Cw)Qt)qi`_C7d?3o_=a8!6AGx+q4A!b^qa9RCyzW7i}FE>}N+AO-sl6hc`!sm-v z4Cfyxi_sN&l!;)Xn$otYb-tV(rTNdL!MSohH?&A1y5!8l#6AMmmZpAQA&(Y))(oi~U2AR_hrF7^#O2x`-NU z#!x10IZ-p4a=brzyG#vR^wgaAvzC5ZiV)lz?zoOL*Op-IsVTUe}l0(1})bo?=nc>`>j{yeSiM2vPuodi{OiU?#XJx@0m z_1bs$O!|}m@K~QcAL~t%uj?SgRM%`=+u{5RL&hMC$NNfosjjsWVY1Q*3YYCC;^(E^ z#!%OZH|_W#YmegA;Fe`>>^zfCZ&Ztw4#eSQzHuv!q1cb83yx@bP*jsfX_-n#-YAkm zHwtx(*~rL~c_t@IlJ}DarAMU9_MiH*VUA5Q0nwd`QtIAi6+zpuvI@%D19XhY9MT9O z5BJEHe2$71XFC$Nbv{e7O?4yd^{|PB@Ye=ZozSEtYX26CNHxzQX8Ry&4Jl(NvOlPb zfWK;aX{DYXSCv$lVL$tMXfdebtp^7V7_m&{U4V=5oyCY!&2+*Yy`hh5&XtgPz@tJ> ze_<27tqu#jGf!@oRIbwE`IW@GQ4SJh2XU}|)#18J$LI&@-xOe?2>l%kM6C1botb1g z>kvjG_t;CuUo}zKn(HcF68L64DF;Siud_GfFGftwiU5qC=L~JnJOX{yJ}!`Pc?r?y zgh9sUrNhKPre-H@{qqbOMh^q#@l=*$e}~EOamZ!udf!9D2UF7(|IT7H=I;9G!EncQ zwQ!H|H=7;M$1->VCva=^+6+Iu03+X0L0wf+@oC)gxkBv~XCu_N^~WnkpHpVYm8rt8 zZm#$?Y0gX!5I2f)q&QAw*lG!YuMk)JiX1BuNW8Tt8mh4Nteq!=^P$Q;Cy-Yy_&z+$ zjIqXCbTB%K(vtNLLs=@-x*nL)?|&PcJo2MmR$Ds1u{cPyGy#e~93vYH?%K;!#18yu ztY7%0FoRj3`r{pDgI=d$NsH+RJ~*m-Zo%V`{+;E%v@UqCs$~A#t?ED!x6j;lThb6| z#E?(*Zfb<+20I_cWHG?mi{j^W-$A>Dq4tXRK~(t$AVi_MLAI%eQ%I0x;~_nl;JaP zGGPdOQG|@YJuIzRqv!=>j+a+@SNu~tbxa=UCPJsm67h7DuhGfY;Bn2 z#=`O*&loJ!vBn)|<*soyUDKF`I>gY0{dL8aq$4Z0!(Hc-$-mg#;7>=`N7;WU;11XQ zJo*4-tUK?QK3Co)q%ZM1>jz(bj%xLkxHb!}!#MeWRKu15=zEO<`Wr-&{{Py-LYQQnSDPtVhh#IOm)9@ILXjek#&MDfX7Aou^_B4M@c|)iAdPc37lFs zA*Z%63On&K6e>m-$|f9sg0^02v+q{P$7A%9PfG^tG{L(|%%&(zlXzZhxf8(vRN|!oX`)L`{9y zY;7J}xJGZa*{;%tGp{c|KK73uVCAYgI(7}Ux*a~BhxS^3+QNf8j@n{XUOSi#(yYQM zw?B3EYR$q?YiTK+k_o4310;p+Tj-~msg_s~BN3U$G8Fc-+VQoF3Lu z8luM>p$Z!S@`t^#Pif#y{k6m#CnuAh$hhfAuCdr3F=%Qv>WN7?W9oGT=iSy}Z68eL z5<;g-0*w=6r_ixIi)yOArVew~+Q5q9-k9o#t+7^I(+s>Ugk)vNTA&Ail>|)4tPPTh z`t+M_7h>Akcnxtg9zF7c@eR{C{#qre6T~kg`kv}P*1b?;l>vP4R{vH${Vi-t`|sZDG5${lrQ!6U=S_O zjHd34e@;GzMhn$I4i^w59%*}AqlK5J$Kr{AI(!ks+bf0{gT27-Z`dXLL;FN}@B&J4J5 zS)K9*QV&aWFe@wcBo-pi7f1HiRu*!qsDYjxxLzn@LRbe<%-Wrt_(?tv^Jb_9g)SgX z4Q{nMyTyJ@^GAh=KY+Hjn&#ax8l4+i;g^;&1FcfAe>{!;eq4nO9a@RCE2*UJEj2_h zsWL=Dlj;Z#0vIf01Yya2oUFzZn~6z@;BueL*zY!0b{wF|9^0NBAD|=8|L}Mv@HACT3JEb~{``kl>2vS%ctXFOpBSxGd@_O+I3)gXEb$PgZ1Ds2 zO}TE1tzD>Qu79>aA(akmXqW+NcGmK(=akl5CYOEtFV=?JRez|ggs_A!1t=0uTjto> zc;}ZqBzLPTv4cCrQgsom+M-7fB zF23#{g?eSaRQ1W`mOt*4mWS&jR|CkS8Sm2(ASQm^ zP#pvNa;zkv?2RsaM4Ix4PawYNd zj>K`WDpBPpj)wmoxoLU0EKK>EHlQ=r1m(sTb#n^fnn^n=m;c_rhJe$JBMEb}SALbB z)!U;gjVIG@^cAYefk-MI7^GKOisb~5oE3_w$F=!99~$}D-){M3SwT&PG`N9p z)_+`p3j}9Ok3UlUSQ}ekxeFx|m%DOB>{i$Ir6zAm!EUwE|J9_sM!VtAPZEhu05Gi6 z8Mn?$PMcljm-u}fvff4i{a8F}J+>2ycSWx8P*ys^6Y4w_p#+4W3dja-95yd6&SU1! z5A~EC3~{MVVnW2HGne2lAQ~^su7%FU;ovyPn3|NXPOEf=fKrSh@tC8`4c8YWvoknC zh0Cu_2x=RmADM@c(@Y8tIi#3Lb2;sSd9XOo_|5y*tpN+f+|0JI&|C3Q5(DLvqc~gl zf8k&k!tF@7I@EaGnZ4<2GA|6Y_pOq%6# zzIy2QyAu?;Xp_0)(0|UQrf!szS`n@>09mU{g8Yh|O?D?abig%^NH^9qv1tZZCYara z)8QpDJM%O{n9@VZ8hBR9+aq=W^?>X$^4a!UK9IxX>6f`6#Rc{TdFLk8#SEvr7}2&E z+@(xwac~xP9st9iPFZ^tcja=RK2kg~iU zoDLG0QR6Lm8$TUq0eV|?g=(xW%=mQFErQT~ujS{zsx2l*ra$2XHt*veGgLBtrzUEi zcUvz1*)aBdVU5$Rf%>*!9K71DwA9vgW%u*XPWZFmr49ae%rYwuKB>|45@R|Y<^08& zcGjVTU+Sxt0=?8nJNAg7dj7tHUa`wo5=NwaqT9HI%lE;(VLPc6DG{otLWr*p*Otc4 zmSEv|IC3sJ&XxOTsA32uGXa6)NQ85cpg+?~s+_ZemHs?n-O-5s%8T|}X0k-k8P?=f zaN+8rVq?ouV@ac1O-&;LTCX>mgply3y6ba;=lH%LLq!@;l2rH`f8*fH*syl4aW&GeAb(%hl<`|62Y#=hDAy@ z8546R_dNW5;4cCGNx@P`Za*Wz&IsutE`H{MvP*$u3<2?LRTc8>S?H@q6I93ca{qb7GskecJ1D#UuAILW$$6;1bQH*ZgbDx{+h@`K>B zup1h{=|zJ_=`_+N8cEr6P19w3%#0s%)Hjrkb`)ZN_MQ(9N`t=@!JUQ=?i9Jc=}e>= zp?MkRQ(qYk&GvizCCnP&x7yzw|C5M;*ZW$@V@KVM!qx198MDRmhhz55V{|U> zbi@3QF`JiqIQz(U_{oEeYK(w977&Q48LMHv4iAf>CYm0liz5DZ|l zHcst|qS5VJtAlw&#E-F91uVoLBEDI@PdPRu>|Dk01$$0)wiU5ZQdwVR3>!U3O*{G%`ZIe$fPb$dcIqkAZfsk>bIMZxCXHcHBpvrOjedCepw znWsI#qO2;{w9apuvW{BDPszNuY2^NW-!c8n(y8`kobY}o#(SF-QS1B=dAA&gWN)?Q zZ>_s`;;m;T>~J%GYU?c;Ug)OI5i|ytd53_fQA|}~N{Tp-cgoSx& zAkaASD>E0N2U_P4wXX=NTk@jLMo_rv-;pORiQbCvk}u$;Z}L20VoY}BqMup{&Rq19vxz785=re=N0*+jW$ z7ubYo#kN!d)O^6)`troXI^46f8aJNhhtzmRD&h@F(3c6YZXupmt(rtylk*~3yg-(#cRF)GsV;Y@ODr2PLX zDSU-YdIw18PTbUHo)3&-J zY`MNFyjuVevb+pX_daV9IQ}p8-ZHMPW=S6n!JUNQY%~cPTsIa1!QI{6-Q9H)+}(n^ zJDcEa+}+*XFYlQ-GjqFVmIsup`Q*ln}3d45GNh`x-U z8TVIIEl0#&*O~CuA&)-Yn|j>7xMK`WB;Q!z87@?EdMv2sT(Ma9;|^G_MYag2a>r%= znvZ7r<=!00T~erpzI4e=@-t~r5&;E?pHN(7#eVl2F40Z}-dDAGK6{4=zO##B>8jn_ z-RL$Q+6gk1gg7Fa_+>17h?X8^;`+TqpQ(Z^Sh2gJLlHQ}U~jNyoH6g^RG9~&ee5TwiKJNaIpIM^&Q0?myJvR)L} zW~`Qi)e?^E^+e^ASp}~rjrF;CcdJ63b~k@pF_2HBqWP^1%o&dk-DbycJ6Qr_&?s+o z9<41#P^ae_&R6dyOAfOy(f*)1dn4n~vKBw6+$@?***V?{Fg=ncr<9(R$rsmSM}9MO zxjmJx&OF0R67$c&5vN^Xiy!$>#3?JR0?KMvjdybDiOgJ`DmC=LTT)OuXo{@G5j5^> ztptE622?F)PLO3NYN%5n2AlirPT7Ar-#2JAyw|0zDZh5A2RLMSQ?VN@6_;gILw((L z(Dm`%vrH+y>MwfFjqgbcVPCzQXjEGJJXu>&!Ul(yWOQk-JfNhmyx_(P z)#Q;@NS04El_?R!2>0qf-OV?;|B(Z}s&=PcmhCtHaoM!??rlk}K7SFfAU{rYNDI7G z+L4*BHlv{~)6Mz>%?{bjvL!@By%C*WNC(EOdiw`YM0?gm<7Ej3SF9ZQ+T1XYYCKpj zakBfP=>eK?`|ZJg7TkB6eX?$LG~zMRKQ`**i;CTbV$g1^pflwyVKkE4LMM%7`kbIi z)4Yqt0xWjPbta?h9Sulk$w`g(MHa_l_KzTT?Oh0EQUP_j3Af0Q-|K4h=97mmsr>I6 zY6>kXwO>1B983T75RH|>oNr)8>F(~M-NDlSxwTFiVp?pwTzDZ;BMHHThLI@5eKOMB z*Kx=Cut;=aO%Fx$_-)Z)%c1=BmFuv7r6%)PM3ZZ}Ij<@-IfGSlVM425c0O$j?@gDW z1H1=02s`YH{1l@8%e?eh`R*UXr16^|WpuQH(BL~u%;8*%+%}bqn~=`@u{Z|t z_Q(<3;zf7A7vQqI1&gaX)IfAb-eFo+)>2FH5F8KfD9rIMYOK z*u{QkvZHqg6F@>n7V%{|E_M)cMGKBrr52;*mbiC321uxTTCq-gq`40W$Zzn%VOCXR zWFW(j&TjXxu2Zekw{kVXvwLm)*t8gxz1|M1<6fB&&1ov6O=0AAV|1liUd_3>s|U%s zsYXsuD5hM&S*HBj=nA*K(9#j*3i$eMGoR)5-ODqCYc9s|(`oDNhmCoAmKaY<6o(jy z_u1`l=SSxHBc8}XaOycVG&FFYo{`}*wtYE%WSMoVpBgZqhsTn~o&MC$ zxlzDdrzza*VTJZ^k60vO%c_hE#gC0USY^}O$ zCtf#$3rf0|cie$l@j6zt;F0n1ZhiQgnbptXyAMgfh>GI% zt&D7>X~Y>zCJv~_3ghQ5%33Yls`Kh;hHA}Ift-NvW;F~gs*%f#?2eZ5>E4P3Y3Ur9 zl}VM>lPxa#&QMFqwjP_u4Vwpe$EWn!zk==?y2;PaESW>3O;riR zF`KMTyLGR5kMl=- z>r9jTg(=0JD$`4GYDHg99PXWqtDTk|J0G8fmJKGzYB`D>EmO!;5^Go!y!Vb(=fx9U zhpFbPr(ZV)M5~~fulYgm&V_){dWG#KTIivcjT5Fu47bTnJb6zc1 z<8Qof*YpTT;XRf$!dGJHtI+YLs;YK`CE8EK9=|37Lhkk*WL6_o64YkrE95;_#8NNB znC1AWr>Ph35$9&3wfD3hp9+b#6jQ&KYIHgmUBles9;Gt%`(DKTCBHf%AKy0jU!THJ z#QwgyEA#s_Fln+Jt1j+wwBAL$H~gdoqQ}9xDV_R4njj^9%gZyXu^g#}Ui90BJ&lD~ zpt?=-Q3f0Tj5YTos+yHh0>Os0x|bm3j~^#LvVviqpWif+!9nm7-sWE8QKU@W7u_+H zBmAD0{1JN@a0QOi+o{>I>l~^YO*`sgT2Fe%*xM7vy;ZX<@N0yDEDCx6&nmPfr$Rc z-l*srKJ@$iYXto$w)7INR{xJSeYeDYJ)ZD3#Q}!euC3!k@aF@f2=+jK^c8!5iXl_D zd%R}ml$PM$b)dP_3`RnX{P-1!eO^H>T|Np_%b7v1X2bm*uWa~;++A-``0jA^H4s0R zW@qL8UBalS@rBPm{uSN zfBm3ON%rL{j><|v%FerFd`4qKfhAetlJK zR~;tKLZS}nsZ1iT6le{u=qGzNC`YPE2LqbznG$kEbXodWj57Mv7GV;-oZLn!hdR!0 zbv0xBy@OEt6oP9b@5 zUtKJa1!1bTj#TlEo^Rti8iN$qm9xm9i zYByn=%b`4z*Qs*uFG7d7-VXX_+keS%;*c_g?%#K2osf?*XcXvoKmX)h8Zc7htJArq zm9v!h!UdDY)pt?@`4*GX^>lZBg)hJVEjC^qEF}O9NFXX78&}cQof#Ww%hVBf!s?zV z&^eiFJTY-wTx_Y_QV?wmH{wV(o7SA{d!0xLX%T z&@jtmD+;YK{Nk1KJQRhMR_tIzdi`Ys$~`@M(|8Q7>{_GW-1qd$<C4po zc!a*Y)A_nu=2P}6UOc+c7B zDOoz&d#>qaJT8}SrpB4#T9aedDSzaIhRh#O~+}VuIL*nD$5=NM}P<^|&nZrK6?K!?F zUi;K5F<-yk6~KD6zJ#JYh8C|9m!_>71y~Dy8vYXa`=EsU@%VQ25j-*9B{77yqE4Xl zEJS~K*b%G*`TS-DoE7Z+P$sO6Y38c_msRjDMO`4=XE5#(4Z;6jkkL*`xf4iro?~S* zbv_OjB4g?zHf(yVG8z%m@ZSEsLRdpVCa-ls6JGg@Y4m;See|Xg32pyxJmS>i%Y7eR z?*;+b5X5!@+hLV+9ixDF_A$rpKXX?DXuz>K>1a6J+|*^AKRsCTTp+9|aQE#s0?KJ9 zHj$b_$J4lr%cP7{u7#>5W)0e0A;OWPJ)^aFu${dtU46xnkY)@0#-`uCPXT!n)1irF zukH;Bm!<|3Po!GzM`+Wytl049Utv;&IzrnY@Tp?gIw%EbCXJ7oa+~T2x%d`*$Pzvy zNe%h%4M7$xRBIy~>NX`#M2SXA)hbNzF#!(yq9nZ8jByi;TR2K%q!okcH_Y+gluBX|U<@E5piMBMvr>bWi@UP?m4d&Ix^tozSd8{H6pn>ZC5>nd4Y# z2oXS3U*}|>T7^O@4Wy7y*)m+#zt{jZ`g)>D$l9bk;b-Wg!w-xM!Up0kj%Bwaq;**l zu?e&W@=V$H-pdYDw&O^txLeCC}pewP|Xb-TeynFP?M1*HbFGhs^w_0=!;Ll(l@f{k?^ zOn_Y)Cbib3y$RAv*^RZK$yEB$_{3S9bZyXNw%gaMy((Y3ufC-l8zxT{Oj4Xhy@i0o zBnpRB9ufHT2z1D)lEop6Qp>PFZ_8(A}8eDuW+kGn=j9&86iz2*e(>qC>TWu^$A|=P1Z8fArg0%4(d`bnj zKe=dNrsC3iGjd(`49>DBXv!HxV^n>xrxX))!SL2=MGa09$$&^hXP$uQ4@3*kD&ox0 zxs>#*eU5K-S`|f0Bcq<(wDO% z;sQFB**K=gte{z-H;mh@5-q-k#YNiRj018cYgse7;`}U3LVpJ$Z91rtb6g^L9C^}< z`iZ>;3#OW%5Y2kiHP0g83jJ3s`+|r+zG=5r7x{eV(JjaYk-m^6A7yPfJXNmSs^u_v zY`V-~of`_foO7PfRM3pPr}~Q-buI^{%-G*l{UY*gYL#$wr#n`$pi#USlhUw*mS0yu zI4>8%X&Nqo1S!!#0=3FOQ2FiZz0XhVYY_n7M_JvYmcG*fQWn^KsAG;+N8%Ni7pf`x zN4WGvTgsuuaGckBG$#O$|Jd*RBLcQC`&G0b*IdCkbYpF+E-A%fRdt;xiCMU14wd8&196*7 zlv4Zs*Bo_21>~MA08!81?0QST4LoX1;qN;l3zsDtkDE)PagPFfW8~+~2F+a((Tr;7 z-sKh!3%!d^rZyzT<|AYd%3u3qDO`CGudHK0yov~ZvNbpsYrWZ_J`lH-NMgm4g&HnC zs&6gPvL4xnr8F_u@6946i%}7p@fA6T}P>g#m2Ur0_c?M$Q z$&I~{lii8H%{;`L`yDDa$ByFB-G}lgT>zKx*(`jeEfIV^LhwfwQNQ?iX(~!iUr`_3 zrMC2+7e{ltrot9?BK$KU5pBeL;SGGeeg|j(Y>g@yS+5(teH|HLR5UBjfrsjs5EP`G z%?mPs*)!i88!~Ysx(el_|64KtPe$nX!jt^@`y$3Sy=VeYFqT3EOsN}^{ls)59Ph~Tcqp4m#18%H!-i*kf7;4Seo+6{4k zATKR6^R!#ktseE9>1ZMGdUB$2PI)$-EI8S9MZs5^UC~5Qg_0I-J!u^PM49}WH}Roh z=M(gAl^foQS`5t-6s?gEn>qI?uWrs!Vqm?K4Dcf$zkj!`%yZ!GvXq?9JKgT&Eh7Y^ zWU%|=RCPqnZHNKf$6G34?+nI4@qgnWOME9x6i@{+{p||&FJ()c4602lST=VY=e4n#L59td>iHH{dnJNmXPjGFg z8+WqOVURq&% zgVFk-$u;rhbMNCm&|n~F=vVE%%he0n({(O@hmSJ{DGa-`zfg@DI*8GZmC{*c;$?-w)uxQ-k9Jyt~IW+R@+m@ zwen~sEww$gd%?(~`IY14c~@I^TKkjvpbr-K%E2fqlM}Xqz6GgED0QjTf5T=Gomg1$ zUVlzDDcG3U8-{)ip+Zc)`GDs`Is+!({W3cvu%ID|OyF3Taawn@Z!u|PKX|@KR4ucF zSHLzY*o~b%ub{WfdIavE-?6`_nFtI$Iunf)5>s`PnPR;hK$`|_4B943Ox^$#Qz%N( zt=#vSCNvqL|K$34gKuwW(?_0LNRr00s^D7T>g-93RlxUG4ojEUijF_S$p2z?mcb3>VaQ!@9qUwH&)ofTan#qwXY-LZTdFG zQzom06Anh;=SvszW(pknG%dKaaUcf9RfLuVa+8=JPENOf@xK>C^U@U+k(}7vgGkP4 zx)49%9p&wd7-rLXm zYgB&AX8vVBvBI7momj9iqUVd~-`wiGL{nIO65V^GEpA*tm z9Q{G~HzR4)M`#|q3yf#C2P10bbP({v;8&Mk3KAyjI75FvYb9X)U4$2D63;w3@h)NK zcn+b)n>U!(RJ0jU@mk~J$qQQ;L1x!DCj5Eg>rk(~My`4jTV*w&({}@=-37}e(WXqz zpZ?1HH{Htrt`zgH>>adZKpf#%!T2=xzvp&uqM`ib@Y*4<5p zJ6iJmWNR#-raKYdWXbnl&nkyQ0m_P3N&Pt1J|0?u3fCrUcR#rk7@RRQwhTAQ)2@87~paljSY2q1kOb&ThRMB@Bq zJaXS#KvsECxFdLzn9QO4Vh&7E(Dq$_UJFb$x$8Z(F0K=^p>S1$Diu_?P$i4C4XzWVRWI|D!Am~d}YBlx6a;vwa1xc4V6gW3h011Whs+PU%z0*Qbh2F zC@GDy*=jhR(7v*w+&=FxMl4mq2j&+$6Rw5^z4-jHiAZL^BG-prgUF5Cj7Wezug^8- z341fDjk#2gAGIMJ@m2H|cP^h{+k<&%r5oKxZF?X@U2C8h;6xy(e{ZX* z(X|N*Dio}uG~yJVUp*lZJ5hBsW1W>#cTD)3<8;p70md<9A{s*5u`@K>l6vZiOSPZL zSkoNG)Bl^0%GkIKUQb>~cO$?yV(s=AqLlsJw33>q7PW;&#fx5_XmlIWWW;WnzG+Fn zS$IvPg=_n#F2u`|7-R$x7~g=q6Rf7MK&s!bado-ZJ2-+p;#&;4>UpDnl^#RlG#O}+ zmhRW0KGd0ViIw29;#EqqZ^Sa$!q&`qwyMN5jo!m~inzKx^sRwfPJZYN+uYTBu6b@Q zaC=Qj9jbJkm94GOUJ5y%Lico=MRRK(x)P-r>;;DdnLNonoZG@t%^UQ89Q^=d#iQlD zu-xfyTt{`&o6_F^xPvYZ;Iuq7veLR&y!b^#2!g4KsLt4`J)GFdaoPynU%#%etnvoQ zsb;M7zXE%>TB?-9l8}fPYz6>9C|fAV{tKPC)Y* z6V!*+w^bDTf%rp@WBxP#X3tuL4jkMQobS21Er)g))x2~Rc(TQvl+h67Yt@7g@pkF8 zpCaBNZJ(#bsuoLC?Y7B5FU$>X-Dnm4O%dH|t_9GJDrdF7cfC#6)@VKcEc9&lJ);Jv zwI62hMjzh`;*G^~To>`QgL2aa(3!F+8SyDjAkEWo>suwDQZW{nc+!z zU<+V1I!>Kd1b`QcCgG8kk~5T_%|GdYN*u@8Tf_ZX(xm3#!{`C(O&FI_0$TYuuD~?^ zzV(fSfWyMij4BH6p&v*Rg5Q8pBL@a$nI|qKk-}-P2r>FZq z(_8nP0JtiBc$Pf&8sE}`P9bWloX~LdVm6!`S4wiWc=(gqT*nwDO^!p!7cy1X{GcU2 zpN_;it%J8mxa0+KPS)kCI8|c)l70_N@z&#| zdZ^=*lg9O+`7`}{-WKvHUbNtQNw)G~ARqyVJupsSv$F;ydiZOvy0S1UC7ow&2?-ml zX8h!NyyO=aN9A?*MsC(4O$cLHd;xKV9bFBcc4?*jWh{2;DFs&51gZ+o!ks49JtMAF5Q z1tC3sAY>7~#>k-a`}5(i)B^H0lQ`n=p5G&s5Ur@qLJG!`xdQU^eFSEg7ScE8P?2yI6yx-eN#TE1km%K`7Ubo-Nk>~iX`VSW??RTw)jf<46Z*1yD(6>Xb1nhP` zeKtF6J2A%qB>E40Z%amI9((qiXLj0`OzQfMZ|_Xc2QV!1`=PLt?0Kjs`Y@BiJm+1C z@im-ivLY>#1YV1Hp~+*#T2fdj@=v_{$Euxv($Yocfb|rRf;ngT^xjmMh{PXOTk=}h z-1mzL?|f-y_tdn-A+ufVie_w|#7{cBJBMf)7gT~#9kf#M@;-1`VaLp8et$}UW=KDk zc9h%z+8I3HeipRgANNJ&kY)K0%>$H^2JA;M%I}g}`b|WCJrG&Jd2|1A$QxukZE0%6 zV|04(c)9Ytd5U`T9Xy*)(X_FX?#yvD9S&npdwu5`&oAS4&@V`vl~mKFQu(ZOu-#uH4Lx0-2UqVI?Ml)Z+#ntiD2&y8rryc7 zd_pfD-~R;y_%MR8n)r4xvTq&+f5U*gj|h}tf8>dzf8GV6S05d!>xPIFtiC(nxL?bi*ZI&*LZIcM>s}P&CvaQpHR=T#x<_Z zxzyp>3gx7vdE7hk;?j6{Aa-iSJS}?)C45cDTr?3OnKkdjCpK9GyiZjL&!Z2T*glFI z4;y%sGJ{UJe3g_qpWf@`UiPRC8OSop^I16WDPIDGcGKmRp%6+ zeXce)W3YRI88)(Jq{rvwJzer|UpG8AXaJ?vd{Qp#$z^#&Kr&OuGdZ93;J`x3(kEt; zmi8}DRy4BEF3iUd474x15WxnWx5ROL#YEnToZLBAX70{@po2Js;W9d(+o2%e{CRPn z&CL;ruX}mFnUA8e$H0bbX+QG4aC>*ZAK4Qpwh7aHUZ7^0{9~^515t0FEgd=oo%UW6 zUDUJ6GN~;~f>bJ+r!qVU(9chx&kPqPX;|Rmg3r2ay!OCfM!;(=uR5L~LkR#o9`S?l+TQ-nGvx9WQu7^b}3Dft`<-_jv2Cuh$w!vGY`pjs`@64$LvUY@q|o8b&cC099O1#)=iyb~?OuY3}_ z`LSKgv#NIKH+n3?RgA=8dSuOu)eKQ09EETNgSMKrCZjd@Diw+jcSq`w^-TT@w5iK> zhG8m~sf1l6kqpXwzPsPE05aLHFlmSaREHy8CQ$+Yd;Oz)lbp9YT=C^QWTmS1@4d1u z`l@ZPxf>xk`)RRBt#9kP^NXO*biRtSNv9DW9_t!j3Tspb=OL%-kAEkQytx}*Ato)V z$;fL3=ocRTN=pf2@ZQ82nyJd!Q(+VT=G4u{f*noj<$pEZbhT~6nAvHjE2J;Zb0LkD z(>{Wj%QU(6XX;n>hEBDb#BUm&$Rl~ykTB!XqOtFxPx(IIhCiB>(e!mKeY^`4B%@+X zG0X@`I4`e%LO}|9qi=HIBaV|YOvrBTrA|ao8~)_bQVEW$RS2kV-IA%Ab!0TJXetia zei}>~APU;bdo%CN$&ABl&pH>1@AvNj5BkY&8uLJev2dFh_T8yP=t%ZaIn~oxvWTtq z_wJc|bGwIr_gL)}n!-l%!f*jXq>R4XpF79^rib@V?*mcOaXIZ^hHh@Ykw0SnXoOi6 z+wU))__6hgwX(;NT0DWE{Ue}=FtP4W6NlmgQpFkW=NgL_?>*?$ z8{_1v(C)8e(7*kw&4RF@Y&Fonx%Qj7#r~#G(=<|5Y*hi_qlw(dGrXs-D0|GJL;VNw0l!`j$ip!a_!1cu? zAoQQW;gJqgszg(&sae^LC?zFj0PbDyYOPS=6+d&`>DK|f6bpk_B+->4^}T;+V%J&2 zH#cS?Z|C0}QZQLMr!KL~%VkL&a6e0vd!v$M?WhV1bM4AeY{NA+Hp>}_IhEt|Ly*FO zzxJj-%h@ZAs6HBP-~JdV&jrI!;3#|1Yh>FyR&^|0s;3GsDSM(rSa;o>oI2szc*W=! z97dWj-oV!Rkn|+`iqg9oVjm%COx0${l^dgITsYxAWgM57VLvB2;Lu56wwu2cUs^kR zOo3aO`Bc=S6AQjKV3$E-vx)=k<_}mDoG^pMdVvs0Rz6L*aZAm#p@Rn_rMK7slE=HH zTlNWO7PCL3RHB!EdhuIc4iucS@UZr$tR2atO+~j|N*5frqEc7nE_VI>?LZWc67BL#!6XB?-xpzMEfa985x0gs_pj-44Yg_-b#-qY&+fm+{TxEiJ%frRkFPV(DTV5_u@ z`48n;`z3Y93dGTC9*$d%Ev&!Gxi*@wy?T9n+HGvj-;dL8?6t6W>46S8wRQDb1vyAx zDR?~m!59a-^IT#pSTvg!;_?6_5K$)tOfjbG&*I8C^?7A*5`$9 zQfC*az5TTkJwIW)P0q#$lbpDI?GQ4S=;PaPyAy+U()o=OF`v3Oe{r&5twXiHMK(XQ zwQ;OvwO>^u><5MfVPiglV;SvS2%lP)_Eq*Zc~chemy0TdR5b}Ayj~Cx(efB=F`%cn zDT2aZ1jVe5k1b8Ce&?c8|58^8LqlFOLJ@kHbLbI<0S}oRp~2ujU138o<<+4faZ{a3 zhPOURurQW z0uN~a4||Ko5cHQDwCmD9`_lGeBrM0st^DCNf9s5u7X&oqz`_+grGtUqVx+iPR&%)d zF3{Nrhglmd0f#}u|xapx=0U}z+ zJRI!T3m?op=f-EkHqyvBoQeshPL}9-Vh^#&QQ&SfF3liy^+E~?GhGI@LMDQrQHaXb z7`jxU6Devte_+mj{-zPDFjLnV_yiVc^17AIQS#~C0>moRojG? zsn_Nhcdue#hKMk~?D1Zc!Q_YRE7qU`}w_Jw{&P}?CM|nyJFD}^S&*ikbZs3PVDa}@I!|Z zGm9~Uz3xkss>=0A$9YtI>KYLS=&<9Ji@Tb##(0g|yt-)P^NPnqh0FEk=$Hh!jEV0z z!fN75SSRYO7rHZT8t5H9*8@J_KZadUUGt%D~-rmf*Z=q=Z52r5k`Sf0LAuu%S8dMc{Tc+(~qpEtg; zw3MH918H%1Z~11+%*OdDZ|Ke#p37P=UuVv+i7<{_$LQ*~jPG@a`#WhX`aJ%#CD2fh zsPZNS-z%&fm|NHMcQ=IZ`D4e59d6mM3&0|r(3v>H>q+Tz03J)*!xFRf`Z2mQ853EA z)UXjuR7O}I(esCx?x(CnrQhG`zTB-v@@-0280tR7xStK3(g`qJ+h}`o&AD?P*aJ}G z=Wh7;{%kJdF@Vh>I_g&e@aI?73FLHBS#3)S>Yy^>XJl#aTQ>aEMb7lN@%qRJP2DrG z-evUF$iSMfBy+RBk5`AyX9(!|!gp6!I(;HZl2R7e@R*T0p00EIMM8Yq-O(ECXj7Ft zd{$pA$zuvNg0-LSta{+i+$g&VBxs*A%*&~I4-<JIc91Y$X)$nNK?p3axy{Ju8pA z7qcxRr|%r5Gk=rw{q*EYPY(@0Td}_-?-_mLm@N`io@kG56eX?*BqBTJ6%m!62yvfX z7BV=?k$Ir96-p`_!-K4}BUfKu&`l6CeS?W!ps@3}X4G;s86fAu(|SVoiga4u>pxpB zx|CQNs#2i+C(>}IdNdsFBGST7y)Usfci(hi%@jg0mVdAq=w-x3M7DE7T$ zg)?A8t`CP&l;5H6vus%L6D7~Bb+#;`>3i^g#q#zrq^ZeDHr-#4N`~F^74{1KscgV- zIO?Q@35{OOdk_C|H-XP`U$D_2{RCIEE(G~wkFt`TS^uL9xrR?Y+*r$vKp2Vo!!IDF z^vf1$geb}(>Ez&jtta4mgV{Q}pei2Z1cR@yX_m2M{nK~0 zVE^hrJB!uD*NIwY2|$oe3la9dhmHtC5B4;j)R6Z>f4%?wQvDTY|1x3I7~KmxTL_>{ z>~TEFOyW8g^(jdxyxEOt_>)c!xaonvg;;w#`>Y*) zB>@dpsK38>`432gdp}byRm(D?zuH%F4tXRddsHY&pekg#bnvvinvue1KRA1d_3k`4 z;%;;)+~_*hh@#{Jh}k)91~$y-9-GsH*DxcjA$W#E&3U9HC2dI7Jg?lmbG9%fK_3QrZ+J4U85fY>bU}qslTb?)Q9w~}Gh{{?5@C%;_pUxypc`A`B zN>lyDo#C04F-yH?^W^tN}n19}QJPEsp;eBmU57vgg7dw97o6L+gi?S5`Ox}j5tRGInr#X2&}7fXlFpdTcj zU&XA@K-lsNf9s-@ySVBfgVq2PP<3&`{EaSA5=_%cwD#K>b70k&Nu9 z#m%cu9GUL!lT?)w7Vaw8FY2fN*&XD4+H{j+!eh8*JuM2oU&~ z<KdD$G^HeB^bQC$ZDvF8g@GIJ@vO=L=YlJ}19%w}O-U;=e^0{AN# zHptUzPn}{!`_M;FKKADjD$LN~vO6HO0?Mpn{nLfLRVHQVV8%6+Lwh#e&oZagv)O8U ze4?ZDJzg_pOwFPlbm-A2GFG|^?fUK0^}TJ6qy@o^ zz{c2i ztR4+SGfbFw|1mijy7RaSO^U>eef|~i@-EZE5#{k?VYmPMj*^g@LxJLk#SxT86CWPi z&3Y1>7t8FAyiW;ILuh^vK@G={-n?A+Dp&2Vvxvt)xzhyDrwB^rDB`a1SazL5rjPWk z+)z!3T$NU-~i7eVYI+I>}pH_rK+Z_RS=Ek8gyzm9ZGwkWiM#-;DwqrnISS4$C`aO+Q*U;~h)T z=823)OdK;CLk;)7M=C%?-U64?nSsGs*70vLCQgf3r$L}4!xgQq#;BV@b zZI0xyt?~?_2V)!*C9nH+jrQE|>!zsPBQn}Cba^zgDz-Nt_xYceGou+e>ZUe_t1v|* zhJqudaWmX@V2!@G#1zQ=6n3Ee@x*;Lq}?7FrlSQwA;9NA_;{ z@^a107T1tfKQC@9U+)LLIpbKX4=XRe7!eSwoq4m@Ay zby&lFZH0Ovf@u9zlYZ63T>o>v(qt;5m=w5YUc}ID#jj7h&tJ9|k+a;847W~=|7k=7 zp37qRbHv`U?WAeWu|U3_5~6vNY)SUof}+ui8o9@r;1$|=kg6w}9*9WDNe!)-z^z+O zd*s%*@>z$)ECrQ8|AZOg+}4i~kYBwF@HJwka(7*=thE0VQWeXXl&r3{8}CG4Mi`LX zP~MDDwOCKu(g@{i@9nz^afdm{o0!a|g}*}dyL9*Nc6XPzCAs-T_Hf_5GV$(9Q!Bfz zNdRh6nAfwPtZ7B?o90Z;&I%uYJz~Q!{?m3v@o429%dS}Md``YXN5(q4#PA_e3C`JF zG1je?j80h0CM#5c6U;bI!SZI9t24l5hzdyHyjU>PFgRVchOed>I{heqDIbM>Y zqFl(tpwL|eUS#xCwBr3&kp)|rzKhEwQyK>Y%$ha(%KP-FU(w2eU6VUH8hqBkvV71d z87Ip~lnoIs;=XG?#9lf#)bQSGAC82K5rR9KH>h*y zcRD*uakN|?Sy51(U`ibya~WHuPJG%ei~|G5=uJCs>b-DTDJ$6{EHfUwCfw|^0YnBSh56xl|F>TdIVinemP5i;A(CC9ENu=GiFHqe-ZVLah1Mt*Y{MD zJsCT@sdjcvZZam@O*`ARZQHhO+qRqRr{}(}`+vPX-<)rb&pM7D*7|yYzw1eNJbyY; zkn%D^I!r+570-p^*ZyWlu@Yn4opqHgkln+tQdWog%7CuUl@gg_lSR zzaA}{A`ZvN479yjR{Hb!_S4?b=0WM4-yKF|VLrw~C??YuLb_b7(!%$!iHaSWHR4y+ zUxRCLL$Cc6c9NZINX?cQNqz@-KS5mavdq8+D|(@vNK^{GTb4i@zSP)NHMaji>2K&L z(Xt)`16p zZ6PS;u;Ly|sbmf?AIrgVD|_LQZ|0_!MjNTPWcd6M|4J#IzTfxV$$O~M z4q(%G2w~R^9t@{+3CnUr%roh8mt<$ENz{v)HIvP-8r!!^*odC8%(apyV`{AZOjr;} zQSGusi!5p)(O(^NW?}lpXwM_%DH$*sGOxX2L|#nMpx!kiIX$+N5idt}X_ViZQoygPd78X%dZgaUPG&j>||$L0F-k zl?}F(jTm_q>m&r#18@3iskG7x50fiDUY|{IY=f87e!(;yEs7G3EJ$^y=xlO*@{gjPq0M;KFQjz1e*{M1@pFYP1;hr1{GaJpIa^ z5q^o0Yuh?QHj5?Ev;?Fx%!OFrmWoBH41@T*;oRvQIXqMB$c>sGZI=7pJy_{OEf zb;Z6&oZ(zoW1e?vZLwN)4JT{@BBfO%4Y>q$z_jE?L1RS#>wyTRq>vIZpK;+Xk%4qB?f(pWrk`aeQOgn|5%p8oa|7OcJY z_kXHw9Ebc(bo{wXU}g=bi0PVQxoVYAvyeJ~>MK4DfeyG-+^4d{ZHX^TV)wVe zof$C`J$*yqUw0*haU#f$5S;V&S6$_3Fe!(Wdm5^X0mHn_Ii(c6<7@e38E6B3FD|;4 z%lQHYmR&x9_n^Mf&EF|WbeecHXQ;&3r%UQ+r)m#Vf{;|ZPUSRG3{+~+xTV!zXGzNv zl+usEC)oV@MCNoQiUXdt4x-*NV)C{*f%oJYey@82T^#`~bwNF$px8>BC^M*C7Up63 zByvb&eq1EVMQzMSPCzB)1f4l5Q#Hdq;k}7802g;{ziilFX4qJ9G~U*Nd`}Ex>X(RG zhlxHrR9;p$D@WHX}oalrb8Z=-Lbs;FNf*B!IG5H`m@ZVra zwLDI))Bg9jEC!>UeVM-9itsE^h2I!jQz!-2s_-$Q&gwaL$HF!&jKc7M>6w|KoTVjn zn<3d>K@Ab-;=eVt;saQ-DuuYtPVTO_0eetTv)AHNMdnT-zYx46abH;M&k6bCLS4`T zXG8uDhfB_eEvgW4-{O{*;_Yl(jCgxbP9n-T(ehuAEuKdSl_k&?^4w$NSA-@oPTzRz zYp14Y^yD|-P`e=$U7~swGw;QyC_!)=|Hht%Ny$hLc2WWOR+bQA(x*Uwy3Nm>FE_bY ziFmKLU=%c4|0Ajd)=}E-m?PZT&3*)9p7O=?WP|!IXvSg^RlQ?RaToEFdPYVNC+TH? zjI&l*O|I4ZP8Ihh;=%mho|i(=)+Z#k$L$Drw*IY+@qr`AC~@&Y(_+cgylH>N)3cgD zCuu=RR;#F{JL&T5tb#_O5+GwxLB^QSZ)v)NY-AHCYmPRKSk_;(&`=nElJb0CPnVZ_ zV$H!<-O`U`II8c*Gl)hn29!00)b*%IT$g*+CqqQH$@0n0MP0wiS%09SRK32#Rz;^=FTCP< zAhtKumiSq6AS+-nhp3p93gs4-8<|FR6<{~6c`Sa~J2vx*%HEGPp&~=tm73x~UrsGa zIdPxQ?Cf-pdYpDoq|uN6w3<>Km>YiIi70gd4@g#B)jRh+I!@Vr4+rsCfGVqm92}V% zi`etzhp-C^?LXk~PrfrZJ`T-EX6a=}KwV=i7Eu$3BH~gHjfYXnD=9-Iv`~&q2|Koi z+Aa9siV-F?UG#DyV@Sct=H$!!5!ufT!1K%d`A(Un4D^V)-lR_Q5aY_{s?0AgEF5?zEIilgkYi zA?6LQU@(NO zzJV%XN6Zwgqo%(wwT~AE_yGr z?E^jX|1`ybBwGkq&Mgb9IuG@?u$l@BeBH%GMTuI3#4Pv8-0}WqN5tfBkRgiv5)Ce^ zd$^QMIu6DC!Gb9&tF5@S`jQ;7hjG&L3x{Al?>w7c_7C~{!$qX4`gDv^T3VfnHNz!Js_e2}AkcFWv z^ex$JDmYAi;_BzRzoj@YA+7Eq++b~0b^HVq4A91cv*jh8T?BYZ=QJqiJ!Yg zrUz&j@f!xdD8D_Jv(#y@AVA&pm5a-2ayJoz(0=HhqKC_mn?G6`VrJ*hosq?~c@!6s zDj0Sxq9+>6y-W=A_sHfoy%%A-6!AJEFyfI)7bD)>>O%ydpt@ljJaG+1fgw zB2%u&r?kDda*{C#X_h%S6r%V9jZ$roJ9MN_S6>pU+X>ajOHmvgr*}XZ;UUta*i1s5 zs@omPUAWfo6b^WS4S-8J5_@l3#*@%T$Fvg$=gE`%2K^sqS}Xm1?AffgXuh=gETsWSw-&gRe1CFcDw;6M?dun5 zD)VWv?d?n$GTsh}959rWbJ6Msb_FxK#ijUY-_BU{4uss%vHr-a#=P7abmAPLoug&t zyx9UV;7b&azsJpyvkrIJxS9bTe`Lg_X@%5Kp_hjbuA;{H(?A*aw&Wp73>4KG02Kv< znh?V3dT&gGu>~JfjD1H2zSqj8#g+H&OM<%c4nt3%6dVij&82wc_&$Qf%6xMTn>_@nv+le40GH zVpx_JfUJ*$*VCC)BQ4$?pkk1NMfKgl(e&6{q!gR2^piAGbeH7!|MMK_Mh^4VTzIc` z9?Sdvf4_Xm3#E_uFv-ckaJR?X-MMPMzrXH94BeM1?2`*95s34>OUEV(^*EAmmMo;R z(DMeJa*+%CAm7l?b#d53XAl;b?T!Ps-gekIu-yXx6fcCYHw{eBkm}|s`Zw`w`|Wb^ zN_oA(hF@Gveykn$^$WRbCCx3UXuG7g0T5G9iHxg=%!(8pd*HHEF7!EID(w{cm}Q1y8A7PYitgIvWB`4=|6Cu(VuE~%t8VG z6u5)xt5yxhmgr&RWKs_$%ynK_D;NI)O?&gM>x>4cCtTz5cPpxPby#h0Cv%Gz(I8#| zdV*H?U6$x~jzfgqUnbXv>0JR`np|M?%A(fio5)ig4{#3`5dwNzyyMAWb0)vY))$tp zs{n4%YU)9hSU(4%zair$1?e5+cCC8zE{- z?rU}g8`xl7&wRSYIk1Vd>0WaX1S(+Im84wb}^MG`7Q?wfV7b1p{7DdUJx6W^<^{Y0Q6%S?K>9 zSDIG4_w;wJ&!Sb~MEJuF9&9(yvQp^n=AFZh5Anh8kjPAm${c%L-V<4)wYbkI;j!bm zV@Pk>jmdZtfJB)?+Q5R`6xkgPJQa6ylFI1tM(-vlzZC_Pn1HD)p-2V)8z%C=&J{`dc9pgKAX}=5 zQ%?Lj{Chc&|0loi%c|}A@FekpDYKv`e?)++Y(RuS8uMW4Srj=#J9t`#Ta1Pr6rfh{ z=?Phgfq~Vr;^SM*J_$gAEpo)qZ?$#>2fYXwET`hA{cO+k()Xe**RiQb=LO9n?475f z%b?E>$A<`qsqMK zzjB)MF4hN`bdy!ZPSh|7B}?alfhuq*Q8CWeQ2g_-p!|}^Peun8Fp}Lr$M|Z^M-7M+ zI@Dv4v{{bkYuQJ7k%{t&EVLAG{s=Ic*)#oFS*KhwI*sHbuoHZ4;M3&w>6Tu`+MSIv zrT<6PBpDCSUKMTh^$+Fw0rdYAD^D{h(UD_>h^{WW|69I{qnOq$Ir9BAuAvH0K#zmS zYiv^wY4ucY#1z$Bgh8w5kmXfK=Xwd!O*Q$JD!fR6NQ;8;gs{Ci3BBg;?yk>&@abtl zj2`+zaeiwQN+u?`l15#`lGQYoO{MP@`6Fdj75$S_{N0CnD!RHSQAwU6tJ9Wj?2j{Z z?4pm_G09heQV#x<%#gy^Kkbd~%*+qwHM%>B$Hsr6n4ud<<5qr1n<*mTbHQv9$N)(W z&V`7h(o8*W)0B5r(jAcSlB&8B+A)_oT)L}d`M+}|lUz*ek(HIjRDZ(1<)v|J^fE{0}3kwnW|nz2a~RX2fd zZ#HE~4NWPi-@=-ltD z-SJC|r;P>XFKYwU&$LifygIB99HVJUKx5?azF8JoE?Y@K9Wup~ld>UYWLeo{^ zl9B<7)P9yk3U4R8cYba;PLxLURBLJz_4wpyz>ZnJFfQE!AXYXV6M$1xZQ_Q2i`cf8 zQe@mSHWQjweSDU&NN~ZOWT@3&c#x*rG=Whyk>#MnH9OjaxJSWX$T6~NZwQ#Y)C@~j zRM9;;4^P635EPQfJrPq}wa-_vLO9)n5Gx_&4lTcR+XRhQ6u*M==>3ktNo7&wSITW= z!bn@HGD>`|ySA(I69_UF2UPA!(E;?#4h4^DSk;Hu<>eJZ%k5^KuCJ7z-m^?%e7e06 z@Mw}(TlH4MR}5oK@sL|_7jLv5As+?`^TbrKDWZ-8Qp<($Lu5q}f0LxU3jwQjP^gUu zJ|3^#ojT#3OCZ4MA{Lqx-N8TA8bd%l9vlwH{oY?$BYCJtkpQ7WPYgD%XW|oIoxVLk zw;q2GKe3`))UR>1ZCKW`D5g~?*NFqwhe4C(cqKoZ+stlL24=%dUSG)*PD5C*6fzRj z?IT#b*$Ih`Wr1&6o258JsbAS`59i?`aWPn#YX3M|xq;n*KH!gLj)peLOnnd$2yiG< z$`K0z%XMEfz=>i(xn;SZxNB(a3Npl;_grRbMJp91950YAy^Et#Zf$KUkN?>J6AAY! z81bQlN4TnU&Htu4$lt(OaM@THg`!y8dOep%)+O@3+r5fEj<#}QCgwAlPwnRmDPq&9 zH|s1>9Ak<+goxwUl4&J}t{9MzN^cFTK7$VWT+pboqAs+eDoPHl&)C7@pzpgE`qpkv z3VDI{HW0XGa-BOcn4cd>*nnLm$wfnVD%=P^w){iaz5AoMiINqv_c!_h=9( zD)x(kR>$@8+ryCy99pW7SHM}HnVRyye+3;Jc+E~vr@LK?aE%QYYXbFo0j5yll~|(u z0vnVc!^2Xz{kZ!^MS2i_(a$D$ZkBhCbk#h;J|B0ms46P`Itp<9HCgnPFxp-Jdke9y z2b$;Ix|s$zB*7OUfM_LAq;ZsHr*UyPu!%eKKhF(OmVDfnVcvSFztKS@9$%h}hSQ-H z2t^b?-jNRK{)_HgcIiYfimL-ka?q!UR9Knzw}z68Df^Yqx~H$+Vsy%s1sV*C^Pplx z?r)q`?`;2_Pl?e^jNe>yB-eepozfkg{-!{&DTC~kMC%;fxhU}HBkw#x`n@7hITXKK zYVFuP0RIjuLT~T}^<1)~KV{xrim=<9csApaWZ-x=oyk0m+fQ>cg$`6HAku=zjK^%c zZ|XdsIq+e}Sq)@2wZp!JDr5qBLw@?7o6$5HFE(22H#E3ulEx&-`C zz`6Nsv&X;T>2=@327_o`lwauA0t>SF&5fm(=x9Gf=0h>>LQ=g6lxetb@Z=`C!%|En<7ca-Gc*I zVLC75^Xcq}UufB8d)zUVa>2ppxF>fLn2%=Xas@GsHWE}B`nGIT=hq9L@jEk$4m*rk z{Re}Eq%~`D7uv?Fhk>^fJxy8cn0%oD+(~_chTIefe}*B#!Ogw$EYs6a6HGcNMfRIC z1~$QoZTdasS?;UNh$mRd>+|{^>srREYPi<1ld&o*p#KAGK-Il=>8y{G4EX zmW|VU?im~h^1@Z0KR`(RJKc#H*vC`4KTQ@D_j!M3flz5bV2g~TMZV%iG^3Ww1(#p$ z1`PH{ampLO%JO7;yTk8}lmeDL{{1Xuo;Eqn{&qUhI1AmBF)s8WA_j12sl~`KpzPHt zBUMYyOEdyY4T#)dN8wU1vJ~aCrW;NRj}CfbWMNI72s{J`Jp;IwkPplMP%ab zT?|WZcC;|37MrCOny6PE)Mui4z^FWpIrA%dD*7*LL@)*Y!?cNEyEq=xHiR7-VK#H! zST>IH*a|g%BHemtPGji%Y465Nq9}EYSiZ$uhKDw<2MVZhC`WAzpMhTW+gYHYd%FeU z=+D-jdlAX$__*K%o$|reDgL0VD{X<`qT-r6YZjqJQ#)h=U-r*)KX@#~=mZssaRv#KWU<6@=mstXF3g6WaW8J+#RwG1SVKz^sW`oPyFd$VwTH!c7%Eam*CK^Fj!039Is?GL~S^6Ran2Z~$$?hJ1 zp7^DPZpnpkW=ezTS(yW+6tPtXh1kYLo&L^@w(XNS2nZ5VT>~1b?bUE8!OaAhq?)(9 z1C9hE{}YCZAyR?K@qak(ZR)%*(ruy6Lfu zcf%OlQQ<`WI;idExQp9x{V0t+_iWD3_8q;b*r-Erxa2&xiK#u&28?#9~Xf?)Kkv zs3(BpEg6)BQ<~poH}KtCjCmI;pT6}$d-T6}KuH8vEPv`*;j(5#teD(5xU-t! zMd()259|m*>zZcoy5@kjoCL}B>lPfA?=5#Mpk(&!U=JgRXUy8;>t0JiI^?rE?3XO( z0hR2@Anc1tt$=fCT3!ThPM7)kDRh{e-xl*@7H0KnxtQ1^Y&CqI%nin!jfnrI0BW3` zZ53iM4VL#go$IS;mERBPG?&}ZieCIlXrxYuHTD`J8g(Y5els@xDc=Jbc$FPi)rCO= zi0_}qbh-q`CpqFL2?khU{T8v?3o&sH*N41aXF|LzCU7z{3FK4^_uefiQN73s=1%=| z|1SFQW!P0TUc+s^PJLaIY(rE61s8vVjnblsXp3O%yl%Lk@Z$BaYQMldju3${r7J#> zV`~lf6=l&^m*L*UKA!suya>E6I|(`yx|nLydK=N8oOtjHhzqeTWV`oeg@(X|!yIxP<&*!bNlM&kXrTYK4N0E0>?*D2o&T`g1&Pve9jM#6Q!8~ogvUYa@HYN8b6Eq5V2 zckgQ3gB@k~-##xdF%ckX3$40VZS9(cYpKJA&>fC{u1G((UUZ(0P<70D1^n8KA7Jp@q`x)0P@(GsSQ zh1$ksq)Oyf5TA9abyo{5#sW805|8%ArnZDiR^dn!-{AUQIpUxk*iV==X3|*q1i{^B zPW6NLhV-N5QzA~G{`vBvM?6>Bi((ue5@(4jDbhmUA>jE}JD##?jb<0N!vraYa3cCv z@z?$63jo4F9t5rD0kLD zTm*Ok1lrjavozo(mQs?>_fEMRen$R!yZnTelY(cQ;ExwO!v!#AuX_!iAE%M4V?oif z8Js-%l%VMcUy(Sv2LNvb{MY`*$~2sb9WjFi!zwB7c$I}MhY;mlCo`<|6*Gil5HwYN ztNNes8`=U6oUr0+``xesg{3s;2M6+e($=^C2F0(w4Wv*XmeAX+l)+8zS@S?RnM3!k z4LmeH(=yFIRy?<}i@nVxdm*)?Kk~Y_1Ib7bkhrutoY=sqp?}CLPyI9} zP#LY1rjhuCb_O_QRBTU3ajk)NB+dlE?z~|kARr2Eu~r+~S->e*jR-#3Gy8vc2m%9; za_PSl+3&aXHOHdcNs7fbdcyq7m}hL`j?k@(R@u_Et$D+nT@&C;JFw&;hmG*(LP<7f6 zWOdy0{WHVvzj<{DK0WZwg>S=I?@`5%^1&AFDAw5t4x=s+=T8Yb#pLQ;5eZ7pLVD+2 zw;CvI6*(4%3NRbqMs2f#mYJibt|i6*j7W{Pihemw3Un7z@s9cDe&lq#tNEp+oZ!Eb z+_sm{;EjF$Bbb#m1KN!Ikh+{h3`k;@hUsS~AE+;J+BEdy!AwguPLjzYQj`awpUwN8)6;BB?QAH`q`%}I^X=>&${p&M|fVvNzIjJ~{ zv>YE`LC*h|r7J8>aSy+akYA?_K2U~O^`$rNc<(?e+;qQ;UO;qLrXOF@Kj$w44;M@F zCF+d2yoy@jelqXV1;0w}KrqtI5T-7E-HZL-_~NpP9X4F9_PZSY3i$xvs6ZrvA>~9n zRNABpJrPnNDJ>pok5=v;VZ{hcV`*!Xwie!hVpY`E11DUZ-ur{7VUvY zM@D;hE4SB9Fhz_b-=XTKS>q1Nh-d*eCbWP3o4g^Jd;j3?wPIR28}1_9;VFm}ebSYc z1*cYxa0h9*P3HVs0z)&2^zLdxF8>>?!j>1;8sm}4+lmHFPbM@}3vE1^5@h*+SxiTR ziuu!suff{>_%@w`mNR0xOO!)|JFD%D>`s!lmi}_E)nG|?2Q!e`&#n6{a(6M0~pbap9VG5tI=$L{7SF`=dPNYT~>t58@%x}d^k$b;^S<6HW@pf#*(G5 zV;GVvB1~XZMT`DVOq03Q-fv)=a}VZE_}sCn+w8}T<9G{ckqQQX4#HeSc}Km30V2Gk zQo}OCQtne&E32r}Ge#9cLEogD_35QTzV?3wS&zi@UVfkRXGW@rT_z9yqUzF>H8%LC zZM1^eS4;}c^F=eX;QIH|QG_!0*Fo@a=AgbPwZ=q*)*o}C<0pZETx9sGk$%5Rrt4t) z$0;+t%Q84iZ{eS~v2eL2CXTOgh zH+!r4tavZyk$p97Gx$kYc@M%y(gkX z$5x6=;FT0kh!5gDyxWsUmd($Z@uwD3&c$?|Jj9_u`LLbFHEv;BNamRZ-S=Fy)qBlh zCXj{0JFN((haIPRSxDc}HFm8bIh}}kzSoInTe#aStZWLion;KA zaSS%$JvyChdOi~oP4fj_YD}%;+c^(?Wef-vXAVP?C)A0*!Si<*%CN8}aTTX}$ z#kl$)M9}d>U-YWZlc2pf6ebCkv$WKnKp}nw77ijVX8eN?l z%=DIAJFpH6yn|}=9tku{zz3x=gFUq)EwH5gv=;|B-2ZLb#wRFsk58c`zR+=uge%E^ z7J85tOW~vPQQ=P}_5wvrDC=S$JUcLpY$bIGc_8bGL>T>~gx#9D`?eBfPMt-~bbeXt z$r@6m4>Z&&J3q}#J2$UVeukPd6nBdTg5a?u- zy0bdQpT1f%qApr^PCSer4;Eq=4iaHZ;L$cTc;v?KK&MN==4I7QO`+o7 z1DYvm{$5_Ks3HwA&Tj`tj2v>qlE~7c#?@Ds5ckje=`GHXo)ifj*m04VUl0*BY_GnT zr$$AW?JgA6rbR5h_eG)N$|WNGS#0MRn)za(L}xr03%fZ|ZzM4gnr7<{zk{ne;VxY+ z-`~us3AjNEc#S)E2R}HFlOuC@wN?)b1PrtwC$EtQx(C-@J!@mwFF%`tdg15b~~@mklXX?v8eX5k*l_UEjmXG9>fbhJ@M+! z7Y% z7S&4vHJyxy(>OthhCH6JW3$FrF>XmedF*qi$eEvX73E@wHG^s~qb9~gb+mzzCa-zr zyHdpyHvCPcQpbMA)EvnT*{V3Mm^7}kd+qC_MitgM>a&Z8HI95V3h|}uOwZ_>_LWCOgL=xL;v#DU|TisdWKfR!+-&p`{ZM{@u7Zv0*9O3a`RqhGM1}=xV z6Izm+Oc~ub%;i+v0nm+}fdEQpSth$*)~eXy3HN|3c6D_*=Y#LcR@}aC>L%?E`wIR~ z!LPU}2;~6Xo$YTKXV38~tp2I{JJ&V#$8iaQa+6APT@%d}H>9lh zBVJ8|=iw%z4R&86M!ZxQx)h$ zfsb>omp#{yd(jCr1j^d{Wt*jDxPAMcuTAZr<3}kad{}|L2!N!p&kq2hI2cY_bg`pe z-5Fqt%Y4AO@EKH_j+iIc7n0XfT6;8-*rPRm(>ZY& zuGsbRpXdq7J={R-!*V^Fw2f`H<$fU}-qKQu_Gqk~o!$f6Kg}Gk?UEFl--+ARdELclHr@sqC zzgic*)wR12mBG$QXTCDge8q(R%<|zQ|KM;pPlA^gn7vB^FhI4_&R#)`D}pqlJNUbw zP3%ax@?J8AL-uAY9AOBN5(Ihj_--!U+iC11wFfe`s zYdS@3q%M^A(ib(ySYBdd{GWumdvn)CDoOubSL0(VL!nC4WOq(Zh(l*&TM}lH3#<+U zi?-YW4qh+Nk@vaaZMgLeU_XDh)J1m#tffbPP$pqc&`OXf^Z zBrpObcGBt#o>%?dV*+QZ3&!?pqC_VX0CuYCc1=s&_MU0Z-Hr8IUYI3V%+m%u`O{t- zC_1AStLuG$z{s+RrJ-6oo*XJN%ZEG)F@O~DJU8S;Z^^cbsIwYIXeDRn=~qO>ErtL@(Wfvwpdv^{`D7*rP^prIU=L*4$h=HoS#$6;sA zeF)f)!Ch76s6m=-|BYJPX|&`+1%8;kfzfc6RBZZ1kHGH;6{1xWG9|xfq72V|o+U1H zv{&G*xx<1%HXj2&Ta_M~;u`M;VZaa3H+j`a8_g)0yWeWcBcVfn=F>BQkrF zQw3lMOgniS$5iq`(NjcdMi3!8(-3WiY zlNo)9q`XJ*EP+_+hIzZhvb#C*sxnfZCKx4al@_z*CCfZ(7aZD6z%uR&O405GmVtCb zr&0G9`Iw=hqtZq14jHOV#&ACtN1p{+CGp(C)vjplgZcTT$A1I+N^&oEzn)*WZg0Bz`SCW3Z#-72XM7YchWXP!i`CCPUPf4~b^{M= zcS2P*rofe1`gxO^stXQK(h?R~PBq>g$p4AgfZ*9r}jrq zH*JMO|7qC9*|zuB04z1r`A<#Py#n^kz93pQlKWliNu@<3iYHt?Hx_n~?$!hvgbAM? zpn8m1j|b$dWlH8Q!7SAPYqW}g>37!IRb3@#EB zoh{_?sc%L)ceevurXux-NY6gZkm+@<@W4b2Lp%5Aom9~hjdi*cy~=;N#DQhLd`+rF zcnb$s23PRnAYAvZULW?}*)s>A$3250aVVs(h8#l4FGK-Q{T^8{>vI21F}DEVTr5PJ zBY=M!0D+T`d4dIz<%bn07LM-*Z%^XOO!}W1BB`MRRtDtC?*G6V2|4`O6FmZevtu~x z7sjPO6HkuE#Rq5JgKTbA5f{62^@7RMZpF(QXBF&jGFcVHd> zoMGW-KZU361pdp&JTB*I7mu#8d}UQ+QXOeA>n zRAqDVU|2hs9@c(BZtuAfwer61RhqZe=!QO?_I5_Y?`H@uEpehzgB6jqq8C2nMDNWW7Rf+qxtAs1j&6gAwAYpPak5pc(6; zlv0y%(>E|xUV_QJ`+e47v!wx2XJ1RYUwf8IX`V@=n-P0&5Gf$6 zf{{?PfmGD5v=%%+J!BvUEq|MaP6a>dWe{`TNE@5S9Ua``H>?Ecw8G=kUHJJz7TzbY zm4-~A@-O=+3X3jB@4KdDW_B#%%AcThP*O=(Byn4I?u5nf7fIPFlyGdmZ!+!txc0D} zrln;9ddf6cQ9(8p*@-N|y*`sKBA)L+GgGQ{d~*(&)m<0JhCdGn&iEl+2o2KT3L`F| z?L>`0%-|v(iL<%*_?*_?RBlJH$qc_Ci@J#2c!G@eXA@6cZo>)B*>SmAaZ>_p?`Lt+ zB{-tmchw81qBBQf9Fcf|hW1|0K-%$S)FlZ;b`sv_pAYz)cCxxs6CRAVZ@SHEDKp7` z!p10_cD4sdz9ZuYRR3a_mB4AdVyGodm5+|`?|Y3RE=v`8wx3Y-vZo?>d6a96yX~T# zZ!cnJG%d+TIpUn6&p*lzY-kE5W5FL|tK2*$B#6{ym_46KscKz&N!$o+Fws(@*UyXE zEu}{EG=OGGmZinxqPLr~p`t)Kpo7BZvBz$2<#bFIn=sYuXzp+3<*;E40(}=<*Jv^5 zPu9Dp%qR>6$+aMKkaM#_(1a>X?aJ|h{;9v{o#%_IPVd<_-Bjc-OO8+AQ6HgTc`45% zm_gYF{~`jpK?@_`AbR0iOh_d+P$CIQjGp~9so@XAakG5CF*PJg7jG%DFq^D{Ta@MT zJjaf$!jNNgEjQwV>3a#X&pn*8WYAedL9sQ}#T>og|5_#eEi6NP)}#P6YL}gdb^!e0 znJI&SYd*EUXDJC-(8c07ffM>03{>x{1OeAHI4 z#^p%wm@`Osd>f^CWfLwJ)u(#F=s7rw*VjMZoP2q(LyQn1Z2vuW%|nqWCEf@Rd=$Sq zCNg>1j>-orQq;50Z!2PbnJO6kykCz3$bV~l9;y*p`m<tz^WZ}E{idW(|^*fNGozk36+A1?3(6x?-0fI8>QfwA6 zhD`VS);16!1i^@{tz*_eB_qO32Cr>rz8Fwt_3nVLgcC!39R(s z12(%Ym3jFWfBwBi8)(l`JubW+bHS0N*5mEu!O{Gpw?8a!d}!`w$8DkWPw7A5=4S!t z58sOoOAFm)Y5&2B{7gJ|J;ko$-BtIjMfh?B<7+L<7QegU^?`WiG_-RC%AMKji~S?* z3$Q+M6gH6Bn^mr&cGY9IC@djNUzv>O*yy(dmW zQ))JC^8*LJya z@o&}e+Ur{e+{Ea7?4zQr2)pDLyMAsU+oVGw$cuFkst2jXjBA2S{VT+fg(uxGx-!NLc|mW!v%M;@R%)3CfjJZ=~+>mXV4`|FT1Lc88iSp6MME^u&9{clSf zT9Cw)KhOwcN~!Cr)pK5J8guDsjS4Bw@?888`)(P!!hN(IB_h(GVbxvegn!}aOcYmP zv6>xQ=U0BQlsfXQ9oHd=$h(737w#kMXRRsqy4iDTROP-5W`jum9Omc}p< zbi`2X1j}Jln+cQ-zn}5o#47v?vNsHNnHOwns5i})6)Pd2oZUFQehldI+pJ<@FL^XQ zH0p*rqA>bdO!b*Yad{@FJ+7j1__Tpv^eMfC{K7})PGN3lu7Dr(ND9r!-k;jil7b%` zpFzT2fO+oqH8E%xhps2#J~bV2|F^aCBy_c)Cf3M1Ckb$|qJ~Keg862e)2H)RG{l-p zj8kUG$ZfxomBU?MG{3}71(FcWziH`}gI#ZV<5^HhPrFBc~8snN+FE_d>zd3g)2Di1v21km0rD$esiEAzG zTy@lXb@kJ5>VMpAM5xUGAB_^frq2g}SKk1w8~PuHUE=Fk%UtZY&W5i*=NNdjPnhv?r z^Ki&(!(r_XEr>I-wUu-?R31gP<`>`{Eb)aI8f;9Ut@boQfICY$=xOCc%pFu08<;<+ z4UlJH)+0o+A=PRUs+% z>oG5EV*2Nw`DS&L79Gcz@mJzC(8beBg{*0oB>PxIpY|GKrAAQVbF96&0PA2?KI|sy znFd^bfLE4B`i;T%ov})QK;wTd0RpM-A8plJR$&8L%2^Q(4OHVJqN2)Rr?|sP3pubR zIs%RqmWEi_t&!S2Rx7FE&#ny|(1H1nDL@Oh!244qkfbC>tBc;-E+u1Zz@Wof6)9<# z|0J<7Tnu;>2`MHc7M$+>0y0Hi*qx_a&PFp0ywnj z&b^88Y>Q!#Z@OT;wNA_c$(moFclJV=@aYO3uo1C;j^Pux!$VVdfbnGeGHB>%!Vmdx zUg;=^gK#v3kutQ!T60l_BrVa?4#a(@dl8wPP4peo@d^Gqs0feQd+p1tgL@`t&x&DO zn3>G?_}l;0X|K7-o4Jnjh+fqj=%&STctlLu70-%*Q8n)ebtmM-3zu=2(5Gs*O{U)A zx)(yupnpEi_n7}%0p7K~eN6RZMSZ`AAkKJF1cmxnTWfCnSaU%qss0{)gIi`jw0?fu%tM~3;k0l_PGi+7eQrPkr$XK79Szn2o72ic3a z`sNMyjwrPD7K=87D}y9A%iN2rOuP|7FIyP#77vXW(D0OoOoF}r)&#H=wuFnRnmQSYuZ{^M{C9hh-|AwLNSTJwiI7v-emIe-N z%3bgD^efOiQBxBESn#%>G;6rZwE#Xb?7)koJd_O_5w>^)!3924TI(5 znJ4E9g7^(8(bHMY^9v7PML+OeG-r?Ju4wr$&LY~$pN zd%ym^G0q)#oPXh+?^<)sXFlM4*qyg4-n$@*Et_9pjZ#eYy4gM{+O{o#$KXC@__yW5OQ(G8h${MvN%3Ni4WzzqvqRuqvAvOmUMJFFw&(^i5QD0O>qjt=^TzUFW;q zT&2*dpV3g0ZuoI9peS-|`wZQG>X%PbHL$IW*@-6@xsM~C{Rg}2z^sqk&*BTpBZWGR zxbiZ2)!(W9btSY%IN>tdMkWNI)k<0lqA+=%%l2b?wrz~4LtPOuYeLPxkY@+y0aH~d zK&D+;{97}Flfb9Sj&TFV=g!1mU+36(rq#B!PoQsF~>!P!dXEB$}0e*Qm8^gjjk>xw>Z!obz@ zuTUKCtXS6DsZH%)>DOD_JeP3lG0!XYpbMzA&Qevzs=i}ae~`5l2M1l#a2}G{eY{!r)v&8O1#_}iEJUHRcZF}<)G00 zTR;WH1+VJc3nz<1tw!jYWKNNFNvTN0IsYv&-&aRdK4@E0%OIP3Ij`Xh?+w^cD>jJU zC>4QZyV1za@EYN=%_``AHVY^C=)Ny{S~yeYb1P+qBJFWT>-GwFb-vShRk3Ej-Hk;TJDH=7Xof(-ApPN?HMw#^ZHFeN%-hX_Xq2@GC)zf@iBy z$*lKv@8@fbPzN)zOnZx3n5C#SS=4)x@>X5U9t+9(1CL`wi^=4#XXNoR$PY)%)ybI_ zgM~hHL8UhQxtwcQ;_~S?j#=a>v&pS~vR&Ec?vyJZ)lC$i+IsKzebr#K4pr2fF& z$+Cs-D&kz#|HyD97qcpuE#ekX`dd!I56y<9dL&>BNtA{Gg+52_y$f9y%>&wP<+w>- zyBa|!$B>pVzr3LEg}H=~K*+dJ!8DQNY(lb(qU@ewCVP ze=Zw>Qgk?EGFjj<8z`sZ17zT51MZ~T^Eq)kw@V-~t6Q3hl8`F8-}AMTp+UWEaP0=B z2L9r)`EFU|DY(D;xzwsG?zoNF>&SpzB%+ANh#~G*GG z3PSau$wlh{5VYf1$}J1!-CYPN>k>EHRrRQvG^w`w0waXm8G% zb*mIXhROI;@%4FrP!L*TpGl})WUKx;8u$WX!uR$H0{bMMT4KapbJ59z~fSr24o&E_As#b3Jarb^qZn%CCS1GU-7^X;xOOd*0 zMaVb5Y(4ir`R%i)Q5%gI8)#X`;8*z>)-Y<85Y)* z4=z`hVMsJFR{?k~iQcu8X6sDHNsmddtOu_R*tNGKJ$sE#PB$+dMeJGR7iJ_6bPITw zowEmamY*zW4;!9{;9ky`2Y2&xDiq>WAtf}!1A`asVT>F^LKmX|{f!I`lFYr9P>5LB ze=xzn(dM6<&wn?JO&C8LA??)T(wOEO^4F8{f}*;R?SU8Xpyt}=9{I!HsG<_>^AkQL z$H~^le(I-!L~)QctB*Oo$U@JO-23aWxwb;u{RnbKW_2~emHdQix_sd8c!@i6YMhG1 z49|M}FkX!W11)6;>E-6)o>dUKEQIG;zutm;zSWSNSXP@a4+34U7BAU@w-!9SBj6z7 zr1Bd_8%yRi`y$i*L!P68IFb;>^&WpAsiFv5jM&{1aU!(Gb&5}C(x-DPgeb)&(b*EEA3n62e z2whpRNIpJ$Efr)isTjvXKXxNjQ2~2Tm|8Gf5i_(N$4aKVzMARO>*olmiQzQF^M31E zmyCu_$lw3)dKf1E=V$PLwBCOgi{DQ)zYp4gX~Wv?g*8!{c`V?|qhP6Jxjm(Ydd=^w zu)pC*q((~<<4iPTU=i~J+({Hr>!~qK7=wu=u)})vpNE( z@@Pr!BfZB`q9yZqMfJxwEbM$+d2Bt5Q<92xq4n@$?US~fe_rpCB7GqWS7`SLmI5EJ zJfY)BA-xXy1Mv$ZeHr1zlB}v9gBZ##EC7hg@da?ufRpf+qzVdlg;7P*A7pYDe~iI6 zH2Q(&opxi0^Fu40U>DzC2~?s;B8fcE4`O~`8@E&R`wTBm`dqBZ-mwjF#lz!Qrp@Fs zJnpqS0GFD5LNLcc%bbAvo|`pY&$?_VdwaO1s|7a-4v_@(Dy_6%dJ2(j7wr-2Fml~= zB~%9+j@Zu>VuJGcRR2S``St4TaFg*6Y5=n|gtS9@kSn zl~9Rf7R4H+(spqdZFRZ{WL5%Y>l^;LwJJp%9L=!_3})<$y^52XQ zF#u5qLWLO=QzC?$TKTH(*8X@>Pr;|NTLSgx!7GXbtIeNJOczJshJVfDfW!%+Vk>i^#%c_f8>Rb%~5cn?S*+K4bYaV5>G z!r8WWB_kb!b=>E&dVdvJO2)(@IzT3`?ckMcC~D)=S6lYod0VQvfHVYmhGjDQq&@nN zTdHkI3h_lM7{7W8TGsr?WqDwFf1X^lYpEjW{j(8@|0gP7B7nl!q`)y+=1nl1KPU&tsjQKnaW2IowO{TR6_>ujZxIMxpUwG!@Y_~B zEdQ1>@*EceQKCF3aHbG*wCcu;-}47I!ZD^eoH}Oe7$Wtj`6?rWdN}%_CzH)HfwrR< zBT(bSZ9Of4_<}TQdmo}*P6S+6*Gga>^Gb=BgM^+4nfD$cHCg98vwZXxn0#_`HMZV4 z*j!`)JY96nlh;PEc1@1svaMG(+6}4Pn#WvKz+ZvsZ2rvJlSL3LAFYVXddRlEW5d}x zJ9E|zev*GH-M{+38bh`YBfESOn{ICx$jr5gA-&rPQ(oA&nz27OzMPn19*qVk+uDy% zgNCA=x|b>*G;v853*iK=&b$p{jNK~_SrUI!Xe6WA$Q~{&PDe|Er1Ml@C!)S6S~Tp! zWOUTdsq93EWs%Z75a6YqQZsUf$qN8sETorOQZsNZ2KuUjr=M`m_F;=J##oyP-iB@s zW*WacQPeY8evrA{@fKFuhW*zLg?1*JgLzmE;O@@PU+#jtmVJFRtagv;L`a83gSC_V zA#~!%ek%su_tsi=HBHb1Td-p(JaFC=WxR|BFfHDJo^L@UTfXy(K?SxQks?njW>yM0 z^e11iI+EZ(jXzzIafdhfC5eDg6)Bu3+zSF{eD}1sD|WC{H&I*7n*srCT=6L8w>{0L zUXO-nHiGDZ-m~XIA7*i)^dp*jIzjsp8~yeRZB|zI;u;g)p3mMyCgH0gEg^sKssk7Y z+lB+Av>T#B^k8GIfB1X>&-i9vDg~4f8`Nv!2th<-%Y?nX7OvkQM%AYJKA;y%+rXCc z3uuRqWxW)%ld8Gob+^>{PPcb@ZCyJme1PwQSmAZVoG*d5MSiv%g!3UIfco`|aB)`v z2PvwylQ+6oDK+Y4>xC+`-Fc}>lOSm=&+@m2!)$tXlLKoP>48Q&zC>YQDfQrny;rrs zA#T`Bd^sW5w5c??J|Xk&pIrHVVu_;1idpXh+Te_tJ=Kx>h`T_H=9m|3FVF6GO=P-f znKDL-x1je0awiiyl%C5SPfywD ztWG&{w8{L%+RSP{$0bf49Kj)L^#!2>(@W#5u)FqlZAX+tDe6HRzE85b+Xk1*VB?7T zGduq9Rd%Gfi?AvCOmn+q8Jx#pY)&K_V}ZBZV5uOk*2qMQpr(e7HvZ0Y4&gpq-fhx# zM}&3D-@#4x1yK!Xy1}5)-(g>bza5ptzeLXXj4YjkOBxuN3nW%31lw7{g!BJhYS%dx z^gfO1a=lBdGUkGgRqC;2)dt7xacFn;yYX( zX4`5g(LRL)`vXG1k&J?H)t}T$@*=VNT^r^m@iMZ>HPI)ot#jMz5V_#Rb%=ZRV!qz& z*$fNWLSWgqP%-vkM!u{g?aMYw5>~^iGmf0#AfKKD+e+J_t_`nCkd{jj^}?Q;l|kq9 z`bs9JRxuMUnDjPqp);wvX+gdWV~7!6VMiQ*_FuLL=_qEVtncuq`{j3Hx7o!&J6*O7 zy=rg|4#wW@al=7cGcR)Hmx)#;Zy@o)wIjgI{Bt0)+ZSn~(F+p8e=z8cd#LtfRnU>~ zk;!C=%O538FNLO(n7G3nL|_HB^#_I#d#%pZ4I2upxK-cC$^6lts3srL3&|?J66rFr z2X?WC_Z4tBaYGbt0I|m|yDf?7`;%;WL;BldOC>3Ts2;kwL&Ky} zfM$xUCW`$J6>zPMni0vjqjnF3_3DrNYU&Nn=yK;9`KKZ>Uv_5(cMF?Y?dO-ery3UK z+qS-32k5bb-!`o#->D~C=gj`nZgs&R+8FDM68QO!DG1Fu|rP3^F%W24yexqYKtYLY$tlVXu z3;u#|Bj1g<@IF=a&3+j*lT!r!6fzB*^(C6fcYA@C^s}RcLr(pfP@rPX3CchKsN5zh(6fzP5#0CI9o~7(B{JT2DGssZ8 z?{%=l4+GH9qIFC~L)hYaRMqqIlc=J}%{fR|&`542Z5sHg6zmsogy~pyO6+lr^Zif> z6#Npyj{u-Kf~4fRU(qjtI9fR}8P5G~%~?=ApyUToaa&!&6CVuuGf+n2K3CeQ_|1}o2A6>_nfx%uR;Dak^jxUr-eA4hYF(S@i@pBex?j54% zC?Ij1!^=86vaSYlIeqY{mAol=@DrV}R$vi@$#-MWh~?14Isc>w=xQs;f&l731~ccf zSsDDjRP6dJkq0dY(4Sxnbmh8nO^A8q6>eEhN3QSmfFH@JoM5WGT_@{WGVWSwvovNB zgojP04mV4mssg3@P&r9)ZDneEz%bZ0iIa7gnhCzNe-h>%Q#NDtZnC}n3JJxXjF3~2 z4@^jIlg(HHbfa69cx$HfauCFdwcfh3g1%V+=*Kit@tYZWvBP#tW+MN-^M;j$Zy5Vb z#H5ILhP;Fa)hYF$k%%*#oH0P5zklEm&X(f#I6z&xp>%y9we5>U6?7E zEi}nrFAcTSLx76Y3gfxM50kTy2y`|o5bz-*`2yqSjrJ$BF6Z_8FTW+6VgEdVCD-`e6;C^!_Z6boOvBH|0vm3Klek?L)1X=X!hQHUdaa?ir=EuyOuQoGAxP=Wce&N2-HnJ7g zu}1eagJ-j_pNu0PCoHwqb6JtBZ91@Kn3@r%&8P4X{|g@%tMm9x8k6 zH$<|cI5Ev*_?hWvqnXA~L`@@XqYEbE3H|Hj6=hK*k}c2Cd&YeiDTM{Uh5E{f?fY3~Kq87O2NDh^c<+YW$;zEDsgqNCHl# zey7dhTwBzKW42f|x}}ACZuaRwN^xe3ijv(wbQ|JRSXZZ+L6$)in%vUjo=iV}iHu+f z9TQ*n_f)hz{g!=Uu|^J`e6$tVmH#K?!5-274Phi8?AosP#&+vzPBoGI!wN1#DqY*- znUq^Dy;C6t#lxAM6blJa0lu~dnx9*b8?7&};>}Nu^CfkRGWqDSoR(3{9Fb6!*PdAJ zfQ(GIse>_0iTUr*k=5~OIPcszh31y+8>Gk{E|{&2sfcxAL%%Ab0+;2sksfq;?!53jphjz5j8) zq|x$sYe><+=HhDIX392dfzwIvk9*2cIJyG8(j&-uCL`?&&!HS^wCS|*Lh-p9q1rGC zz@eLQalVL9hmUW#{4fe&@1`s zWvdUpzN`)+T4f77;~KM6bC&BpD}f6gVPFdIZ*hn{Q?#hM9j#6C2b>Hc5tI(T{ehsY z8>vhABMPHeOW4CPpnro4vU?yKG|V9oo@N>)=DPr6fqACyMe6;~r&MG!z5N@;CuDm# zXHD$^sjbR>LLoNE56ZS9XxDTcc$~-)OR(o~*^pgQhQqT!-xEPx78uP8IZOQP)AVs& z*YF*EHR%Abwa$U1-}_DMq2S{9J7Z?aPlWvjm#2L5WU zxI4;5Z7Azz8sgrClM;%5{()G5n+`6Z2SU3wE~e>v?REYPdV=G9eZaOC>wI*c!5O$L zF+Y9*r~@r}-I4AGC@A@3QD8^?Mw0h^j1UKpc&1-<{lrMfH{p>gj^a)c>ds`MY zz;40j2l|QZJ8*k=lZ1g^xEXpyPVT!?m>@%zQlsb9SFEy;zC8I4{G^94^ZHmqj^aw1 ze!l{rZUKIt0Du|l)Cs`z^|iju)XjclD%9UvZ@ah=i>xvC#-8&2p!aYIuG1U!Au1}Q z-I($GV0c-&0>o zM%J#pZP{QEo$1y)kIfEm`MNmK@*~`VPZNThNsqY0`S)iKJB#y{8SnL_S1972i`W}( z4@dG2^-Bz_3LgKLOm7%+_dq75`4_SY!3whfpxS=K$Y2jsFCQZcLA^gMNxCIWUG;$! zUiv#UF>?hZTHi@tAG5n*NsPXbkGz2=4ud%1g4&eYj1M#7La-wvD_9#QV<)t7-pY%R zkBBk8e%wcQc!Uk!>Qkn5PiEaMN5X=_57`sq6)3egV`;iJkH*8afq@MXyG-z5$dbaD z(*jI5_MTG?atDEJh*Ezhylt^P;)p-cQ0;V2x=~R#PxcaFEcw%)V)Qv!xH-8q7ZC>& zfOfyaBnNLQ@NJ0^!A0^q7Mb@(lGwp%(Y`{j$R6#)jk2F5m zo-_iFL1bM#s9~U2h*IW{(g4^qJ~;DOu<(GX3CWXi$L6I502jYrJ6OTD{i>HE)N%Tj z?-2f>{zKF&)qCVtyz0S};#&Ca!U~fxrlj&Tsc4Ct>LN4OtZnaC7E5iBk|&$d6XI+m z6Dr$40Q>LG4+0Cb6QUNtkDga2r*jEIBtoJOPs?vuUiIMS59|4oZF)@QzoMoP-*X_; zn9H^!|KkNPC;JY#GhK4bMN3-0#xX>2nv2u^QM+Kjan-Bd=t=t9dQ{NFnxV~_woZM8 zCI*YUKt`n*c-SwXexy1W1P2pO78e zY((|(I=4>H(nMMHK&gGr)o0|1=oypQJJSv1V8eQSj`ctxHr$$%5Aa^x^uM&t?CP1P zi(TL$dedNR)S7hVTqv0^*~yqxWksJyehZEYj!Nc9R?z-wNN;rLLa+1A<&wn-AD_4k zKkw{BUk?+ej@##CG0ENb`V3Q4YZ05yjv~d}QB+=pAvZ0EzUVB&2jv>8+{u~UbVeM z)^`2Z;#J`HuS>*^N1mYqj%TyRB>}e=wXN~}5R~8SxaO!7T%6FD_-^blS_|~O*rn{b z`E}q77JP>5))Z&Sy#`_v=OEx!GZd$$k%WZ+K%-E!x3=udD(Aw2&oiZz9NT$1Z(vI5 zh9_W#gpw^060qymS1=@Bv)dOIPtEWddnkIJm9cD;gTwC5>GFLe+%>6>Kzt^FHSW2ku*(e_|y0gZ19E8|;+0DpqEfR#rJ_F=l9oh`8`7t+O#R^O4c! z*S5>k7wTY+T#s8ZEyw{CU;z?d0-SYsH;~>)9Z|tp`!}p*`WMv^@ zj3B2HxImVe7HR?bL6*s_2v=Q`n02FM*LheR9L}v`xPHt<`L4x}i*NMWEO^M9Tw!~W z?``o3wmf807UU1#2YKkCNZK|rZjokhbT{o8P;sYvWJcQ{Rxei$sT6cIp&~-S?v+{R z$se^-$QfYmgf1kTPNLb)M9psd!USU_-GMyB68XAIk}&aKEvolxIW8CC7?ky<47;kj zLy?qnI4dEkfG8SQcp&!j?SLc+jgk~>OeR@aFYh0$_p15}9@KFPSLhaQC7u;p)ZE#Z4i{wLHzKiFco5xEc8J+0t zHCs3vk5&u;%OcaRQ+8&Wd4WM7-da~&UEsS&g8L&$Fgr^SdyUl6(Aw;$n;7Q1gc$x) z7w>J@zGr%kwAV9Yko}u(+e2kE>Q&=vY$i_b8WoQ~)2s5o-IoAA_=oe(%pH1PpVCl} zjun5BB7ArKmHhkh7|yjhXyLUo6Nzex?0PIhw89F_FGAdR5kxX2_Qm%T5dVa+w>pD- ze@WRTk6iX&BT`87G8^qA!sdSk+qIik_fCzv$F6eSgj3e&Wpa?TB^aSsO|Qw|pZ^NO z_mI@?2*s~gA8)LVApBE$n&egC6V+y`mJ>1OZzx~)tF32Wdv6`II}6+nMYs(rXuud1 zaL25qUkMyPQ8ikr`VpwmG7@kHR0@{syJWDu+CHv~;j-j@b;~@qNu8mq(PY^Fb2(hY zF!j)cH!E>Z`ByST=_mSxiB&i2TXH^I-b` z&5+`xey!k_#I(CYyk8z3+FNZRopSQyPUrn~Qa;(jkBbabLmJ>Ieb_2T+`YO!Gg?YY zhiT6E`JjOzw@OJM(1s1eDo1X8AAgiZAf}icj&Pt^rgII)|76BBkvc~as}!r2P?|t! z2U8lhYxL5C$*F4Ca|%P$-&RURMoJcwf~vCAa;?<%I6%c&Z31)8nO(;E0gbaf8RN-jJcum==4vXO(ZE!Kif@y%7LS`u*H8eW?M> zBN$2Wv?x=0LF3_X>Al`@O>D$(Yv1MbAPsaX1#ykarJ>*%dftyycuqT#cy<;7a_FEf zJJqmR@rLsrA*#Ic91J~{tsO~o#JCXckWF5GWOFIf1jq{E5iLf8?j75zWZb%ViGpr5 ze8H4^l(CmL^s$jsYD_vK!q5)b8ePx@_ld^U9=?zA2ZN0x->TXS+T3S)&J0&s>Nbbhqg1rV-w)*{MTh+cO32Fe+VR|=R6^w=MeN~B z=+Dkj+*!VGcy_7t#&GX}O94n3G&4L<7~eDftN}jx{ZEcE9^HdVAqKmY`n?@S>QC>U zV;xpthHn}-%4qs$T`o0yb+J^{cKz%)GnGmsq{ULy?hbN_TJBh-o$Ar;DA1#q-}B~9F|yT=Vn(jN{2aAn*#77izcNKS?3@5xbqYdSI9 zb(RH~1k$;XIDiOxq8Z)`ebw67dRnUO>7I(Sp9KlMqMgWf-2vTw01jUx0NtZ;JILpa zF+gC!a5*Zctr6r;m6({T^s-yCXY6SQ8l`HvfaS9GMH$8tJDN+WjNFh9=4b<%lDv1LCvifz0-mDSi7i1d}{4BAc;~qy9en_j?AH z^VOM}*yD03MbEDYZBI|)2`hKW%qPsBcFab)qJxw`nTVk4OLjJYcioU>^pNU%M8W`s zv8l%(gWE(k(($9{7HN1qymBW#?le*+v;YY?e|c+WEN?G@c~7Q`it(Q399(=!2NjPz zpG{>^R&qJW2sq)Ioi&5K1h2SK9%-pXxu-je$$lq%9Zzi>T^kX^nFOKp2ec0Y#IVAOillM&OX$@OIjgd zW{~5{n?z*)R3PM!G_7AsCOMInT`6oE(Kg~>mN*4S#6h^T;#KU4QCS{j6fH0n9WGeG zeHPTXZ5V^s(yQEk0zdFrgGGCLxK&bMqH}&3eX^#}3u+!nc%pH&f`3I4 zBp1znQMED-s)`k3x+exvZ33ZdD+nC?Ky|eg;^y~YXwz;}kbyf5AqS)>%lg8)8oT(B zr8dbSmd`Ia9maGn#?`?;PUgRAnO#1F_y~RhUwgmp;7-Wk3cT(CUrw045<6GLhb`i_L0VQGqAi zK5uOyGBVf&c=O##(flMROVu)oZ$C4flx|!V)N;F;a%vC;A5Qlk7n*z8xF>1&y z|AWDrKEO9DQhjC2mkHnz`+7Qjk(QCphzG&sIrXJ3dD4Ioq6|-}%vtTl zm6_0#JSWQ)h%JXb_Y0E_p%;dNn!RSXA@@E9_hsSxzzPHuQ{Z?XVeKE1aQ5+bI5B8U zmD)(FtGQ_JV^Hgj?pfKP$DH$1G{{`ZdHW1Ltb*)W@5CRxNYk3?qi+i1a@n+ zWV_8yZ5xTMu{ddoMFtc7G-PPxqyA>UO2>(Ad!t(R+=gL@ZLb5vm8RIs*{|Qe$bU*P z4R@R!V?=|#`~*SOr-#5h*9RXvSh@L-AM?&7i}!~tE5Y=TG;BX!!$#*wr;`pml>@wU9kflumNYssm>7<@Q48Ym@Flf@h2 z@&$f(Tp3#H)r#?|K$?CVm=&K8c2Kq}1^pm1>4F_=2Q(OxvnkKnuSa;k`7;5YQA#|j z5W0)kIn`$83S%6%afjsp@i;Db$uE6=+du7oV7Q6?7pI^D8C-SY_t?;opqLT7KQ%i3 zZlw!=gEQGJ8@V@-hLzc(2U9W%uD@8?n<^aJW#N&@8o)4L|C3*WX!gY)CaO2GboQ#7 zmKd1IZ9{^Ab(<5`bY8)uP>Dq4){B4%fDbR6!?kRnt@q^&LY4j)tq38y5Ff z$lyvcpJH>EJaNhOnxKO4ciC;aW~szQj#0L|4|Y`6u&_oVT<|Yl_`^R_)E+H$ALanW zsyvD4oS9QW(azCTYl?4fL7;TbJIzRgLU%O6Kq_esqkCR_b0fY{0_p4Db5#lAOVs&P zA=GkSVk{{}{WvI5KSm7PZL~t!S{<0m^$1>ZdCAiI2vdjFDD2y<@3<3E+ovB&cxX{c$U)lO%S*Rp$AvKe&ys#^ps#p!~>pfp%Oq`twPa9721+deX0#5xTm1c&z4A zG?cRg5|bEg&DbSBw1TqqrE)}Ypl1#h)YsNkE}pwUgu8d;zJ^P%!_hLs(85W`yCK8X z@%#>rRbYq4ds94H;G2V{wJf7zZvUf}9fZV`nd7J+>h57Cbw z30C59%>kJGfXleY>#cYjT6BTLvy4cF`)*}SU}%K4Zj}%7N{(iKLmu5l)gmDLR@^hg z9fD!!VHfqi$B@AAeDEa!$JF^7*HZU$Z3n89q^#T?_ubIwL`ZWO`|K>f_yjSr_bXh% zEvRz(=u~fdG98{083*oTKV|9uj+7U;u3mz4=}j4;=^@d~04KG7yo1h}po6Hg#(9e{ zTR%Y_1S%WM*-b36bE_z!H!R;rsDQ;xo|C8bsW6a;pT`+T;jLz zA`DY*cYr5i#AIV36XF$S1)nw_xO>tel(X#L)JFpO(kiyg>d`-B)s-AbLQp)`w_Ia^ zAcf*w)}mKRClI17K8oLKgo;WisX;{row>nHfzScnh&Vrmd*YW`6D;>4)Bn8VZkR$q z;d#d%8VLsvht*n+yU&IDm{2casgCXVnAl4qZTgisZUV(OCrP&bixpjC`2<%e=1as> z?y*>yBI@=TFDa^ZC49IjFRK~ykHoF9ZpW%yB)`l)8COeTiyDd-CR)iT3Pc&-OwsV! z=25)A!A#oBUf(l-8x$Q!kb8QjMzV7(1YA!eNKbA59yQ#vF;pNjZUw z3c^h7VW*Sl3uR-{lLeqU=F&fh`T(?MD+)^?d?T3M4n2_5isZfDj+H>=?rELnw=&jM z$+Q*AAwfucm~*zaHmRn-z(LZ`H?#5|)mVu|=EocIa(&ioJ}U3Jp3QeSZ97!k2e<)} z*~}he`&-{|u?ez*splO|m$N26^G}4Eice0?7i{ax+S7hYwD=f<_izzu+ke_Y5gTrB zuoGIkR`xHL5a)3eTm@5wR6m*!M#T#1wpGFs`FTxMN`N!EO>YylGb9w#Pne23lI=@! z?2LN7ke+9ek?R<%My0UB%u<1jK@Io%$U<@_$zp_98GXzy&{T3s`kofBo}bi#zx&iy z8d3|S$}tpjs#X+(;Z43__CIUk_5rK`OuR8OJgINDH0&3gVeTa?OGk-nN`}pwm!)>Q z_2g0{mRgk_^3VrW13Vw1%CfA(BcICUCDL$t;%+)y263ptD~xmb%ButRiRqpF-GIx! zA7(!+=4uMf4a^DAz`>=oT?Aeh%_TA(3|J4pGza}LASaFErbePqT&qTX2@V(be7zzw zX8+Wah0@^gG2za8$g@0r7^VgYL8vTsSYci>AR(M`;fT!c;s(xgM{jXLKyVl;8Rg8n zu}mFc9OL0IV;i<5XSl99L)vGJwk(4w(7;Ates06Z$`GP@67X$KLlIx9!@$BrlFG#3 zAlT!`AW%lOgqYVYjn5R|2hGfY;7+O|&;oC$NGgk$-T;-*pIKcLYo4RyI2$~Y3J-Sc zNqu~*)bwz}??gido?bMwX=05}O{zyg==Jvu*ld~!{y^ytN@VFh-kLlRz0n)MfIQwq zeCOzQbhXK1F*|%z{kMK<`ij0PWByNZPqVYf z)VDAHt`G95H(m(=uD$F37;=ARDBcJPqNuQW{!)pTImKL;19zW-s~~gBL5<>lXM_dW zTB>~eQ~g;wg&vvg)r%~@>S9!F9Zp!r4h<>qOs+>Zth40_24^R5y^y6}LS0RGVOV-T z(vkh$stWy@5u3Ij745IrdO`GR-}c4uX&#Dm;{RlMU_P|G3k zga9+4l9Rhb!4j1v=G6{?ez+DQVTq zEXu&Nu&gZN+6C?C1y^9$d1BF$T>B1HAtIsV=1s&)qqNM*Qk2nVC;_)4LYmd=8N0p! z?MssR(;-U#Z*W0EAQWX}D=q|DZ#RodcpGW+m-9`C0sRc&@CNH4s2N>?-Pfd=T&&Er zp|QEzlR)KSY5c%jWn^WRm$CsXi}*38%?=)VI`$gnB}ud-1>JQ0P4g0Fv zCjry8_=;$$y%vW}wRPshy0386J?T9c;!Y*8YFa$F=C>vj>)BdbaQ(darUTDc3;FFc z%$KV@h(^vq@)tqQU=@n)104bE-Q>3tfUjSCQ@*D=Vr>U4@>T|iJQ)i+s*pFuP0D^; zRuU-wPfgX?5h3vTDh>7gBu{ekD|y-VG}#OexX(FHXI1KQ#*Q?Xp5Xf1Y0E`TR|jua zy;l%uPaXaGFgGk488zJ?Z>}gP#Urcz3K_Vls?flKfSM!3v{)5&GYrA$3FN3LCF-iQ zZHJaBds|%)?NoerI2oy|0-OjfX`nyORmT@O0Lg+VR-DyzgTLr_h*wnkSbQ+u0(>Vq z#%dVKZSv8;hUJ&J#kvMqzzm6s70hFf0py8^K{|PXZ_^q0)s_#afp-L9=Ck6Yfxtzs z*2Y;cm&cgpgMK#+^?FODGA5t#{6L0y}bG*d&6f z{HdCiv1)%nUF>Wu&UuY_ndNb@nD*umpz1+hO-CwL(pcU9g9-m{!vA9m2}#&!mXyTM z>_1UOS9HW@@>mXQLrGv%h)_b+$~w98SxPf z-?LZf&#P=5_X84LpA+&u6&@zz>+vQRVcW+N9&VsmL*dA3h{BqSvMNt4T;~aakG70Y zWFGTNb`%m7H~iZibKj5-63C46qw@$|L7`+co=sxZqQroO*)d4N4X_YW!j&dv-r)&} zcv3F8mvJG1=DcGeJ)yrJVr!(fz$MzR=Vz`&rSzP1ZEZ~#5uX!@Kbyg;A26&9o>tDo zjqx`$kG!ZMu&+Rb6HX&dHm0S*1=VQ0zU0NSwm-1MF$HM!Zn@-9NaQsz(U-A^=$9p@ zjlPpSF|dyAbA|k|hQC?TZ<_0iPiSbPWv9bvmU6#kH<#U!(~*Xj^XiQKTVTiKLfA?qfjGscCw0nMzqi$Q8q4vbGs^YLcJIO}ewR6{ zLo`dmK~0npwNem;OD<7k;(b&^*`#*FH2NR?`?AvHJIwqwWaLV{fFQaK!GYAEM-~#? z=nd9FmG7!f{(akPIX_ol*#Ve=|e2Qk>=b`x$8GSm;a zL5cNd=BVB3GuFZ=6PEr{HC@U|;w%<8{lEzVlM=Fvj64i$H}#bahR zv5vRBVPVDyk|bQbK=DD&M;>?UIGIH2M31`-CE?4pJ(P)s^_M;9403SU^|*x z6CJfZ*FmO^lZDxXu(r zAbSWV%a!^jf0rrwGru^0CH1?%!dARaUq`XM{BAqiW8_jC>sdA7GwDsU4P97iY!Vhy z6A&!KZ_-`SuFD=*{1*H z_NcS9)}HKGJqxCu=@B!rQf_8EorYhT5;gQCL%3x53=2RrwcTwL8u|*57GI{0J=~{k zq$G+G!*CUQXXbr-E# z=7g0-)B2|HeJm?aXq|*yHEx7Bx#-sx_+8hyso(#FS#klwY!p!o^}*l77S7>WQmu$$ zr8ZZi>2*FqcC2Z9jo`-gdGD=cqGlEyo^rbTeUjMmbDx`4%Tf|UyR3Jn{;uF!8hk|L z)rG-Q>hEgCwZP@!ved|2!ol`9$7avs%LECnHO6`K4Rjw&vxVtOP-muWOmseEpE`D5 zveqtctlR-LS@X{J@RdtuWz2B;-j5T7pJC#EX2+~|ZEC2QEDQ#s4asSO9=+1QX+BWv zi#2)Ih7A@o`^*Y6FpS~$k@zM{V`&IJejlS*tu-SjBFf@$Idi)YL_8}UI%By~NhV<_ zIHArLF;|=NxHO*luJ5R1)1EW(>%J3OQw$36U1{HLI4-45`&TuE0WX;sm?8~>iC9&? zvLJ8GALU~g z2ebOs{%FWRn=h9tvb?VV=aZg|{!soEElGO7r18j~`1W%$%x?g<&bOrtJ6Zz?8=~39 zW)LN=;d4y3LWw;0)m`IH!3QP}BA-gMJ0rYuX=k+Y4TejDa@3g69j8o2 z%t{SqJ3v2m`*&;`cHf{>MJevOLi_Z;h5AxuLprKI?dY?&G)MX2ue&jMnaa;d&xpIb zKh|!pS>-3cFhf`^#`)-P{{7kq$^@B1w{*Hvv619up#BCXEHVqNh!|J!79SV?8GRYA+w)yAhbx+YvfKA(7Bcz- z#T#w{pI2Mz?-NGrXK1(jg@vizV7WBa;7DnPm8F(XYh5S7JW3t$AKIb5v*L>Vf%QN! zNm(s)KcXJ->l6~og3liWMge+t7;IKN7&5o`fF7%iJA4-gIeYu}bT~-)5}U`sR~fz% zW*WwjVECVE)GrGj{3E6DN^0ch9<0Y@$Co<(S`q*n3PKi9 z`kp=oA-6Q#ACgmb$|TV#2>?g*@JWJRI4?ppB_&kMokbU%?W&T3Uf1pu+qk+hiPNG| z`-}2LQqr}o$xUqTgCO)|ga|UR<5E)>(bdeBW&b1E_xh=BastE;DfGCm;4P*{=&y(5 z>u*-=;ni+ewLmvn($}7s?W<1`Nq`4rt&QB_ ztaNvIID!Z$DKju4Ew8`6_xzgn)xK{@T=+xaFB__zeg${LH{$l_f)iJA5-$Xs&J!Di zzo^4qpIfW2W|CSfirN~-lBqSiI*1ttr>~1~feozWvKc5)B`wZ4N-`L`>I=e^a|5S^ zd9qFkqvG#^*TUK_;|br#I2EM*XL2C5nx-%`PGk{I&Guk|JNObbJmUml!W`fH9j-f04>wK}A>!`4Nk-pA({n;KWleOyzoSlUeaSr=we#rkXV%5Nkz zpqWtljp#(Go6F$=1OyQApiIYtm%~^%ZZK8!pQ~?SD}O!voL_C;k8Q}^Y#r;d7Q}7o z*CKo>=|%n*dv6(BceCROn;BzfN=%74W@e_CnVBhOW@ct)W@ct)IJV7Ci5zmjMrIe(e{xM9y=R|#E^SGXDgZ?0=9?4?QHj&E#?&((t>?&yzAz?VES zx?llQAAAzjwZ|2)MX0VY*R9<761P}uz2iX4M9e>-bo-SvurlFH1I6@=l_A=Z8FeLh z7B82XUM?7|Ib{VE#-w(J5QhnS{$)Ir2u-{7l+`A_9T%$s_4!#}NL0b@Mm}lPJ40y8iAJwJ+w$kBkH;L43v_X0tO1!&1u! z7jN#9cZqDNb_j(|NCme?;RUAW)r)#3ImKl3q`0u}zAI)lqJpU3dMqmm4LLkc%EoIA z&ICH@_vtfx^itfHGqE{|k<=q?6*Di+P}W!Vtck~ zynf^_p!c1oAoBIy$w@fK)zTIMj9=}X?8L4~k3;`90om$~l0_ltLG#>B(c!c})N#VY znI5lnRWTAVn?@MNh+<%PYp5*<5N$0k)c(%^e^!=*fV5Ehp!P)J{W%Bc;!jg2CfR!`@br|cq zQm6j%{x6LAar=e(7b#TVgPKRa=o3fPrf~TcT&9-j+MJKh390 zRko-^+X^L0Uv?QO)A5C_khvq>%iYd`LU9uH4LeMSSYwG1_$YN0;y#>KTg#wgJe{A9Txa!TU)=$gpN_v-bLQ_ER(D?HoQY_BC{PE5$VG%pU%)Ya!;#)YprF zAhq{%^p{itID?QK&5i3~-+GF=@ITuVpWQQBsy@NqTh388CI#M_dMXi)rQ)2k)gyg) zU9mw)bweX>x)hRyh2)6_H8|lOKQ}CH%?g=~FrQ?r*hsc z_~s<)0>U^$6b{cdFN3A7Zc_v zu!2iL7>Y5zshod_Krgvl?VqyhWe>4Gu|shI<_h+AsQ%Fm1%=rcAs4mc$*}KeS3E65F|124SoiT0$fa9ZQxiiE%s_MKi1p7| z*IYl5$q_dsCBkmI>47d!{YsQ&wfgt@-u-|2fAtc(j)Ah)Z&Q94H_mM@u330s zOzpjTEK#5}?l$JX6iwwMoQp^|#2^6$MxHPqnZYn?D4`Q}s6t#VZG#{)w zn(eU4D2-f!R?YeX5&?06b1c>?$w-9A)H=6Babvj3StDGYFs{Od2{RU?!*hLI6$a#v zP>je2F^)^Ry6Qhf%`MPC!K546oYLNaI3Q%zjpxTBUj_tZ$@V|%A^Vi8{o)e0%4eFf_u3!$d+nNq&`?Qsdb4P;FW%2{zyDQ_)O3bi6Q z?6>sDuLFWf!N^qz_;25Zmtr#qY?v~ei`sA(&>JZmmxUtbkiV!Nl;L;0ymPv|^(RtN zmix7a&H?+h!z|U}m~h;i=7XAu)67AwKp`5u^Dynq1euRrH%i2K#~7O)F22FMdf&zi zEyZ|CwMRD0BT;?E5<5L&mnhJEMQjoh2!%kw&7~TQ1fsf9d-3lYl~(L2@$SB!JGBuu zxNZ~;hP|yP0Sc3|`}3-!e}ToWdhYTJcVX@~t}cX7L#fP;^m3*Ih@Y&&znB^k94UNV z?)b?All$8Fz5kx~**E!ch}vdtWXaV~MqFt}g0i(bQTO?B$gm>NL>*NVoHz^owrA+nFTk zT}N9Ju$tzx)Yhk`FtHK1D&|La;DD&~r23j%T(jE)`%@HgFdlg;0F>d!^|+nUA1?U> zZDLf5FYVa4~O3O|{{#gVW%SL~F4JIJz zqS0Un0;w9=qG+nk*!vMaC_@n#TePFx!9zrf=$XpgI z)dAG~jq%!uNI6_r7nPzBN*Mkw8^Vk(z1Zy0o`=x~b|6H}wJ?x^g!jMFkUpV0D)rq& zPe1noe@Dna9Z|KYD6!M1sN$xzio`E`prM8nLip%XtI>M5w^ zfBAb)bwa0~ETp`ZQ!rQv{UDIwfis)ikPE8jbqBvV-3o@fpqIksh6p-PH_R?mAWRwT zf~ZA{jS*-i>t4d8p>dQ-%-MG&>A34h8CCb%Y{c;=MH#ScJKg0-z*x-r#(nN?lwH;+ z5)p4{O!h@5P{K1g5DV?WLy~LIcYiG#+YGV!jD#;CIBtmSDem) z9!N&{gM>hRSSEwr9upIAxd-V{Yap$8!Xk-?RPljc{X;uUBAw75I-38Y2XjAj%6jts z%id5_PmSsrRoqu{2aI(Skms*7eYjt~XM5p7ZdN+P5@LnTiz);5qICvg>k$oJHlwm) zd#(D#AuvTMU+{B{y_A%g43O<0Y36%)cDLc{_*r@ah`r~^FLaxRR>lseA+(cJP> zrYZ3!wgTyNJ}B}CJ<b+1XXPdCY&8N(0dP(qa6jZl>dy4umG&xsv= z0Y~1ti6EIx$XM9B9i_W`hURH;x@SHd{6YGYIdA9E%cJd5>D0nhL~Pfw0K#HNx^9J& z5)@o;;ql;6ovWMAfBir^*l|rx!xQJ;S~DQJ=}+D&6ZR1|G}DS;We!cowREJ?Y4VR1 zb}u(_NI@DV+V-w5u0*i1htSgFgc%2_7Nn@B4Y7R^Wicxsa0thpSdvZWIcIPIp0uF} z1!4<{*@Hz&I(koe(lyWl`M?!Dn9HKU!aN~UMRfcm`wH)uXc$MXfKfCNk_p-HvAp?} zT0KY}tvYzZce55dv1t}OYQ~;2xG1KPeBae1KnNUKU5Zv@dzH70^V86}Xk^GG#L7Kk zUuwUJUN(nZEq7KzGV(DyW_AaC5qC-{MuKsiT5*$SV6l#7NL|9oRbRI?acrLaX}Z0v zVUw$PuejxU9VoRG$I!H)l_bC8Z@2z^rQMo5%9EdI$9SmL2>$#)eE-ctWUrBnTlgpu zVvEs(%yNEVGXixtPOAbKTFVOBzGK#`KH#dF>4K2{@xu75qOxDdTG$dXIW!Pr3nnZ= z5Pebh7Bn}-_QOflemY+D2B2^I2zWk<-zol`L;OOC`NJjtS3R@XArp-MeEu4YDaju# zrMU1@uyZj*MNBVrF^|l=NbM?1kxn$z+5k2#7SYU%N?c%=*MI5Qj8sbRYvSJLXnpgA? z_R#(PzAV!e5^NglWE_bzU!?brw%p6N zJ2!MX-lsqzH#%a2Xfyyt5%`_a_8d5;l_X3AA@acImH?e(Vksyxs+G`Su*ssrO~*>C z=}GQ3*$JFj1lBy2OH*3vHYJ;o$ylpJcoS;vb$!c}>oH=o9@4ro?(KR18Ss#kaGGDU zz&l=1bLYbG03q<^_}RIvXd#6aN-8bT{erN{kfY_dp>@S@ZRNVEiA>2t*umt;$n) zV^jT1WHbC`EfJI3^!0r~B&8El=J5lLy#43(Kk$$C+)iqlIHA>CO=fiU-UN;7!+u=n zq-eLis4VX`$}a6a4h_0IAJPW=(vCZdEuo-RyrcFKXC&^5$F8kmc_RfBd6umW4)yl62|9uqT7!k zOHYYNfT}71l1@DAd2mltG8Mw@FoQzpKGT`zRWa^w9yXuVjK30m1m#0__12rzQPP;n z`I%2Krf-}R`5m#o!LddeoJlaqiB;Uw$W|K47L~GnSMMH;qp@qeSZAW{12&atA6#Iua`#AUwnt9zIjTt2QI^CMHL! zQ!7@)Z1l-bb5$soJNk!oK_o`P*mI$~A9nHO#RA&oSn45q<)I)lXz%()TI=blY0_Sf zUmqoW6a`_U_LFjb$B+P?!M(BbV zs_n+S=IqNg>Jl}svv_PHxCizEAmp2`^anCxzz&(nm<5KGllzm)`#NdWr%^n% zx*Ll%&ZPK~QX!H>xW2rgog_{hL4D%k_&TDp>X@9V^=`Dquk%5^`zMev zAy?=^c&nUXs_Cte!;&_^{1@xiWP)GpfSwNyUJuS|HI**+j^yJYGa@WYyO#1eR)&;S zsm^p&-4)JwQ_A(JV@NNt<8er^qqAaBc@u4h(R4l9UVoKj;w1pfbj9;-tB(~;`YXZu zGs{9$uqS*~2zFuw4q452BCLMJxdQ~?Zve=5TI_Ym3Jd8%%+b`>7vS2>Q^N`_Sa4=R zwka5*$g2s(5#={;j({|gE*Pm{yS>#*k}Q^H6Ls{!UT*NTC@b$9?L3(+VbmX5=ncbH zT~FS$Z|?q&FK@pKM5%Z;V6c72;5wAZo&0z58V&g zX0Ge6`YPT*)hwri1eubJ`;uH@LJ!#VVD&a#i2d_lD7VQo-uyi7eTgjYz7?xtm<={% zw$i}>4SZrY(B3G_N0|X5v*2th%YHfU%OuQF(tXA5ZG{j)U0!;J+@>H)_-g*l>(RM7 z=oYwfuWa24JTuR1C*5J%au$do=M0$0??d1= zUYC${aMW`8);S8iFlOMvd$e&3RPtXH*S<=mTFJX2jo$-%)>#p#sK8NjP9o4Ro)KsZ zZ2}^tq=*5}z(s#Z>=7QHh;g zq`@68@RZf%*&rPWPkV-L;k}dKUKLH)w=r+PWvD`h5<6(u1i~*neDkMG%B6YusAKjLa&o?o(*LTy$L{$uQNI*LB3M zyVP~*;1+EuY7)Gopwoh*5$CoE`nmk3OiHgSubiGoF+8 zMzXSW*y0(5@1lfX5|EsjLr>sc2SE%Y^16^mJ&+OE0lOm6m# z>zJBQMvj{?Kfkz6KlfoE2eYBk3?FJUPcen&rfTN7t)ptWi&~!{3v%*OBU1PYeU{%? zi=E!JG3gaPv;y!%r?PrD$+?reF2n%6`cd?NEL#2US$j1gQnBs|=1CefFTxS(MpU*F zK!IEs`3k@FcIl8YLM0^JIxiwJSgha2d}BKq;0tqW;Y~=-s;s|UmRX|^#mP>E2;k@V zcBAG;`*;5BXMCU{nFeC@zdGFe2<5zx^icWa!%Akop=q0BHWH%Q^b=iViysxs)Zwse z$&n-6nrl`V^fC{kAdN;z^M&{{>$4@up+9zLJ&j^E5&Zld-E$ex04#kJyYfR%u;#*Nyb07Z22{ zLg~|Zfn0B?@||*Cyf3ZqJ}aRb@8>=5#DhK|vmMsS8bBmNOGF57!f0Rj>yOIUNZKRv z_Qq`V61;pVeuqvj6n^u*-vXyEqxSspW{hV%;w}gdhzSS?m?@CBfcaGr9_C}^WYvDs z*51}uby8)a@`Dlv?3bdPVi~2d+XZt0MZhf!)2Jci2XF&Lj97Pd`yo2!!89e6v)OCQ zeTw^Lsr;AR8!)WvCfsmFcX^{K&7PLlIFpLH+p@yu?R{5pRHz=(ag(X zVkPFJdBy?AFK_P(bJcKys!wz7UVaf=joHwN=Y=t&M^|fv6>q76T^wq=9Nn3q99NO1 zRW&Xap4m(}fA(P;8z}Ei54C#IPPt9?)-|tQNDwPOfrw<_DSreaB^*C zR+qIXG0LlfkA;QH_dhV>GjVfqsZl5!VVm}(JMBmfenu+J+j~>6q(1tYu;&w@Fsdld za3R=42p_#5wcW0i6y2N$vvnT9-G+2nM5u8-<*hUi9b>Wa6z?Zt`)rSRa5;G!DsoYl zFaAe_NJ3IluZVwI664Ce);Rf$ZA7D5xvl*J$Lo78Zx5~OS8b1b*~5i6r?&zvf4)uZ z)`;o))olXdzL=xO@i(%=Q@dUtk4l=>Qrr#P+l+9FkBzHO{~>5PR<|E3mX*KBuHt9H zozN27pH5pl+3O-RutrLniJtGslLJwtL{d*DH!U|yDd93fSWm2-wM`9r+od|viy@Up zR@=M3{;c#SJpR;lvx`_bhGazv8yrZR&`1Vkb$f=yO!;3<=zRFGc_)+C<1AcgXk&nW zzpsA5|8P4(OnGbHJM!iVJ-6#s$&sI?LSrgXR4!kuRly^(Vyidd)^iTr-LWBESdb^ZO2~AfH)8#*F6zw&T@JU#Jyl ztg;-xktG^MNdk~Jm#^F7yVV;xbLepk*1dD3N~0h(qm|OrqxB%B;>wG`!U*c6gR|9; zh$Q47k_;3PLN+~NFBJUJs1%Q64;Pe6K+A9>Y~V` zwJ}~nV=N?NJ;d@1t#Z4X7EA&tWWnqJ;I$ zW-69%=|vFEmG5&q?cY&uIHCQ&zk+MXO})ch-jyk=v8u{b;ZGU`ve}#Rj@?yZa~Lt^ z`FG$}%;ImC)sFcP8eH_ycxACQIZ@1oM@UD<4mjK~RG)KlBMxnOdBI%X2=n=TC`BfP zIlgt_9=fHY`@}+JOma5$kB&ow4@L0PMKm#p5-Sc^?oWZIg<5YvZVR#4WOs{lgV2hwwx6LC$kx|ze1PhtS*x@p;x*8dA z_@{{WU6RT)4MBc^*3Nn!WVRE}<=s_FpK0k-zoNDe__haZelIfGlZRC$BaYY$ESQtQ ze&ya!K3Xf`W}Sa*a#49yETVIf;cw>Jw~DK1BrxVgHPw$ZLbetPK-ZBcV4@B}W!$@1pb3p`CwiTU=FhlUM4dWGh+UO zd;h`Jmmg@LMl+XuhMJW4n;WR}EQIe;llSYhMD*%?nn4-%Inu)#Gd6szk8W%XT2>Cq z(u-93ou-zz=73<#UhR<3X#ejg-`?$87pV#r%f{Qfzhg*sABZ$8Dw5D~5}jAZ+pgzq z!605H{tQ(tKF0B!VI;@rC{hZKGCKZqNh~s?rRDl!v7%#zC;g;K2ZSFR89JdBPD1^L zp%fx^9xM;O&ZJbyAEBF&o*xgbZXe-juVP>brhvuq5!NNLw(nJ1&Cr*c{jscUW&Bkw z5cZ1OdN}9Y?y_Lwo#^!5o4fL)gPR@AkJaMN4eF7Eeg%yI)=r(KCdj}rpC|Vjs`-iP zce2fV0z&1YF?td%D)thbClr#Xl#9>(#PfEcXJ+ktHj#E@ERt(8%3xp1n)pSHQU)IO-u_41GFMWX*U)g;wo9^cgXPmj%RErBfSe zQhT2NWCr4Y>a~Ax#{cq14$hYrH`)wi7{R{=`A<#wzo+>8$M-~#4VazH^DG+BH|Z2l6Ge{9UZTwe>% z=gM3?TgK)8eHv4~&kaD-9ntvQ^?y0|Ut&oNQHA`uGHs%Bd&PgB2KRFVjKHz4mH+*1 z{-2}yzxZf+x{8Vpp7GV9sQ)$BwD7|o-np%;u5RX-&=vpvI%yW~>8h){Zgw@cSN_)m z?z>G1s-mK@vcCQyGA$z``A`1oi4^aNLQQVMyIJ^C@qgg<Xju; zsx7oR^G7>7hcKC$GV2F*i;dAu>edPx;NgQ|>ehzCP6UfAZq^P@P#fOTkr>L>2F3IJ z+SNdYDpg5N*(}PB>UABoVz;L(o2lu2H3fs&6sjhU&d#BUEp6|~lg0fCh?3DZnXNm# zl8cHB3SmCb_1@ks0CioXU1rgPd^eqIr_c_mL*S{o^og^|{kO&7iX~j>DO|gI5@_gP z@)tQPniu4klDNpNE$x&1Hgb*-4x+`-#&;QZoz?NG-*J=9uJQJ^1^8@8EQ7KP%GWiD z^}MLf>eE^F^WV2rMglxne<>^%KW27<8#xbh7*-n=jYf7pP!C&meR(eZPQeN1_qZ@q z%pFcky3|75aXfC(0AHtYy|h2xrL3R;&KcF|HYPX?T>B%vZqhm7((~QO`w4UMl}T;U zo;K$W?~a(1sk4(T+p3Km2p*>VwlN@}wqTWaKrTV<*7$@W9a*ewQTdy0#UaB|!g{0p zzO&8bH0`ur<+RC_E@xw*ez@-`mxYMLog0XJ|6IO(W6o1*s0ZkfokVoXxM8=C=+HD? zOBt?JoJ6KVI8SyE?9%Sa`r?CI+(aKjtu$O=R^?=n8JB2&FVUppDno@+ zVkeJSe9T_BWRZtg7FE9aU9-F@`mJL=vGMQMjIXiK=No5IY{kCxFJ+%bnnSf;YDlk? zkiS;s-@8aq6*Qy(QHWhp%92aWi8%a=(X<01Bcl^F85^0GPK8Yl;MiIxXl2k^GdaqH zQ&Ltau(gV275@cr0+1SnZf>;|NXyG#3#o{3L{CcFIXw>#xg>63<78IeQJ9q%98@)O z{Z_%@?&98d-J(Jrp58Wzeo1PlA(15R~W@Y=W{&Iv8;LLC0-gqN~49ZCH{>$hgt|6q&m;hwzEzus(dUX#VH*|sWPWZ=EV_d*}*4$vRizzeD%Qo#nPz}wP_uN zEsX_FLxIl&ai*gqR4Mf+eESMoNL<~R(^2uuAy3{Q8#!1o> zYh&lYeCD}$@mFMA$BzOdEc=REA=7b~$CkyrLPwL>%Ag2Rrm z{D5|@il1+kfAVaSToJq6G}6(peZ7!9x9Qum{BD_Rk}G{zZLqS!`T1shSi3e9JEaT?&U z1it_z%p#t)7tLjRah7N{QmyST3E}j_pph>>(r@6p9<7PWO7^P@NgCHzps(7+{YxLO z%Q&$LcZzqthQpGt*5>+cLvVwTv$7FvsN>QV%5#02E52fd^QHt=vk(Qu28rQCHdCLE zk51xg4f6&Bi#Q7ARC6Y4nPMe)1M8=BuQncP+<;UKrZ~aF*}coZJ{c1#;l8_kso@X0zFeE_!EVe8LDOHduScQ5sONtcMq1RoRb3&? zrT(zB+7o8!bRY%(87e2Wv%48MEZ*)Lh$69d$lG`624=2?2iCv)+8`(N4;`Y1p|Vh) zYi)4ZJ{Wip#%O1*UX;jH=kC^0LfUY@p*J1S!(qAIV>nBtJz)<;9wN}Gh*sJx{Q}iA znCRv7eunnq(uvaED>8U4@v<%U_h~4Cq1;i<;OxY^+4e%UKu*3QLwwMbn&i-=3U<$S zfdY4T^x^!YD+^f_rpl7EueVo+e6@8wNy$1tCaz{arsYl-(Rm>sL9_r%e62v09>Fi`4>mX&yeIdm|K|^-YLGJm|CZxPHyPT&=x{Y-)cMOb5(g zZg}ni&J25#=(dMXZkFcFgr-Ak%>x?E{VCqB!FK&NPa@(sY^)-2hMPP>m$%Z9!*w*I zydRMdlY0y#+WmYOCp01o*=oI+w7pG`emR7vdh!eJmYV*6{(6V&`%)~+Z&{MPBzpW? zF1)V;y+Vr-i^#Jv63UZ#I;S$;`+kb{kOM%%#bpkn?t=3Zn&6LEVjU2#qyJ?`OdA8= zQJKTssSj;H*;7jqCM8FvgzFr-0x(Mr+ZhsoZkHDDAVCGzn6|BaAl%* zsvo$+?LTy-Ue>>3V~=0e0}5`#>TRarDWUI!I~vej9y*b0fG*@*>yG{P(c94lAwJ={ zKa|K7>!Le{fg8?&gY@f*m#y^ zXZu?LVAuTB%&j)erCs|Y*Z1!u11dJ1LDR@4ISL=^moG2H{qyp|9@<{~YzQ#D~=+$m>p+2IGzhhvMQM~8q!b9_U6uqvk=M$VobOwac4*H7o< za5RnneZ^qAn2t@7;#;4}4r7iHje@&uD$c)@HGOruu4AsfL1u%8pkYmoM8FPapv%~H zt(icZA2w@9#kstj+RWoWIZLEa6d|hFLX@AJ7OTSjM?S_CEPTs_P|}MHjsiDrot!+M znM}tMfg@vla_}lMI0rdY#4?39BX#T-6{ckPlmRdaG38^ZyViOeCm|uRcWV)x+wFlL zn~XHU?%?qGkBjF!N6$AxK`3WmPbY@ouP!Lsc4xCiMkx443_1p71F3=P z{07B279NxsqteAz7TTR|W1a zS!*4gX~E*%bcL!}MmU_XhZ)0Wn=B|GT-oPCtn&T&7+9(vu_ajvd=$jKkjvsvUT8Li z^cGEbxzbgZRte}0fljyqx*y+F2{m5@VujehiUcsG4I|-5AeqboaB%$(iRtdaIYma4 z^>45)0B?oqa{Bh7_P{CAW$G{5Su&KP?2-Aw_xW=S2;x>6y@Z$i&6%`|Hb2CTnL6%zKm*mMNls`DbG-@D)LwwTK9`B1u5dqyv* zm&?oBBV~6z5VD+ej)!=WNf5k#`iIY9?j9DhXsFZqtkvPhPPORnC!cwaCrcdnGX>T8 zVFlJ4g{q^jD2qv6HrdDRswNNxgOP*VwHT%f4D*G6!BMcN5d!u(vz1V;CddZo!|_KUh4#v%#gO+ zuBzEo529E+MRH6M&}6dN{VJiu>2+ zV-ykfluy5b2iJ|GU?=)Sjn;<1hNWN`+v*KhSr-P|Ak zUMC2-DHS#!uYW%CWRc}LqWR?1y3DaD=*ypg_V{&X~Y1<(1$ zcvk-8#d=|>8Xk7o>R);*mK!Uv%@jp>emLx_0LehBTV)HvFwPo!{pbp^G1FynfyfuE zwl>8&^q(91Re-Po^!44o}J6r+rAk>E8RR_`}r&bvC}n3 zJVe%A=NBFy$bf3D-E|I$=xte~MSmRr}!J;p{7GX@%$bK&*2jOEzBqi9gq*9cMvsmZT@DM)OOISiYXJcF%b(jlbXf_OPk z^A(jYFj<}AblhBrt9IL9ZMi$ptaZE7br6)g?ywXpO3^&(6NF7hVbec(adDK=fo+`1 zWjNqpyxlygx_j^%(`*MIRg(Ag3i$7D5y_d|_2e)-=zDxL9q!xo-Vk~CTWLh?0%Feq ze0#F|ORzsuA3&+Gdw)9isUn=OLQP4PuX^D2iZisf+D-{ilmyPo;fHqFK^5&W>W?q~B&Aag-=)TT&DRGcrO#1XVOc7%Iqjg2 znQ6A4)gHAt*g{CmtM9EW@ak`u{)jjgHe%=md=HXf6$)tv~!2})pE9GE4$HVE{#+-EmUj7|fv9b=AY z*(IX>u6fr(>~l#$2@HGLi@|kx<=kIb!JMty!6lfUfNgeHcIbpMPyj5yZl_pGp zLjphO?APyZjl}{Ry+gG>5aG98@sv*`d*^JLbam||t+>jlrH3=VIj)qz4YuG!>mL(n z?HnEZT;HTVS_9TyKEi1p0qN89x~gLgwHn*lNPH>E^@}ZLaK5c@f>+%Bs{YVcy$sx~MTf!AZG0 zPqF`q$KGrw1LHzy@W#+T3zNOt%9yV87=^~3DF){VYU|g`h`Nwf%sY!&zuLbYg=sh- zra6N=<#__1gCjEdN|Whe8uO&LXrkKsz|}$Kwp(@8+&{l5O`4wGp<+d8G8CmGsggfn z8NGhtjy*qw5LjGKSm2w8`xjXXRmqT8NyVwdGuVJo4Ab5@*5PX;v zTBqa~n>BSko+~KRYTmJxf|fM2haG zOxe;aG*Lm8)U56K9wFy=2aw4GaTtuxU+fAQ^R#_!Fl_iy-%)VZ|2n-y+&F+7&HF;n zF&sG0U(60CSoP1y*{9dzkqV4a_>oHBy7Qm0RNwR%J{ko{Orr}yiC9z`(W#!33*N}7 zD^#h?wUy%1HAP>h^)8{P!5|iH_noOwPk{7b4z~r~b4}3JrgN0ACOr4GLh_z1rUm@s z0wd#G2>7;@Q6~$NdM)O*=iW=dtks^bkFzd*(Z++QP&*s(O* zyD!`&J-XEN@D_o!`$!I9W~JL>;i+zRcW@pKH7LEA#d>oi+G)KJy<(Ha3}rp$RXw8{ z_z5v8?d#`)ALnNP;iR&u;$m+~HzdMHJ@9zB#AIJ)4VjuM+&kaIz!9##5cE#0^N+o8 zm)KGy!MEy)qr#&Km<5`Nc(Wc=|LC*={W+z^4aJ2Vj{7pmX+0-%TK!<@;fD3KW!F_! zBG}im?@D#EOqFR^7GK=l-B}-R3_H0@@HbkKRx4;Qc<*JtLblLT2Quc@8eJ4AC8Sb3 zPe0;8Ifn*wTF4(0-~%wwLt!6y!}QM9i3gN|)r&Bd%rFyQ6~|O`SWRfg2DY*SOqHQ2f%8m7 z&fDwUFs7X)AsVZ(oX{66*)X&Bg9p`aP1j^#pZ4RH^<>_eSL|iEH9ZG5_cK^ zsR9ly+}~%MA38QBV59NM{brt^jrG*$#GF&_T(xxS$XfB_?cqpX2o3gr44WcO z*S$Ch4<_u!s}%GYFkR)7LfkGhl}yYfVcz>WLCAwWc)JwM>jAB3`1EJCl$=9|VEc|{ z><%iz^V<1vTRPEs=XGNi@~rK?54?X@PsI~yc=}Y(3vzzOB1(;=^4_!r`2}%UtW#Th zf3;f`d~wXw*ujjN=l#Yz#%n7CRSKE2B2yxA`_Wmn|0eO_uXjuU-wio&eZT>YcdOff znq8(v5Q>wO7nlqwT-->4Q1^lHm2pABxaG9<5Xv}`H3m8)75COK(NY36-O%$AON+U7 zjs<-&V;em?C>cBnf!q|=Gdv@Rx@7}ot5h{JBXf0|C;=}V{k~zYg&8W$5$XYh`O@o{ zY<1rtp%o-hvvwd%aGe}6DD3X{#rGQ=hgSV8Q_id|Z|{AgP_dv@Xb1aj9!P7XWx1|) zbVZa}mKNpBMXvLsVtOmcQ^vx~j%P?{dD6_F5wR>J*VZ|E1{V=gb7N<$ruh$=3@E#c z>G1J4P%_ydqUA%^+a>X@r%%fy5hYwTEq}XFr{Yt2;~`yehg7}WQL;H;B1yd+00Fr; zG|gTg%$U}cekQtL#!+}kvSU5IM+Sv&KS3*H(vLw9(*}irzV)0n*TLv6LXQ2L{&4`I z{=_I$E%K%Ejo%9h!PsLJRxs3(Q)KN0e#|J9GqYwr{WSOwuTQSjCT9N2`zz6vU-IK_3JMGb4xg9?D_f51dq%J^RULU3 zOwOouK*|gb_c}yw-Xp$*1hrn%!f5Y2_m_Fq>Sh_FS2bbO_D7LDXJj6P=*y+c2fNOM zsl@AM<#|}2Fasyv!$r2TW)lt*@Qv1^rb=kk3)Jwi`O=ydNBSAL%6g@}ktL@`w+HP7 zjsgAGOoiDpNJI{)$scEH5DBRAg|Dw}s}dqr>a$L#^o|m6BF7SaMY)CY`CLC!f2x-3 z+fZ{ypRM2L)Xaqu_6Ox!NqR@Q`qVRJ^GYeXIWB`{oM8+m)R@^;)b&hf-ldp{u{)U4 z@w{5nWo-1*>wnF3?^8{V{VXRI4T6ZYbh^DZD?fxNXgndL;x7AjAgD&BgmG%U^heIj zEJ<0=(3m>df*oUS(sCV9BF2H8FSmZGR?m1~5C9(kOz}<4**PGf+0S5-yug|(ATPHT z#?~7l3ery;V$I3v*>nPf-Lk4Rwurc#gxR!63 z%=pLmGSEbef@PCqGlLvq>iqJYMIz3=@VwbvLzLqVK>5VXL57wL5eeS&cxD*|WP`0d zS2i20FnMIIuqj9KmX_)AOlJq}s^eVFwmhp~jaaq{g0}gqqMh4l{OlC+Cq`O~L6h1S zTSUh!*uyOJA)dSpIz_sCP0h}k+-Y4l+O^QV(kDvl>Y~@x=moAgHI@5wPNWN#DKYkR zMBA;4soTkeIRQ)6E7UH!N&(Y8-3^8o=#3SdM1XqQ-`Fc+h&0lkZ0))KilH2c@Bw!s zYLLimZ76UtN{OjyS~TM|L<_Ey!7dJXnwt$Kl zB&WtIeJ-iZX=s2!nu2POh6^4e)Ywj-=qgV}wonscdhbMgLQjD*^Na?aYhyw#4Ik*GH5>%1U87(;k3yukF&j$>>{&&CY7fFPSP__GnCU zOK{`&Dk_V{)y5-3DJz>q_eCYY(L+x)$4LZ+Z^fVMqklsr(X35@LNkI`WZ4bBADTkF z+Fa{@3$A2qGw0FXIvGRo94z^57I;7FOKG$)TYN#Q3r9~OMaYZO8Bf|U!e{011XMw( znWeNxI>D+IQf?#*+ML6Hjg8=vpN)?YE2X9rl3~V%BtQ(Fi+{B3Wh~gISN9E)RBzwe zTSj%B!jIg?))7HuP@mW&0=Eh68zR%F(SQG8nP1O0G|FTvh8y(&i-E6S{-f>|7F)VTBsDJW;v;Zx8^?JPKXAZw z%(qTKLd)M7TO?u#-udmDuZHzgBpd_S71H{A?8G_r?( z^1D+;smHv$rIX0VAxlKdWdHFhyd7I5cKrJ#E5Z@(_oD{yI2ItrJ#B>^TisO}bzU@c z+H@vI6k4zh!}Thxm4U_r4qCZV0wLKCGwWJhni|{l+>EjY$7GMLQd2G^o2H}}`n3DJ z5_|MMuzdOsNy;vcj$|rvCV=sGVPh{y&GrbC+6o!Ze#|#7n!=$pW^yNz5S|AGX_#J|wM2%+ zl@tn`tc+@^wm@%>Y&{O^ct;P!fY6YRveFYG^V}mmL#zY*SBaPkD=-K+r3L~t_5kL( z+C#>lJ}4m@TZ4jp=-Z!2HkwWNCUxNrifwSBm4gv}844?HPT&!WD{HF3A@>+zA&mC= zZjxgs$In~@q@2h-lHT43m82fn%>1j6LV82}-U98q?rx*gL-$

        e1f#`Oja?M8?ZM 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

        +YvLuE<2=y_SAdNH_c=?~9pMkE3! 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 z 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 x literal 0 HcmV?d00001 diff --git a/doc/administration/usageping/usage_ping_cohorts.md b/doc/administration/usageping/usage_ping_cohorts.md new file mode 100644 index 00000000000..f0991ad6fa7 --- /dev/null +++ b/doc/administration/usageping/usage_ping_cohorts.md @@ -0,0 +1,85 @@ +# Usage ping and cohorts + +> **Notes:** +- [Introduced][ee-380] in GitLab EE 8.10 and [added to CE][ce-23361] in GitLab 9.1. + +## Usage ping + +In order for GitLab to become a data-driven company, where deciding what to +build next is driven by how users are using the product, GitLab needs to collect +usage data. This is done via a feature called usage ping. + +Usage ping is a weekly, anonymous JSON sent to GitLab, containing several +metrics on how people use specific features. + +You can see at any given point in time the content of the JSON file that will be +sent in the Administration panel of your GitLab installation. This feature can +also be deactivated at any given time. + +The information that is sent contains the total number of: +* Comments +* Groups +* Users +* Projects +* Issues +* Labels +* CI builds +* Snippets +* Milestones +* Todos +* Pushes +* Merge requests +* Environments +* Triggers +* Deploy keys +* Pages +* Project Services +* Issue Boards +* CI Runners +* Deployments +* Geo Nodes +* LDAP Groups +* LDAP Keys +* LDAP Users +* LFS objects +* Protected branches +* Releases +* Remote mirrors +* Web hooks + +More will be added over time. The goal of this ping is to be as light as +possible, so it won't have any performance impact on your installation when +calculation is made. + +### Deactivate the usage ping + +By default, usage ping is opt-out. If you want to deactivate, go to the Settings +page of your administration panel and uncheck the Usage ping checkbox. + +![User cohort example](img/cohorts.png) + +## Cohorts + +As a benefit of having the usage ping active, GitLab lets you analyze the user's +activities of your GitLab installation. Under [LINK], when the usage ping is +active, GitLab will show the monthly cohorts of new users and their activities +over time. + +How do we read user cohorts table? Let's take an example with the following user +cohorts. + +[IMAGE] + +For the cohort of June 2016, 163 users have been created on this server. One +month after, in July 2016, 155 users (or 95% of the June cohort) are still +active. Two months after, 139 users (or 85%) are still active. 9 months after, +we can see that only 6% of this cohort are still active. + +How do we measure the activity of users? GitLab considers a user active if: +* the user signs in +* the user has a git activity (wether push or pull). + +### Setup + +1. Activate the usage ping as defined in [LINK] +2. Go to [LINK] to see the user cohorts of the server From cc677c8f8ce038610018e43fd41d238a3bf73ff3 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 3 Apr 2017 14:36:57 +0100 Subject: [PATCH 058/168] Make UserCohortsService more understandable 1. Extract out into several methods. 2. Add more comments describing the data and the shape of the data. --- app/services/user_cohorts_service.rb | 102 ++++++++++++++------- spec/services/user_cohorts_service_spec.rb | 6 +- 2 files changed, 71 insertions(+), 37 deletions(-) diff --git a/app/services/user_cohorts_service.rb b/app/services/user_cohorts_service.rb index 7f84b6a0634..6545ceffec6 100644 --- a/app/services/user_cohorts_service.rb +++ b/app/services/user_cohorts_service.rb @@ -1,41 +1,32 @@ class UserCohortsService - def initialize - end - - def execute(months_included) - if Gitlab::Database.postgresql? - created_at_month = "CAST(DATE_TRUNC('month', created_at) AS date)" - current_sign_in_at_month = "CAST(DATE_TRUNC('month', current_sign_in_at) AS date)" - elsif Gitlab::Database.mysql? - created_at_month = "STR_TO_DATE(DATE_FORMAT(created_at, '%Y-%m-01'), '%Y-%m-%d')" - current_sign_in_at_month = "STR_TO_DATE(DATE_FORMAT(current_sign_in_at, '%Y-%m-01'), '%Y-%m-%d')" - end - - counts_by_month = - User - .where('created_at > ?', months_included.months.ago.end_of_month) - .group(created_at_month, current_sign_in_at_month) - .reorder("#{created_at_month} ASC", "#{current_sign_in_at_month} DESC") - .count + MONTHS_INCLUDED = 12 + # Get a hash that looks like: + # + # { + # month => { + # months: [3, 2, 1], + # total: 3 + # inactive: 0 + # }, + # etc. + # + # The `months` array is always from oldest to newest, so it's always + # non-strictly decreasing from left to right. + # + def execute cohorts = {} - months = Array.new(months_included) { |i| i.months.ago.beginning_of_month.to_date } + months = Array.new(MONTHS_INCLUDED) { |i| i.months.ago.beginning_of_month.to_date } - months_included.times do - month = months.last - inactive = counts_by_month[[month, nil]] || 0 + MONTHS_INCLUDED.times do + created_at_month = months.last + activity_months = running_totals(months, created_at_month) - # Calculate a running sum of active users, so users active in later months - # count as active in this month, too. Start with the most recent month - # first, for calculating the running totals, and then reverse for - # displaying in the table. - activity_months = - months - .map { |activity_month| counts_by_month[[month, activity_month]] } - .reduce([]) { |result, total| result << result.last.to_i + total.to_i } - .reverse + # Even if no users registered in this month, we always want to have a + # value to fill in the table. + inactive = counts_by_month[[created_at_month, nil]].to_i - cohorts[month] = { + cohorts[created_at_month] = { months: activity_months, total: activity_months.first, inactive: inactive @@ -46,4 +37,51 @@ class UserCohortsService cohorts end + + private + + # Calculate a running sum of active users, so users active in later months + # count as active in this month, too. Start with the most recent month first, + # for calculating the running totals, and then reverse for displaying in the + # table. + def running_totals(all_months, created_at_month) + all_months + .map { |activity_month| counts_by_month[[created_at_month, activity_month]] } + .reduce([]) { |result, total| result << result.last.to_i + total.to_i } + .reverse + end + + # Get a hash that looks like: + # + # { + # [created_at_month, current_sign_in_at_month] => count, + # [created_at_month, current_sign_in_at_month_2] => count_2, + # # etc. + # } + # + # created_at_month can never be nil, but current_sign_in_at_month can (when a + # user has never logged in, just been created). This covers the last twelve + # months. + # + def counts_by_month + @counts_by_month ||= + begin + created_at_month = column_to_date('created_at') + current_sign_in_at_month = column_to_date('current_sign_in_at_month') + + User + .where('created_at > ?', MONTHS_INCLUDED.months.ago.end_of_month) + .group(created_at_month, current_sign_in_at_month) + .reorder("#{created_at_month} ASC", "#{current_sign_in_at_month} ASC") + .count + end + end + + def column_to_date(column) + if Gitlab::Database.postgresql? + "CAST(DATE_TRUNC('month', #{column}) AS date)" + elsif Gitlab::Database.mysql? + "STR_TO_DATE(DATE_FORMAT(#{column}, '%Y-%m-01'), '%Y-%m-%d')" + end + end end diff --git a/spec/services/user_cohorts_service_spec.rb b/spec/services/user_cohorts_service_spec.rb index 8d8d0de31cd..4db8d577d79 100644 --- a/spec/services/user_cohorts_service_spec.rb +++ b/spec/services/user_cohorts_service_spec.rb @@ -32,11 +32,7 @@ describe UserCohortsService do month_start(0) => { months: [2], total: 2, inactive: 1 } } - result = described_class.new.execute(12) - - expect(result.length).to eq(12) - expect(result.keys).to all(be_a(Date)) - expect(result).to eq(expected) + expect(described_class.new.execute).to eq(expected) end end end From af8a3e3583247001ed41a74c0ca5032ea6fe18fd Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 3 Apr 2017 14:37:36 +0100 Subject: [PATCH 059/168] Cache user cohorts results for a day --- app/controllers/admin/user_cohorts_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/user_cohorts_controller.rb b/app/controllers/admin/user_cohorts_controller.rb index 5dd6eedfb06..1d9e3547807 100644 --- a/app/controllers/admin/user_cohorts_controller.rb +++ b/app/controllers/admin/user_cohorts_controller.rb @@ -1,7 +1,9 @@ class Admin::UserCohortsController < Admin::ApplicationController def index if ApplicationSetting.current.usage_ping_enabled - @cohorts = UserCohortsService.new.execute(12) + @cohorts = Rails.cache.fetch('user_cohorts', expires_in: 1.day) do + UserCohortsService.new.execute + end end end end From a4a8f0db68e08233a87444edb29b2f77327cee3b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 3 Apr 2017 14:47:07 +0100 Subject: [PATCH 060/168] Fix usage ping doc location --- doc/README.md | 1 + doc/administration/{usageping => }/img/cohorts.png | Bin ...e_ping_cohorts.md => usage_ping_and_cohorts.md} | 13 ++++++++----- 3 files changed, 9 insertions(+), 5 deletions(-) rename doc/administration/{usageping => }/img/cohorts.png (100%) rename doc/administration/{usageping/usage_ping_cohorts.md => usage_ping_and_cohorts.md} (89%) diff --git a/doc/README.md b/doc/README.md index b6790b4d008..c2414b5f478 100644 --- a/doc/README.md +++ b/doc/README.md @@ -73,6 +73,7 @@ All technical content published by GitLab lives in the documentation, including: - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. +- [Usage ping and cohorts](administration/usage_ping_cohorts.md) Usage ping and cohorts - [Web terminals](administration/integration/terminal.md) Provide terminal access to environments from within GitLab. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. diff --git a/doc/administration/usageping/img/cohorts.png b/doc/administration/img/cohorts.png similarity index 100% rename from doc/administration/usageping/img/cohorts.png rename to doc/administration/img/cohorts.png diff --git a/doc/administration/usageping/usage_ping_cohorts.md b/doc/administration/usage_ping_and_cohorts.md similarity index 89% rename from doc/administration/usageping/usage_ping_cohorts.md rename to doc/administration/usage_ping_and_cohorts.md index f0991ad6fa7..f22a5e1c47a 100644 --- a/doc/administration/usageping/usage_ping_cohorts.md +++ b/doc/administration/usage_ping_and_cohorts.md @@ -56,7 +56,7 @@ calculation is made. By default, usage ping is opt-out. If you want to deactivate, go to the Settings page of your administration panel and uncheck the Usage ping checkbox. -![User cohort example](img/cohorts.png) +[IMAGE] ## Cohorts @@ -65,10 +65,10 @@ activities of your GitLab installation. Under [LINK], when the usage ping is active, GitLab will show the monthly cohorts of new users and their activities over time. -How do we read user cohorts table? Let's take an example with the following user -cohorts. +How do we read the user cohorts table? Let's take an example with the following +user cohorts. -[IMAGE] +![User cohort example](img/cohorts.png) For the cohort of June 2016, 163 users have been created on this server. One month after, in July 2016, 155 users (or 95% of the June cohort) are still @@ -77,9 +77,12 @@ we can see that only 6% of this cohort are still active. How do we measure the activity of users? GitLab considers a user active if: * the user signs in -* the user has a git activity (wether push or pull). +* the user has a git activity (whether push or pull). ### Setup 1. Activate the usage ping as defined in [LINK] 2. Go to [LINK] to see the user cohorts of the server + +[ee-380]: https://gitlab.com/gitlab-org/gitlab-ee/issues/380 +[ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361 From 0d7645e1b0312ea0a78401a835785422cc01660d Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 5 Apr 2017 10:49:33 +0100 Subject: [PATCH 061/168] Fix column name in user cohorts --- app/services/user_cohorts_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/user_cohorts_service.rb b/app/services/user_cohorts_service.rb index 6545ceffec6..c7909bb9e07 100644 --- a/app/services/user_cohorts_service.rb +++ b/app/services/user_cohorts_service.rb @@ -67,7 +67,7 @@ class UserCohortsService @counts_by_month ||= begin created_at_month = column_to_date('created_at') - current_sign_in_at_month = column_to_date('current_sign_in_at_month') + current_sign_in_at_month = column_to_date('current_sign_in_at') User .where('created_at > ?', MONTHS_INCLUDED.months.ago.end_of_month) From 5b698082323e019e47b5c739cdec0300b521340b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 5 Apr 2017 11:24:15 +0100 Subject: [PATCH 062/168] Rename user cohorts -> cohorts --- app/assets/javascripts/dispatcher.js | 2 +- app/controllers/admin/cohorts_controller.rb | 9 +++++++++ app/controllers/admin/user_cohorts_controller.rb | 9 --------- .../{user_cohorts_service.rb => cohorts_service.rb} | 2 +- .../{user_cohorts => cohorts}/_cohorts_table.html.haml | 0 .../{user_cohorts => cohorts}/_usage_ping.html.haml | 0 .../admin/{user_cohorts => cohorts}/index.html.haml | 0 app/views/admin/dashboard/_head.html.haml | 6 +++--- config/routes/admin.rb | 2 +- ...r_cohorts_service_spec.rb => cohorts_service_spec.rb} | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 app/controllers/admin/cohorts_controller.rb delete mode 100644 app/controllers/admin/user_cohorts_controller.rb rename app/services/{user_cohorts_service.rb => cohorts_service.rb} (99%) rename app/views/admin/{user_cohorts => cohorts}/_cohorts_table.html.haml (100%) rename app/views/admin/{user_cohorts => cohorts}/_usage_ping.html.haml (100%) rename app/views/admin/{user_cohorts => cohorts}/index.html.haml (100%) rename spec/services/{user_cohorts_service_spec.rb => cohorts_service_spec.rb} (98%) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 6c94975d851..a0afffb6635 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -366,7 +366,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); new Admin(); switch (path[1]) { case 'application_settings': - case 'user_cohorts': + case 'cohorts': new gl.ApplicationSettings(); break; case 'groups': diff --git a/app/controllers/admin/cohorts_controller.rb b/app/controllers/admin/cohorts_controller.rb new file mode 100644 index 00000000000..947afe3a028 --- /dev/null +++ b/app/controllers/admin/cohorts_controller.rb @@ -0,0 +1,9 @@ +class Admin::CohortsController < Admin::ApplicationController + def index + if ApplicationSetting.current.usage_ping_enabled + @cohorts = Rails.cache.fetch('cohorts', expires_in: 1.day) do + CohortsService.new.execute + end + end + end +end diff --git a/app/controllers/admin/user_cohorts_controller.rb b/app/controllers/admin/user_cohorts_controller.rb deleted file mode 100644 index 1d9e3547807..00000000000 --- a/app/controllers/admin/user_cohorts_controller.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Admin::UserCohortsController < Admin::ApplicationController - def index - if ApplicationSetting.current.usage_ping_enabled - @cohorts = Rails.cache.fetch('user_cohorts', expires_in: 1.day) do - UserCohortsService.new.execute - end - end - end -end diff --git a/app/services/user_cohorts_service.rb b/app/services/cohorts_service.rb similarity index 99% rename from app/services/user_cohorts_service.rb rename to app/services/cohorts_service.rb index c7909bb9e07..e7f8a50605f 100644 --- a/app/services/user_cohorts_service.rb +++ b/app/services/cohorts_service.rb @@ -1,4 +1,4 @@ -class UserCohortsService +class CohortsService MONTHS_INCLUDED = 12 # Get a hash that looks like: diff --git a/app/views/admin/user_cohorts/_cohorts_table.html.haml b/app/views/admin/cohorts/_cohorts_table.html.haml similarity index 100% rename from app/views/admin/user_cohorts/_cohorts_table.html.haml rename to app/views/admin/cohorts/_cohorts_table.html.haml diff --git a/app/views/admin/user_cohorts/_usage_ping.html.haml b/app/views/admin/cohorts/_usage_ping.html.haml similarity index 100% rename from app/views/admin/user_cohorts/_usage_ping.html.haml rename to app/views/admin/cohorts/_usage_ping.html.haml diff --git a/app/views/admin/user_cohorts/index.html.haml b/app/views/admin/cohorts/index.html.haml similarity index 100% rename from app/views/admin/user_cohorts/index.html.haml rename to app/views/admin/cohorts/index.html.haml diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml index 0c2e5efc052..163bd5662b0 100644 --- a/app/views/admin/dashboard/_head.html.haml +++ b/app/views/admin/dashboard/_head.html.haml @@ -27,7 +27,7 @@ = link_to admin_runners_path, title: 'Runners' do %span Runners - = nav_link path: 'user_cohorts#index' do - = link_to admin_user_cohorts_path, title: 'User cohorts' do + = nav_link path: 'cohorts#index' do + = link_to admin_cohorts_path, title: 'Cohorts' do %span - User cohorts + Cohorts diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 5b44d449b2b..52ba10604d4 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -106,7 +106,7 @@ namespace :admin do end end - resources :user_cohorts, only: :index + resources :cohorts, only: :index resources :builds, only: :index do collection do diff --git a/spec/services/user_cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb similarity index 98% rename from spec/services/user_cohorts_service_spec.rb rename to spec/services/cohorts_service_spec.rb index 4db8d577d79..878e9fcea05 100644 --- a/spec/services/user_cohorts_service_spec.rb +++ b/spec/services/cohorts_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe UserCohortsService do +describe CohortsService do describe '#execute' do def month_start(months_ago) months_ago.months.ago.beginning_of_month.to_date From 9fa00d69e8674d4752644c0e491965eae29fb9fc Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 5 Apr 2017 11:49:51 +0100 Subject: [PATCH 063/168] Fix documentation links from cohorts --- app/views/admin/cohorts/_cohorts_table.html.haml | 1 + app/views/admin/cohorts/index.html.haml | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/admin/cohorts/_cohorts_table.html.haml b/app/views/admin/cohorts/_cohorts_table.html.haml index a322ea9e5db..38795583a8c 100644 --- a/app/views/admin/cohorts/_cohorts_table.html.haml +++ b/app/views/admin/cohorts/_cohorts_table.html.haml @@ -3,6 +3,7 @@ User cohorts are shown for the last twelve months. Only users with activity are counted in the cohort total; inactive users are counted separately. + = link_to icon('question-circle'), help_page_path('administration/usage_ping_and_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank' .table-holder %table.table diff --git a/app/views/admin/cohorts/index.html.haml b/app/views/admin/cohorts/index.html.haml index dddcbd834f7..d7305db49cc 100644 --- a/app/views/admin/cohorts/index.html.haml +++ b/app/views/admin/cohorts/index.html.haml @@ -9,8 +9,8 @@ .bs-callout.bs-callout-warning.clearfix %p User cohorts are only shown when the - = link_to 'usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-data') - usage ping is enabled. It is currently disabled. To enable it and see - user cohorts, visit + = link_to 'usage ping', help_page_path('administration/usage_ping_and_cohorts', anchor: 'usage-ping'), target: '_blank' + is enabled. It is currently disabled. To enable it and see user cohorts, + visit = succeed '.' do = link_to 'application settings', admin_application_settings_path(anchor: 'usage-statistics') From 61eaf4fe1755799c42e474019759e3859e6b2926 Mon Sep 17 00:00:00 2001 From: Regis Freyd Date: Wed, 5 Apr 2017 15:23:50 +0100 Subject: [PATCH 064/168] Move documentation as per Axil comment --- doc/administration/usage_ping_and_cohorts.md | 88 ------------------ .../admin_area}/img/cohorts.png | Bin .../admin_area/settings/usage_statistics.md | 31 ++++-- doc/user/admin_area/user_cohorts.md | 30 ++++++ 4 files changed, 51 insertions(+), 98 deletions(-) delete mode 100644 doc/administration/usage_ping_and_cohorts.md rename doc/{administration => user/admin_area}/img/cohorts.png (100%) create mode 100644 doc/user/admin_area/user_cohorts.md diff --git a/doc/administration/usage_ping_and_cohorts.md b/doc/administration/usage_ping_and_cohorts.md deleted file mode 100644 index f22a5e1c47a..00000000000 --- a/doc/administration/usage_ping_and_cohorts.md +++ /dev/null @@ -1,88 +0,0 @@ -# Usage ping and cohorts - -> **Notes:** -- [Introduced][ee-380] in GitLab EE 8.10 and [added to CE][ce-23361] in GitLab 9.1. - -## Usage ping - -In order for GitLab to become a data-driven company, where deciding what to -build next is driven by how users are using the product, GitLab needs to collect -usage data. This is done via a feature called usage ping. - -Usage ping is a weekly, anonymous JSON sent to GitLab, containing several -metrics on how people use specific features. - -You can see at any given point in time the content of the JSON file that will be -sent in the Administration panel of your GitLab installation. This feature can -also be deactivated at any given time. - -The information that is sent contains the total number of: -* Comments -* Groups -* Users -* Projects -* Issues -* Labels -* CI builds -* Snippets -* Milestones -* Todos -* Pushes -* Merge requests -* Environments -* Triggers -* Deploy keys -* Pages -* Project Services -* Issue Boards -* CI Runners -* Deployments -* Geo Nodes -* LDAP Groups -* LDAP Keys -* LDAP Users -* LFS objects -* Protected branches -* Releases -* Remote mirrors -* Web hooks - -More will be added over time. The goal of this ping is to be as light as -possible, so it won't have any performance impact on your installation when -calculation is made. - -### Deactivate the usage ping - -By default, usage ping is opt-out. If you want to deactivate, go to the Settings -page of your administration panel and uncheck the Usage ping checkbox. - -[IMAGE] - -## Cohorts - -As a benefit of having the usage ping active, GitLab lets you analyze the user's -activities of your GitLab installation. Under [LINK], when the usage ping is -active, GitLab will show the monthly cohorts of new users and their activities -over time. - -How do we read the user cohorts table? Let's take an example with the following -user cohorts. - -![User cohort example](img/cohorts.png) - -For the cohort of June 2016, 163 users have been created on this server. One -month after, in July 2016, 155 users (or 95% of the June cohort) are still -active. Two months after, 139 users (or 85%) are still active. 9 months after, -we can see that only 6% of this cohort are still active. - -How do we measure the activity of users? GitLab considers a user active if: -* the user signs in -* the user has a git activity (whether push or pull). - -### Setup - -1. Activate the usage ping as defined in [LINK] -2. Go to [LINK] to see the user cohorts of the server - -[ee-380]: https://gitlab.com/gitlab-org/gitlab-ee/issues/380 -[ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361 diff --git a/doc/administration/img/cohorts.png b/doc/user/admin_area/img/cohorts.png similarity index 100% rename from doc/administration/img/cohorts.png rename to doc/user/admin_area/img/cohorts.png diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index 6c9352332c2..2f85777f840 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -22,26 +22,26 @@ importance of the update. If enabled, the version status will also be shown in the help page (`/help`) for all signed in users. -## Usage data +## Usage ping > [Introduced][ee-557] in GitLab Enterprise Edition 8.10. More statistics -[were added][ee-735] in GitLab Enterprise Edition 8.12. +[were added][ee-735] in GitLab Enterprise Edition 8.12. [Moved to CE][ce-23361] +in GitLab 9.1. -GitLab Inc. can collect non-sensitive information about how Enterprise Edition -customers use their GitLab instance upon the activation of a ping feature +GitLab Inc. can collect non-sensitive information about how GitLab users +use their GitLab instance upon the activation of a ping feature located in the admin panel (`/admin/application_settings`). -You can see the **exact** JSON payload that your instance sends to GitLab Inc. +You can see the **exact** JSON payload that your instance sends to GitLab in the "Usage statistics" section of the admin panel. -Nothing qualitative is collected. Only quantitative. Meaning, no project name, +Nothing qualitative is collected. Only quantitative. That means no project name, author name, nature of comments, name of labels, etc. -This is done mainly for the following reasons: +The usage ping is sent for the following reasons: -- to have a better understanding on how our users use our product -- to provide more tools for the customer success team to help customers onboard - better. +- to have a better understanding on how our users use our product, +- to be more data driven when creating or changing features. The total number of the following is sent back to GitLab Inc.: @@ -79,6 +79,16 @@ The total number of the following is sent back to GitLab Inc.: Also, we track if you've installed Mattermost with GitLab. For example: `"mattermost_enabled":true"`. +More data will be added over time. The goal of this ping is to be as light as +possible, so it won't have any performance impact on your installation when +calculation is made. + +### Deactivate the usage ping + +By default, usage ping is opt-out. If you want to deactivate this feature, go to +the Settings page of your administration panel and uncheck the Usage ping +checkbox. + ## Privacy policy GitLab Inc. does **not** collect any sensitive information, like project names @@ -89,3 +99,4 @@ Read more in about the [Privacy policy](https://about.gitlab.com/privacy). [ee-557]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/557 [ee-735]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/735 +[ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361 diff --git a/doc/user/admin_area/user_cohorts.md b/doc/user/admin_area/user_cohorts.md new file mode 100644 index 00000000000..06b1597301b --- /dev/null +++ b/doc/user/admin_area/user_cohorts.md @@ -0,0 +1,30 @@ +# Cohorts + +> **Notes:** +- [Introduced][ce-23361] in GitLab 9.1. + +As a benefit of having the [usage ping active](settings/usage_statistics.md), +GitLab lets you analyze the user's activities of your GitLab installation. +Under [LINK], when the usage ping is active, GitLab will show the monthly +cohorts of new users and their activities over time. + +How do we read the user cohorts table? Let's take an example with the following +user cohorts. + +![User cohort example](img/cohorts.png) + +For the cohort of June 2016, 163 users have been created on this server. One +month after, in July 2016, 155 users (or 95% of the June cohort) are still +active. Two months after, 139 users (or 85%) are still active. 9 months after, +we can see that only 6% of this cohort are still active. + +How do we measure the activity of users? GitLab considers a user active if: +* the user signs in +* the user has a git activity (whether push or pull). + +### Setup + +1. Activate the usage ping as defined in [LINK] +2. Go to [LINK] to see the user cohorts of the server + +[ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361 From ac0146a08eb2ec6c23b46ea014376b7a70a21415 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 6 Apr 2017 13:12:12 +0100 Subject: [PATCH 065/168] Use serializer for formatting cohorts data --- app/controllers/admin/cohorts_controller.rb | 6 +- .../cohort_activity_month_entity.rb | 11 +++ app/serializers/cohort_entity.rb | 17 ++++ app/serializers/cohorts_entity.rb | 4 + app/serializers/cohorts_serializer.rb | 3 + app/services/cohorts_service.rb | 69 ++++++++------ .../admin/cohorts/_cohorts_table.html.haml | 36 +++----- spec/services/cohorts_service_spec.rb | 91 ++++++++++++++++--- 8 files changed, 169 insertions(+), 68 deletions(-) create mode 100644 app/serializers/cohort_activity_month_entity.rb create mode 100644 app/serializers/cohort_entity.rb create mode 100644 app/serializers/cohorts_entity.rb create mode 100644 app/serializers/cohorts_serializer.rb diff --git a/app/controllers/admin/cohorts_controller.rb b/app/controllers/admin/cohorts_controller.rb index 947afe3a028..9b77c554908 100644 --- a/app/controllers/admin/cohorts_controller.rb +++ b/app/controllers/admin/cohorts_controller.rb @@ -1,9 +1,11 @@ class Admin::CohortsController < Admin::ApplicationController def index - if ApplicationSetting.current.usage_ping_enabled - @cohorts = Rails.cache.fetch('cohorts', expires_in: 1.day) do + if current_application_settings.usage_ping_enabled + cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do CohortsService.new.execute end + + @cohorts = CohortsSerializer.new.represent(cohorts_results) end end end diff --git a/app/serializers/cohort_activity_month_entity.rb b/app/serializers/cohort_activity_month_entity.rb new file mode 100644 index 00000000000..e6788a8b596 --- /dev/null +++ b/app/serializers/cohort_activity_month_entity.rb @@ -0,0 +1,11 @@ +class CohortActivityMonthEntity < Grape::Entity + include ActionView::Helpers::NumberHelper + + expose :total do |cohort_activity_month| + number_with_delimiter(cohort_activity_month[:total]) + end + + expose :percentage do |cohort_activity_month| + number_to_percentage(cohort_activity_month[:percentage], precision: 0) + end +end diff --git a/app/serializers/cohort_entity.rb b/app/serializers/cohort_entity.rb new file mode 100644 index 00000000000..7cdba5b0484 --- /dev/null +++ b/app/serializers/cohort_entity.rb @@ -0,0 +1,17 @@ +class CohortEntity < Grape::Entity + include ActionView::Helpers::NumberHelper + + expose :registration_month do |cohort| + cohort[:registration_month].strftime('%b %Y') + end + + expose :total do |cohort| + number_with_delimiter(cohort[:total]) + end + + expose :inactive do |cohort| + number_with_delimiter(cohort[:inactive]) + end + + expose :activity_months, using: CohortActivityMonthEntity +end diff --git a/app/serializers/cohorts_entity.rb b/app/serializers/cohorts_entity.rb new file mode 100644 index 00000000000..98f5995ba6f --- /dev/null +++ b/app/serializers/cohorts_entity.rb @@ -0,0 +1,4 @@ +class CohortsEntity < Grape::Entity + expose :months_included + expose :cohorts, using: CohortEntity +end diff --git a/app/serializers/cohorts_serializer.rb b/app/serializers/cohorts_serializer.rb new file mode 100644 index 00000000000..fe9367b13d8 --- /dev/null +++ b/app/serializers/cohorts_serializer.rb @@ -0,0 +1,3 @@ +class CohortsSerializer < AnalyticsGenericSerializer + entity CohortsEntity +end diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb index e7f8a50605f..a7963f01176 100644 --- a/app/services/cohorts_service.rb +++ b/app/services/cohorts_service.rb @@ -1,11 +1,19 @@ class CohortsService MONTHS_INCLUDED = 12 - # Get a hash that looks like: + def execute + { + months_included: MONTHS_INCLUDED, + cohorts: cohorts + } + end + + # Get an array of hashes that looks like: # - # { - # month => { - # months: [3, 2, 1], + # [ + # { + # registration_month: Date.new(2017, 3), + # activity_months: [3, 2, 1], # total: 3 # inactive: 0 # }, @@ -13,29 +21,26 @@ class CohortsService # # The `months` array is always from oldest to newest, so it's always # non-strictly decreasing from left to right. - # - def execute - cohorts = {} + def cohorts months = Array.new(MONTHS_INCLUDED) { |i| i.months.ago.beginning_of_month.to_date } - MONTHS_INCLUDED.times do - created_at_month = months.last - activity_months = running_totals(months, created_at_month) + Array.new(MONTHS_INCLUDED) do + registration_month = months.last + activity_months = running_totals(months, registration_month) # Even if no users registered in this month, we always want to have a # value to fill in the table. - inactive = counts_by_month[[created_at_month, nil]].to_i - - cohorts[created_at_month] = { - months: activity_months, - total: activity_months.first, - inactive: inactive - } + inactive = counts_by_month[[registration_month, nil]].to_i months.pop - end - cohorts + { + registration_month: registration_month, + activity_months: activity_months, + total: activity_months.first[:total], + inactive: inactive + } + end end private @@ -44,11 +49,20 @@ class CohortsService # count as active in this month, too. Start with the most recent month first, # for calculating the running totals, and then reverse for displaying in the # table. - def running_totals(all_months, created_at_month) - all_months - .map { |activity_month| counts_by_month[[created_at_month, activity_month]] } - .reduce([]) { |result, total| result << result.last.to_i + total.to_i } - .reverse + # + # Each month has a total, and a percentage of the overall total, as keys. + def running_totals(all_months, registration_month) + month_totals = + all_months + .map { |activity_month| counts_by_month[[registration_month, activity_month]] } + .reduce([]) { |result, total| result << result.last.to_i + total.to_i } + .reverse + + overall_total = month_totals.first + + month_totals.map do |total| + { total: total, percentage: total.zero? ? 0 : 100 * total / overall_total } + end end # Get a hash that looks like: @@ -60,9 +74,8 @@ class CohortsService # } # # created_at_month can never be nil, but current_sign_in_at_month can (when a - # user has never logged in, just been created). This covers the last twelve - # months. - # + # user has never logged in, just been created). This covers the last + # MONTHS_INCLUDED months. def counts_by_month @counts_by_month ||= begin @@ -80,7 +93,7 @@ class CohortsService def column_to_date(column) if Gitlab::Database.postgresql? "CAST(DATE_TRUNC('month', #{column}) AS date)" - elsif Gitlab::Database.mysql? + else "STR_TO_DATE(DATE_FORMAT(#{column}, '%Y-%m-01'), '%Y-%m-%d')" end end diff --git a/app/views/admin/cohorts/_cohorts_table.html.haml b/app/views/admin/cohorts/_cohorts_table.html.haml index 38795583a8c..c3b37bcf8ec 100644 --- a/app/views/admin/cohorts/_cohorts_table.html.haml +++ b/app/views/admin/cohorts/_cohorts_table.html.haml @@ -1,8 +1,8 @@ .bs-callout.clearfix %p - User cohorts are shown for the last twelve months. Only users with - activity are counted in the cohort total; inactive users are counted - separately. + User cohorts are shown for the last #{@cohorts[:months_included]} + months. Only users with activity are counted in the cohort total; inactive + users are counted separately. = link_to icon('question-circle'), help_page_path('administration/usage_ping_and_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank' .table-holder @@ -12,27 +12,17 @@ %th Registration month %th Inactive users %th Cohort total - %th Month 0 - %th Month 1 - %th Month 2 - %th Month 3 - %th Month 4 - %th Month 5 - %th Month 6 - %th Month 7 - %th Month 8 - %th Month 9 - %th Month 10 - %th Month 11 + - @cohorts[:months_included].times do |i| + %th Month #{i} %tbody - - @cohorts.each do |registration_month, cohort| + - @cohorts[:cohorts].each do |cohort| %tr - %td= registration_month.strftime('%b %Y') - %td= number_with_delimiter(cohort[:inactive]) - %td= number_with_delimiter(cohort[:total]) - - cohort[:months].each do |running_total| + %td= cohort[:registration_month] + %td= cohort[:inactive] + %td= cohort[:total] + - cohort[:activity_months].each do |activity_month| %td - - next if cohort[:total].zero? - = number_to_percentage(100 * running_total / cohort[:total], precision: 0) + - next if cohort[:total] == '0' + = activity_month[:percentage] %br - (#{number_with_delimiter(running_total)}) + = activity_month[:total] diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb index 878e9fcea05..5dd89e3e341 100644 --- a/spec/services/cohorts_service_spec.rb +++ b/spec/services/cohorts_service_spec.rb @@ -17,22 +17,83 @@ describe CohortsService do create(:user) # this user is inactive and belongs to the current month - expected = { - month_start(11) => { months: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], total: 0, inactive: 0 }, - month_start(10) => { months: [2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], total: 2, inactive: 0 }, - month_start(9) => { months: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], total: 0, inactive: 0 }, - month_start(8) => { months: [2, 1, 1, 1, 1, 1, 1, 1, 1], total: 2, inactive: 0 }, - month_start(7) => { months: [0, 0, 0, 0, 0, 0, 0, 0], total: 0, inactive: 0 }, - month_start(6) => { months: [2, 1, 1, 1, 1, 1, 1], total: 2, inactive: 0 }, - month_start(5) => { months: [0, 0, 0, 0, 0, 0], total: 0, inactive: 0 }, - month_start(4) => { months: [2, 1, 1, 1, 1], total: 2, inactive: 0 }, - month_start(3) => { months: [0, 0, 0, 0], total: 0, inactive: 0 }, - month_start(2) => { months: [2, 1, 1], total: 2, inactive: 0 }, - month_start(1) => { months: [0, 0], total: 0, inactive: 0 }, - month_start(0) => { months: [2], total: 2, inactive: 1 } - } + expected_cohorts = [ + { + registration_month: month_start(11), + activity_months: Array.new(12) { { total: 0, percentage: 0 } }, + total: 0, + inactive: 0 + }, + { + registration_month: month_start(10), + activity_months: [{ total: 2, percentage: 100 }] + Array.new(10) { { total: 1, percentage: 50 } }, + total: 2, + inactive: 0 + }, + { + registration_month: month_start(9), + activity_months: Array.new(10) { { total: 0, percentage: 0 } }, + total: 0, + inactive: 0 + }, + { + registration_month: month_start(8), + activity_months: [{ total: 2, percentage: 100 }] + Array.new(8) { { total: 1, percentage: 50 } }, + total: 2, + inactive: 0 + }, + { + registration_month: month_start(7), + activity_months: Array.new(8) { { total: 0, percentage: 0 } }, + total: 0, + inactive: 0 + }, + { + registration_month: month_start(6), + activity_months: [{ total: 2, percentage: 100 }] + Array.new(6) { { total: 1, percentage: 50 } }, + total: 2, + inactive: 0 + }, + { + registration_month: month_start(5), + activity_months: Array.new(6) { { total: 0, percentage: 0 } }, + total: 0, + inactive: 0 + }, + { + registration_month: month_start(4), + activity_months: [{ total: 2, percentage: 100 }] + Array.new(4) { { total: 1, percentage: 50 } }, + total: 2, + inactive: 0 + }, + { + registration_month: month_start(3), + activity_months: Array.new(4) { { total: 0, percentage: 0 } }, + total: 0, + inactive: 0 + }, + { + registration_month: month_start(2), + activity_months: [{ total: 2, percentage: 100 }] + Array.new(2) { { total: 1, percentage: 50 } }, + total: 2, + inactive: 0 + }, + { + registration_month: month_start(1), + activity_months: Array.new(2) { { total: 0, percentage: 0 } }, + total: 0, + inactive: 0 + }, + { + registration_month: month_start(0), + activity_months: [{ total: 2, percentage: 100 }], + total: 2, + inactive: 1 + }, + ] - expect(described_class.new.execute).to eq(expected) + expect(described_class.new.execute).to eq(months_included: 12, + cohorts: expected_cohorts) end end end From 44c6ef6125e8636373d8c45660ea3fd3e8ae37f4 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 6 Apr 2017 13:13:15 +0100 Subject: [PATCH 066/168] Remove redundant sentence from cohorts disabled --- app/views/admin/cohorts/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/cohorts/index.html.haml b/app/views/admin/cohorts/index.html.haml index d7305db49cc..cd8e29926eb 100644 --- a/app/views/admin/cohorts/index.html.haml +++ b/app/views/admin/cohorts/index.html.haml @@ -10,7 +10,7 @@ %p User cohorts are only shown when the = link_to 'usage ping', help_page_path('administration/usage_ping_and_cohorts', anchor: 'usage-ping'), target: '_blank' - is enabled. It is currently disabled. To enable it and see user cohorts, + is enabled. To enable it and see user cohorts, visit = succeed '.' do = link_to 'application settings', admin_application_settings_path(anchor: 'usage-statistics') From 800baa426d3b7872ccdeff27e98a46206de315df Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 6 Apr 2017 13:18:34 +0100 Subject: [PATCH 067/168] Tidy up usage ping and cohorts docs --- .../admin_area/settings/usage_statistics.md | 17 ++++++++--------- doc/user/admin_area/user_cohorts.md | 8 ++++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index 2f85777f840..0f43e51e75a 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -25,8 +25,8 @@ for all signed in users. ## Usage ping > [Introduced][ee-557] in GitLab Enterprise Edition 8.10. More statistics -[were added][ee-735] in GitLab Enterprise Edition 8.12. [Moved to CE][ce-23361] -in GitLab 9.1. +[were added][ee-735] in GitLab Enterprise Edition +8.12. [Introduced to GitLab Community Edition][ce-23361] in 9.1. GitLab Inc. can collect non-sensitive information about how GitLab users use their GitLab instance upon the activation of a ping feature @@ -35,13 +35,12 @@ located in the admin panel (`/admin/application_settings`). You can see the **exact** JSON payload that your instance sends to GitLab in the "Usage statistics" section of the admin panel. -Nothing qualitative is collected. Only quantitative. That means no project name, -author name, nature of comments, name of labels, etc. +Nothing qualitative is collected. Only quantitative. That means no project +names, author names, comment bodies, names of labels, etc. -The usage ping is sent for the following reasons: - -- to have a better understanding on how our users use our product, -- to be more data driven when creating or changing features. +The usage ping is sent in order for GitLab Inc. to have a better understanding +of how our users use our product, and to be more data-driven when creating or +changing features. The total number of the following is sent back to GitLab Inc.: @@ -81,7 +80,7 @@ For example: `"mattermost_enabled":true"`. More data will be added over time. The goal of this ping is to be as light as possible, so it won't have any performance impact on your installation when -calculation is made. +the calculation is made. ### Deactivate the usage ping diff --git a/doc/user/admin_area/user_cohorts.md b/doc/user/admin_area/user_cohorts.md index 06b1597301b..81e9711375f 100644 --- a/doc/user/admin_area/user_cohorts.md +++ b/doc/user/admin_area/user_cohorts.md @@ -4,7 +4,7 @@ - [Introduced][ce-23361] in GitLab 9.1. As a benefit of having the [usage ping active](settings/usage_statistics.md), -GitLab lets you analyze the user's activities of your GitLab installation. +GitLab lets you analyze the users' activities of your GitLab installation. Under [LINK], when the usage ping is active, GitLab will show the monthly cohorts of new users and their activities over time. @@ -14,13 +14,13 @@ user cohorts. ![User cohort example](img/cohorts.png) For the cohort of June 2016, 163 users have been created on this server. One -month after, in July 2016, 155 users (or 95% of the June cohort) are still -active. Two months after, 139 users (or 85%) are still active. 9 months after, +month later, in July 2016, 155 users (or 95% of the June cohort) are still +active. Two months later, 139 users (or 85%) are still active. 9 months later, we can see that only 6% of this cohort are still active. How do we measure the activity of users? GitLab considers a user active if: * the user signs in -* the user has a git activity (whether push or pull). +* the user has Git activity (whether push or pull). ### Setup From a67b6b38948df3b4028d343e87f87558b66fdbf6 Mon Sep 17 00:00:00 2001 From: Regis Freyd Date: Fri, 7 Apr 2017 13:41:11 -0400 Subject: [PATCH 068/168] Add missing links in the documentation --- doc/user/admin_area/settings/usage_statistics.md | 2 +- doc/user/admin_area/user_cohorts.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index 0f43e51e75a..5108f52c49e 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -26,7 +26,7 @@ for all signed in users. > [Introduced][ee-557] in GitLab Enterprise Edition 8.10. More statistics [were added][ee-735] in GitLab Enterprise Edition -8.12. [Introduced to GitLab Community Edition][ce-23361] in 9.1. +8.12. [Moved to GitLab Community Edition][ce-23361] in 9.1. GitLab Inc. can collect non-sensitive information about how GitLab users use their GitLab instance upon the activation of a ping feature diff --git a/doc/user/admin_area/user_cohorts.md b/doc/user/admin_area/user_cohorts.md index 81e9711375f..1671487bc8c 100644 --- a/doc/user/admin_area/user_cohorts.md +++ b/doc/user/admin_area/user_cohorts.md @@ -5,8 +5,8 @@ As a benefit of having the [usage ping active](settings/usage_statistics.md), GitLab lets you analyze the users' activities of your GitLab installation. -Under [LINK], when the usage ping is active, GitLab will show the monthly -cohorts of new users and their activities over time. +Under `/admin/cohorts`, when the usage ping is active, GitLab will show the +monthly cohorts of new users and their activities over time. How do we read the user cohorts table? Let's take an example with the following user cohorts. @@ -24,7 +24,7 @@ How do we measure the activity of users? GitLab considers a user active if: ### Setup -1. Activate the usage ping as defined in [LINK] -2. Go to [LINK] to see the user cohorts of the server +1. Activate the usage ping as defined [in the documentation](settings/usage_statistics.md) +2. Go to `/admin/cohorts` to see the user cohorts of the server [ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361 From f17f60405eb5ae9b0091abf566b332ff08267145 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 7 Apr 2017 19:54:06 +0100 Subject: [PATCH 069/168] Use last_activity_on in cohorts --- app/services/cohorts_service.rb | 12 ++++++------ spec/services/cohorts_service_spec.rb | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb index a7963f01176..6781533af28 100644 --- a/app/services/cohorts_service.rb +++ b/app/services/cohorts_service.rb @@ -68,24 +68,24 @@ class CohortsService # Get a hash that looks like: # # { - # [created_at_month, current_sign_in_at_month] => count, - # [created_at_month, current_sign_in_at_month_2] => count_2, + # [created_at_month, last_activity_on_month] => count, + # [created_at_month, last_activity_on_month_2] => count_2, # # etc. # } # - # created_at_month can never be nil, but current_sign_in_at_month can (when a + # created_at_month can never be nil, but last_activity_on_month can (when a # user has never logged in, just been created). This covers the last # MONTHS_INCLUDED months. def counts_by_month @counts_by_month ||= begin created_at_month = column_to_date('created_at') - current_sign_in_at_month = column_to_date('current_sign_in_at') + last_activity_on_month = column_to_date('last_activity_on') User .where('created_at > ?', MONTHS_INCLUDED.months.ago.end_of_month) - .group(created_at_month, current_sign_in_at_month) - .reorder("#{created_at_month} ASC", "#{current_sign_in_at_month} ASC") + .group(created_at_month, last_activity_on_month) + .reorder("#{created_at_month} ASC", "#{last_activity_on_month} ASC") .count end end diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb index 5dd89e3e341..1e99442fdcb 100644 --- a/spec/services/cohorts_service_spec.rb +++ b/spec/services/cohorts_service_spec.rb @@ -11,8 +11,8 @@ describe CohortsService do 6.times do |months_ago| months_ago_time = (months_ago * 2).months.ago - create(:user, created_at: months_ago_time, current_sign_in_at: Time.now) - create(:user, created_at: months_ago_time, current_sign_in_at: months_ago_time) + create(:user, created_at: months_ago_time, last_activity_on: Time.now) + create(:user, created_at: months_ago_time, last_activity_on: months_ago_time) end create(:user) # this user is inactive and belongs to the current month From 00e9568e140165edcc091bd6729393cdeaac642b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 7 Apr 2017 20:34:06 +0100 Subject: [PATCH 070/168] Fix doc links --- app/views/admin/cohorts/_cohorts_table.html.haml | 2 +- app/views/admin/cohorts/index.html.haml | 2 +- doc/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/admin/cohorts/_cohorts_table.html.haml b/app/views/admin/cohorts/_cohorts_table.html.haml index c3b37bcf8ec..701a4e62b39 100644 --- a/app/views/admin/cohorts/_cohorts_table.html.haml +++ b/app/views/admin/cohorts/_cohorts_table.html.haml @@ -3,7 +3,7 @@ User cohorts are shown for the last #{@cohorts[:months_included]} months. Only users with activity are counted in the cohort total; inactive users are counted separately. - = link_to icon('question-circle'), help_page_path('administration/usage_ping_and_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank' + = link_to icon('question-circle'), help_page_path('user/admin_area/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank' .table-holder %table.table diff --git a/app/views/admin/cohorts/index.html.haml b/app/views/admin/cohorts/index.html.haml index cd8e29926eb..46fe12a5a99 100644 --- a/app/views/admin/cohorts/index.html.haml +++ b/app/views/admin/cohorts/index.html.haml @@ -9,7 +9,7 @@ .bs-callout.bs-callout-warning.clearfix %p User cohorts are only shown when the - = link_to 'usage ping', help_page_path('administration/usage_ping_and_cohorts', anchor: 'usage-ping'), target: '_blank' + = link_to 'usage ping', help_page_path('user/admin_area/usage_statistics'), target: '_blank' is enabled. To enable it and see user cohorts, visit = succeed '.' do diff --git a/doc/README.md b/doc/README.md index c2414b5f478..5deb8a49e54 100644 --- a/doc/README.md +++ b/doc/README.md @@ -73,7 +73,7 @@ All technical content published by GitLab lives in the documentation, including: - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. -- [Usage ping and cohorts](administration/usage_ping_cohorts.md) Usage ping and cohorts +- [User cohorts](user/admin_area/user_cohorts.md) View user activity over time. - [Web terminals](administration/integration/terminal.md) Provide terminal access to environments from within GitLab. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. From 380e40fee30d836e6dffb1e956df39033d43a671 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 12 Apr 2017 16:13:24 +0100 Subject: [PATCH 071/168] Remove unused user activities code --- app/models/user.rb | 4 - config/initializers/1_settings.rb | 4 +- .../20161007073613_create_user_activities.rb | 24 +-- ...161128170531_drop_user_activities_table.rb | 16 +- lib/api/helpers/internal_helpers.rb | 3 +- lib/api/internal.rb | 10 -- lib/api/users.rb | 1 - lib/gitlab/pagination_delegate.rb | 65 -------- spec/lib/gitlab/pagination_delegate_spec.rb | 155 ------------------ spec/requests/api/users_spec.rb.rej | 124 -------------- 10 files changed, 6 insertions(+), 400 deletions(-) delete mode 100644 lib/gitlab/pagination_delegate.rb delete mode 100644 spec/lib/gitlab/pagination_delegate_spec.rb delete mode 100644 spec/requests/api/users_spec.rb.rej diff --git a/app/models/user.rb b/app/models/user.rb index 2a351b679ea..34a5f3c9202 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -949,10 +949,6 @@ class User < ActiveRecord::Base end end - def record_activity - Gitlab::UserActivities::ActivitySet.record(self) - end - private def access_level=(new_level) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 96d39f7c4b3..2ccf9e45111 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -212,8 +212,8 @@ Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab' Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}" Settings.gitlab['email_subject_suffix'] ||= ENV['GITLAB_EMAIL_SUBJECT_SUFFIX'] || "" -Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url) -Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) +Settings.gitlab['base_url'] ||= Settings.__send__(:build_base_gitlab_url) +Settings.gitlab['url'] ||= Settings.__send__(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' Settings.gitlab['user_home'] ||= begin Etc.getpwnam(Settings.gitlab['user']).dir diff --git a/db/migrate/20161007073613_create_user_activities.rb b/db/migrate/20161007073613_create_user_activities.rb index 4239cebd8f6..1d694e777a1 100644 --- a/db/migrate/20161007073613_create_user_activities.rb +++ b/db/migrate/20161007073613_create_user_activities.rb @@ -1,27 +1,7 @@ class CreateUserActivities < ActiveRecord::Migration - # Set this constant to true if this migration requires downtime. - DOWNTIME = true - - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - DOWNTIME_REASON = 'Adding foreign key' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! + DOWNTIME = false + # This migration is a no-op. It just exists to match EE. def change - create_table :user_activities do |t| - t.belongs_to :user, index: { unique: true }, foreign_key: { on_delete: :cascade } - t.datetime :last_activity_at, null: false - end end end diff --git a/db/post_migrate/20161128170531_drop_user_activities_table.rb b/db/post_migrate/20161128170531_drop_user_activities_table.rb index 3ece0722821..00bc0c73015 100644 --- a/db/post_migrate/20161128170531_drop_user_activities_table.rb +++ b/db/post_migrate/20161128170531_drop_user_activities_table.rb @@ -1,23 +1,9 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - class DropUserActivitiesTable < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers DOWNTIME = false - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - + # This migration is a no-op. It just exists to match EE. def change - drop_table :user_activities end end diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 8f25bcf2f54..718f936a1fc 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -62,8 +62,7 @@ module API end def log_user_activity(actor) - commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS + - Gitlab::GitAccess::GIT_ANNEX_COMMANDS + commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS ::Users::ActivityService.new(actor, 'Git SSH').execute if commands.include?(params[:action]) end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index d374d183dcd..5b48ee8665f 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -15,16 +15,6 @@ module API # project - project path with namespace # action - git action (git-upload-pack or git-receive-pack) # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList - helpers do - def log_user_activity(actor) - commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS + - Gitlab::GitAccess::PUSH_COMMANDS + - Gitlab::GitAccess::GIT_ANNEX_COMMANDS - - ::Users::ActivityService.new(actor, 'Git SSH').execute if commands.include?(params[:action]) - end - end - post "/allowed" do status 200 diff --git a/lib/api/users.rb b/lib/api/users.rb index bcfbd9ab3c5..9e0faff6c05 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -535,7 +535,6 @@ module API current_user.update_secondary_emails! end - desc 'Get a list of user activities' params do optional :from, type: DateTime, default: 6.months.ago, desc: 'Date string in the format YEAR-MONTH-DAY' diff --git a/lib/gitlab/pagination_delegate.rb b/lib/gitlab/pagination_delegate.rb deleted file mode 100644 index d4913e908b2..00000000000 --- a/lib/gitlab/pagination_delegate.rb +++ /dev/null @@ -1,65 +0,0 @@ -module Gitlab - class PaginationDelegate - DEFAULT_PER_PAGE = Kaminari.config.default_per_page - MAX_PER_PAGE = Kaminari.config.max_per_page - - def initialize(page:, per_page:, count:, options: {}) - @count = count - @options = { default_per_page: DEFAULT_PER_PAGE, - max_per_page: MAX_PER_PAGE }.merge(options) - - @per_page = sanitize_per_page(per_page) - @page = sanitize_page(page) - end - - def total_count - @count - end - - def total_pages - (total_count.to_f / @per_page).ceil - end - - def next_page - current_page + 1 unless last_page? - end - - def prev_page - current_page - 1 unless first_page? - end - - def current_page - @page - end - - def limit_value - @per_page - end - - def first_page? - current_page == 1 - end - - def last_page? - current_page >= total_pages - end - - def offset - (current_page - 1) * limit_value - end - - private - - def sanitize_per_page(per_page) - return @options[:default_per_page] unless per_page && per_page > 0 - - [@options[:max_per_page], per_page].min - end - - def sanitize_page(page) - return 1 unless page && page > 1 - - [total_pages, page].min - end - end -end diff --git a/spec/lib/gitlab/pagination_delegate_spec.rb b/spec/lib/gitlab/pagination_delegate_spec.rb deleted file mode 100644 index 3220d611274..00000000000 --- a/spec/lib/gitlab/pagination_delegate_spec.rb +++ /dev/null @@ -1,155 +0,0 @@ -require 'spec_helper' - -describe Gitlab::PaginationDelegate, lib: true do - context 'no data' do - let(:delegate) do - described_class.new(page: 1, - per_page: 10, - count: 0) - end - - it 'shows the correct total count' do - expect(delegate.total_count).to eq(0) - end - - it 'shows the correct total pages' do - expect(delegate.total_pages).to eq(0) - end - - it 'shows the correct next page' do - expect(delegate.next_page).to be_nil - end - - it 'shows the correct previous page' do - expect(delegate.prev_page).to be_nil - end - - it 'shows the correct current page' do - expect(delegate.current_page).to eq(1) - end - - it 'shows the correct limit value' do - expect(delegate.limit_value).to eq(10) - end - - it 'shows the correct first page' do - expect(delegate.first_page?).to be true - end - - it 'shows the correct last page' do - expect(delegate.last_page?).to be true - end - - it 'shows the correct offset' do - expect(delegate.offset).to eq(0) - end - end - - context 'with data' do - let(:delegate) do - described_class.new(page: 5, - per_page: 100, - count: 1000) - end - - it 'shows the correct total count' do - expect(delegate.total_count).to eq(1000) - end - - it 'shows the correct total pages' do - expect(delegate.total_pages).to eq(10) - end - - it 'shows the correct next page' do - expect(delegate.next_page).to eq(6) - end - - it 'shows the correct previous page' do - expect(delegate.prev_page).to eq(4) - end - - it 'shows the correct current page' do - expect(delegate.current_page).to eq(5) - end - - it 'shows the correct limit value' do - expect(delegate.limit_value).to eq(100) - end - - it 'shows the correct first page' do - expect(delegate.first_page?).to be false - end - - it 'shows the correct last page' do - expect(delegate.last_page?).to be false - end - - it 'shows the correct offset' do - expect(delegate.offset).to eq(400) - end - end - - context 'last page' do - let(:delegate) do - described_class.new(page: 10, - per_page: 100, - count: 1000) - end - - it 'shows the correct total count' do - expect(delegate.total_count).to eq(1000) - end - - it 'shows the correct total pages' do - expect(delegate.total_pages).to eq(10) - end - - it 'shows the correct next page' do - expect(delegate.next_page).to be_nil - end - - it 'shows the correct previous page' do - expect(delegate.prev_page).to eq(9) - end - - it 'shows the correct current page' do - expect(delegate.current_page).to eq(10) - end - - it 'shows the correct limit value' do - expect(delegate.limit_value).to eq(100) - end - - it 'shows the correct first page' do - expect(delegate.first_page?).to be false - end - - it 'shows the correct last page' do - expect(delegate.last_page?).to be true - end - - it 'shows the correct offset' do - expect(delegate.offset).to eq(900) - end - end - - context 'limits and defaults' do - it 'has a maximum limit per page' do - expect(described_class.new(page: nil, - per_page: 1000, - count: 0).limit_value).to eq(described_class::MAX_PER_PAGE) - end - - it 'has a default per page' do - expect(described_class.new(page: nil, - per_page: nil, - count: 0).limit_value).to eq(described_class::DEFAULT_PER_PAGE) - end - - it 'has a maximum page' do - expect(described_class.new(page: 100, - per_page: 10, - count: 1).current_page).to eq(1) - end - end -end diff --git a/spec/requests/api/users_spec.rb.rej b/spec/requests/api/users_spec.rb.rej deleted file mode 100644 index f7ade32ce42..00000000000 --- a/spec/requests/api/users_spec.rb.rej +++ /dev/null @@ -1,124 +0,0 @@ -diff a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb (rejected hunks) -@@ -1,12 +1,12 @@ - require 'spec_helper' - --describe API::Users, api: true do -+describe API::Users, api: true do - include ApiHelpers - -- let(:user) { create(:user) } -+ let(:user) { create(:user) } - let(:admin) { create(:admin) } -- let(:key) { create(:key, user: user) } -- let(:email) { create(:email, user: user) } -+ let(:key) { create(:key, user: user) } -+ let(:email) { create(:email, user: user) } - let(:omniauth_user) { create(:omniauth_user) } - let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') } - let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } -@@ -827,7 +827,7 @@ describe API::Users, api: true do - user.save - expect do - delete api("/user/keys/#{key.id}", user) -- end.to change{user.keys.count}.by(-1) -+ end.to change { user.keys.count }.by(-1) - expect(response).to have_http_status(200) - end - -@@ -931,7 +931,7 @@ describe API::Users, api: true do - user.save - expect do - delete api("/user/emails/#{email.id}", user) -- end.to change{user.emails.count}.by(-1) -+ end.to change { user.emails.count }.by(-1) - expect(response).to have_http_status(200) - end - -@@ -984,7 +984,7 @@ describe API::Users, api: true do - end - - describe 'PUT /users/:id/unblock' do -- let(:blocked_user) { create(:user, state: 'blocked') } -+ let(:blocked_user) { create(:user, state: 'blocked') } - before { admin } - - it 'unblocks existing user' do -@@ -1100,4 +1100,78 @@ describe API::Users, api: true do - expect(json_response['message']).to eq('404 User Not Found') - end - end -+ -+ context "user activities", :redis do -+ it_behaves_like 'a paginated resources' do -+ let(:request) { get api("/user/activities", admin) } -+ end -+ -+ context 'last activity as normal user' do -+ it 'has no permission' do -+ user.record_activity -+ -+ get api("/user/activities", user) -+ -+ expect(response).to have_http_status(403) -+ end -+ end -+ -+ context 'last activity as admin' do -+ it 'returns the last activity' do -+ allow(Time).to receive(:now).and_return(Time.new(2000, 1, 1)) -+ -+ user.record_activity -+ -+ get api("/user/activities", admin) -+ -+ activity = json_response.last -+ -+ expect(activity['username']).to eq(user.username) -+ expect(activity['last_activity_at']).to eq('2000-01-01 00:00:00') -+ end -+ end -+ -+ context 'last activities paginated', :redis do -+ let(:activity) { json_response.first } -+ let(:old_date) { 2.months.ago.to_date } -+ -+ before do -+ 5.times do |num| -+ Timecop.freeze(old_date + num) -+ -+ create(:user, username: num.to_s).record_activity -+ end -+ end -+ -+ after do -+ Timecop.return -+ end -+ -+ it 'returns 3 activities' do -+ get api("/user/activities?page=1&per_page=3", admin) -+ -+ expect(json_response.count).to eq(3) -+ end -+ -+ it 'contains the first activities' do -+ get api("/user/activities?page=1&per_page=3", admin) -+ -+ expect(json_response.map { |activity| activity['username'] }).to eq(%w[0 1 2]) -+ end -+ -+ it 'contains the last activities' do -+ get api("/user/activities?page=2&per_page=3", admin) -+ -+ expect(json_response.map { |activity| activity['username'] }).to eq(%w[3 4]) -+ end -+ -+ it 'contains activities created after user 3 was created' do -+ from = (old_date + 3).to_s("%Y-%m-%d") -+ -+ get api("/user/activities?page=1&per_page=5&from=#{from}", admin) -+ -+ expect(json_response.map { |activity| activity['username'] }).to eq(%w[3 4]) -+ end -+ end -+ end - end From fce5738e9f2511effb14114389a0c0e1c880b88c Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 12 Apr 2017 16:13:48 +0100 Subject: [PATCH 072/168] Don't use `send` in settings initializer --- config/initializers/1_settings.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 2ccf9e45111..87bf48a3dcd 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -223,7 +223,7 @@ end Settings.gitlab['time_zone'] ||= nil Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil? Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? -Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) +Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} @@ -236,7 +236,7 @@ Settings.gitlab.default_projects_features['wiki'] = true if Settin Settings.gitlab.default_projects_features['snippets'] = true if Settings.gitlab.default_projects_features['snippets'].nil? Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil? Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil? -Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) +Settings.gitlab.default_projects_features['visibility_level'] = Settings.__send__(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['domain_whitelist'] ||= [] Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project gitea] Settings.gitlab['trusted_proxies'] ||= [] @@ -250,7 +250,7 @@ Settings.gitlab_ci['shared_runners_enabled'] = true if Settings.gitlab_ci['share Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_broken_builds'].nil? Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil? Settings.gitlab_ci['builds_path'] = Settings.absolute(Settings.gitlab_ci['builds_path'] || "builds/") -Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url) +Settings.gitlab_ci['url'] ||= Settings.__send__(:build_gitlab_ci_url) # # Reply by email @@ -289,7 +289,7 @@ Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['host'] ||= "example.com" Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" -Settings.pages['url'] ||= Settings.send(:build_pages_url) +Settings.pages['url'] ||= Settings.__send__(:build_pages_url) Settings.pages['external_http'] ||= false unless Settings.pages['external_http'].present? Settings.pages['external_https'] ||= false unless Settings.pages['external_https'].present? @@ -364,7 +364,7 @@ Settings.cron_jobs['stuck_import_jobs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_import_jobs_worker']['cron'] ||= '15 * * * *' Settings.cron_jobs['stuck_import_jobs_worker']['job_class'] = 'StuckImportJobsWorker' Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.send(:cron_random_weekly_time) +Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_random_weekly_time) Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker' # Every day at 00:30 @@ -385,7 +385,7 @@ Settings.gitlab_shell['ssh_host'] ||= Settings.gitlab.ssh_host Settings.gitlab_shell['ssh_port'] ||= 22 Settings.gitlab_shell['ssh_user'] ||= Settings.gitlab.user Settings.gitlab_shell['owner_group'] ||= Settings.gitlab.user -Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_ssh_path_prefix) +Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.__send__(:build_gitlab_shell_ssh_path_prefix) # # Repositories From a4e2a84f990641f7b067adf16261928084dac5f9 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 12 Apr 2017 16:16:13 +0100 Subject: [PATCH 073/168] Make downtime_check happy --- .../20160713222618_add_usage_ping_to_application_settings.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/migrate/20160713222618_add_usage_ping_to_application_settings.rb b/db/migrate/20160713222618_add_usage_ping_to_application_settings.rb index c7c5cdf7a56..a7f76cc626e 100644 --- a/db/migrate/20160713222618_add_usage_ping_to_application_settings.rb +++ b/db/migrate/20160713222618_add_usage_ping_to_application_settings.rb @@ -1,6 +1,8 @@ class AddUsagePingToApplicationSettings < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers + DOWNTIME = false + def change add_column :application_settings, :usage_ping_enabled, :boolean, default: true, null: false end From 73a8caf13bd743b1670377763bc1ed3c5a795ee3 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 12 Apr 2017 18:26:30 +0100 Subject: [PATCH 074/168] Fix method visibility --- app/models/user.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 34a5f3c9202..457ba05fb04 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -949,8 +949,6 @@ class User < ActiveRecord::Base end end - private - def access_level=(new_level) new_level = new_level.to_s return unless %w(admin regular).include?(new_level) From b5d9d9e8981bb511e101b46cf6f33371f80863f5 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 12 Apr 2017 21:40:32 +0100 Subject: [PATCH 075/168] Remove Geo references --- app/workers/schedule_update_user_activity_worker.rb | 2 -- app/workers/update_user_activity_worker.rb | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/workers/schedule_update_user_activity_worker.rb b/app/workers/schedule_update_user_activity_worker.rb index f1adae653b1..6c2c3e437f3 100644 --- a/app/workers/schedule_update_user_activity_worker.rb +++ b/app/workers/schedule_update_user_activity_worker.rb @@ -3,8 +3,6 @@ class ScheduleUpdateUserActivityWorker include CronjobQueue def perform(batch_size = 500) - return if Gitlab::Geo.secondary? - Gitlab::UserActivities.new.each_slice(batch_size) do |batch| UpdateUserActivityWorker.perform_async(Hash[batch]) end diff --git a/app/workers/update_user_activity_worker.rb b/app/workers/update_user_activity_worker.rb index 9f48eb46393..b3c2f13aa33 100644 --- a/app/workers/update_user_activity_worker.rb +++ b/app/workers/update_user_activity_worker.rb @@ -3,8 +3,6 @@ class UpdateUserActivityWorker include DedicatedSidekiqQueue def perform(pairs) - return if Gitlab::Geo.secondary? - pairs = cast_data(pairs) ids = pairs.keys conditions = 'WHEN id = ? THEN ? ' * ids.length From 4b1e25faae5037e62fc9534ef4925e55f42dc9e1 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 12 Apr 2017 21:40:51 +0100 Subject: [PATCH 076/168] Fix git HTTP spec --- spec/requests/git_http_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 9f2857ce2e7..d4de1b339ec 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -159,7 +159,7 @@ describe 'Git HTTP requests', lib: true do end it 'updates the user last activity' do - download(path, env) do |response| + download(path, {}) do |response| expect(user.reload.last_activity_at).not_to be_nil end end From 41b71efd51f7c645ba83099ba53efcba9d5b5660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 13 Apr 2017 14:59:35 +0200 Subject: [PATCH 077/168] Fix `last_activity_at` to `last_activity_on` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/requests/git_http_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index d4de1b339ec..7124a3caff3 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -160,7 +160,7 @@ describe 'Git HTTP requests', lib: true do it 'updates the user last activity' do download(path, {}) do |response| - expect(user.reload.last_activity_at).not_to be_nil + expect(user.reload.last_activity_on).not_to be_nil end end end From 115d1afeb386d2b1fef4ca437fafd8227d72c0ad Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 7 Apr 2017 17:29:08 +0100 Subject: [PATCH 078/168] Only add newlines for multiple uploads --- app/assets/javascripts/dropzone_input.js | 10 +++++++--- .../remove-double-newline-for-single-attachments.yml | 4 ++++ spec/features/issues_spec.rb | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/remove-double-newline-for-single-attachments.yml diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index f2963a5eb19..df0e3f46827 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -66,7 +66,10 @@ window.DropzoneInput = (function() { form_textarea.focus(); }, success: function(header, response) { - pasteText(response.link.markdown); + const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length; + const shouldPad = processingFileCount >= 1; + + pasteText(response.link.markdown, shouldPad); }, error: function(temp) { var checkIfMsgExists, errorAlert; @@ -123,9 +126,10 @@ window.DropzoneInput = (function() { } return false; }; - pasteText = function(text) { + pasteText = function(text, shouldPad) { var afterSelection, beforeSelection, caretEnd, caretStart, textEnd; - var formattedText = text + "\n\n"; + var formattedText = text; + if (shouldPad) formattedText += "\n\n"; caretStart = $(child)[0].selectionStart; caretEnd = $(child)[0].selectionEnd; textEnd = $(child).val().length; diff --git a/changelogs/unreleased/remove-double-newline-for-single-attachments.yml b/changelogs/unreleased/remove-double-newline-for-single-attachments.yml new file mode 100644 index 00000000000..98a28e1ede1 --- /dev/null +++ b/changelogs/unreleased/remove-double-newline-for-single-attachments.yml @@ -0,0 +1,4 @@ +--- +title: Only add newlines between multiple uploads +merge_request: 10545 +author: diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index e3213d24f6a..362d167befa 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -601,10 +601,10 @@ describe 'Issues', feature: true do expect(page.find_field("issue_description").value).to have_content 'banana_sample' end - it 'adds double newline to end of attachment markdown' do + it "doesn't add double newline to end of a single attachment markdown" do dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - expect(page.find_field("issue_description").value).to match /\n\n$/ + expect(page.find_field("issue_description").value).not_to match /\n\n$/ end end From 0636320ea67849023973a7eb12f566cefcf7e6ff Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sat, 8 Apr 2017 09:44:03 +0100 Subject: [PATCH 079/168] Add expirations to CA and user callouts --- .../javascripts/cycle_analytics/cycle_analytics_bundle.js | 2 +- app/assets/javascripts/user_callout.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index b099b39e58f..48cab437e02 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -125,7 +125,7 @@ $(() => { }, dismissOverviewDialog() { this.isOverviewDialogDismissed = true; - Cookies.set(OVERVIEW_DIALOG_COOKIE, '1'); + Cookies.set(OVERVIEW_DIALOG_COOKIE, '1', { expires: 365 }); }, }, }); diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js index fa078b48bf8..b9d57cbcad4 100644 --- a/app/assets/javascripts/user_callout.js +++ b/app/assets/javascripts/user_callout.js @@ -18,7 +18,7 @@ export default class UserCallout { dismissCallout(e) { const $currentTarget = $(e.currentTarget); - Cookies.set(USER_CALLOUT_COOKIE, 'true'); + Cookies.set(USER_CALLOUT_COOKIE, 'true', { expires: 365 }); if ($currentTarget.hasClass('close')) { this.userCalloutBody.remove(); From 9bb9cbfd937a17056c598862d6617724eae86948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 14 Apr 2017 17:32:09 +0200 Subject: [PATCH 080/168] Use a proper matcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/requests/git_http_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 7124a3caff3..069ff4bdaa3 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -3,6 +3,7 @@ require "spec_helper" describe 'Git HTTP requests', lib: true do include GitHttpHelpers include WorkhorseHelpers + include UserActivitiesHelpers it "gives WWW-Authenticate hints" do clone_get('doesnt/exist.git') @@ -159,8 +160,9 @@ describe 'Git HTTP requests', lib: true do end it 'updates the user last activity' do + expect(user_activity(user)).to be_nil download(path, {}) do |response| - expect(user.reload.last_activity_on).not_to be_nil + expect(user_activity(user)).to be_present end end end From 35e260b9d19d900a25284d8ec6d3e4a59dadce42 Mon Sep 17 00:00:00 2001 From: winniehell Date: Fri, 14 Apr 2017 21:16:04 +0200 Subject: [PATCH 081/168] Move labels of search results from bottom to title (!10705) --- app/views/search/results/_issue.html.haml | 5 ++--- app/views/search/results/_merge_request.html.haml | 9 ++++----- changelogs/unreleased/move-search-labels.yml | 4 ++++ 3 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/move-search-labels.yml diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index e010f21de5a..fc4385865a4 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -3,6 +3,8 @@ = confidential_icon(issue) = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do %span.term.str-truncated= issue.title + - if issue.closed? + %span.label.label-danger.prepend-left-5 Closed .pull-right ##{issue.iid} - if issue.description.present? .description.term @@ -10,6 +12,3 @@ = search_md_sanitize(issue, :description) %span.light #{issue.project.name_with_namespace} - - if issue.closed? - .pull-right - %span.label.label-danger Closed diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 2e6adf3027c..9b583285d02 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -2,6 +2,10 @@ %h4 = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do %span.term.str-truncated= merge_request.title + - if merge_request.merged? + %span.label.label-primary.prepend-left-5 Merged + - elsif merge_request.closed? + %span.label.label-danger.prepend-left-5 Closed .pull-right= merge_request.to_reference - if merge_request.description.present? .description.term @@ -9,8 +13,3 @@ = search_md_sanitize(merge_request, :description) %span.light #{merge_request.project.name_with_namespace} - .pull-right - - if merge_request.merged? - %span.label.label-primary Merged - - elsif merge_request.closed? - %span.label.label-danger Closed diff --git a/changelogs/unreleased/move-search-labels.yml b/changelogs/unreleased/move-search-labels.yml new file mode 100644 index 00000000000..3a1d23d622e --- /dev/null +++ b/changelogs/unreleased/move-search-labels.yml @@ -0,0 +1,4 @@ +--- +title: Move labels of search results from bottom to title +merge_request: 10705 +author: dr From 3468dc387ed705a6cf5a33479bce7058d9b7889e Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 14 Apr 2017 16:00:51 -0500 Subject: [PATCH 082/168] Fix spacing of nav header --- app/assets/stylesheets/framework/header.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index abb092623c0..2071d4dcfce 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -155,7 +155,7 @@ header { .header-logo { display: inline-block; - margin: 0 7px 0 2px; + margin: 0 12px 0 2px; position: relative; top: 10px; transition-duration: .3s; @@ -186,7 +186,7 @@ header { display: flex; align-items: flex-start; flex: 1 1 auto; - padding-top: (($header-height - 19) / 2); + padding-top: 14px; overflow: hidden; } From e7c9aa6030312efac89de1c5a4afae35ef47f330 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Thu, 16 Mar 2017 15:46:56 -0700 Subject: [PATCH 083/168] 29595 Customize experience callout design --- app/assets/stylesheets/pages/profile.scss | 59 +++++++++++++++---- app/views/shared/_user_callout.html.haml | 19 +++--- .../29595-customize-experience-callout.yml | 4 ++ 3 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 changelogs/unreleased/29595-customize-experience-callout.yml diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 8c6dd392865..a8dad58b1d3 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -289,8 +289,12 @@ table.u2f-registrations { margin: 0 auto; .bordered-box { - border: 1px solid $border-color; + border: 1px solid $blue-300; border-radius: $border-radius-default; + background-color: lighten($blue-50, 3%); + position: relative; + display: flex; + justify-content: center; } .landing { @@ -298,28 +302,59 @@ table.u2f-registrations { margin-bottom: $gl-padding; .close { - margin-right: 20px; - } + position: absolute; + right: 20px; + opacity: 1; - .dismiss-icon { - float: right; - cursor: pointer; - color: $cycle-analytics-dismiss-icon-color; + .dismiss-icon { + float: right; + cursor: pointer; + color: $blue-300; + } + + &:hover { + background-color: transparent; + border: 0; + + .dismiss-icon { + color: $blue-400; + } + } } .svg-container { - text-align: center; + margin-right: 30px; + display: inline-block; svg { - width: 136px; - height: 136px; + height: 110px; + vertical-align: top; } } + + .user-callout-copy { + display: inline-block; + vertical-align: top; + } } @media(max-width: $screen-xs-max) { - .inner-content { - padding-left: 30px; + text-align: center; + + .bordered-box { + display: block; + } + + .landing { + .svg-container, + .user-callout-copy { + margin: 0; + display: block; + + svg { + height: 75px; + } + } } } } diff --git a/app/views/shared/_user_callout.html.haml b/app/views/shared/_user_callout.html.haml index 8f1293adcb1..108643f2f6f 100644 --- a/app/views/shared/_user_callout.html.haml +++ b/app/views/shared/_user_callout.html.haml @@ -1,14 +1,13 @@ .user-callout .bordered-box.landing.content-block - %button.btn.btn-default.close.js-close-callout{ type: 'button', + %button.btn.btn-default.close.close-user-callout{ type: 'button', 'aria-label' => 'Dismiss customize experience box' } = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') - .row - .col-sm-3.col-xs-12.svg-container - = custom_icon('icon_customization') - .col-sm-8.col-xs-12.inner-content - %h4 - Customize your experience - %p - Change syntax themes, default project pages, and more in preferences. - = link_to 'Check it out', profile_preferences_path, class: 'btn btn-default js-close-callout' + .svg-container + = custom_icon('icon_customization') + .user-callout-copy + %h4 + Customize your experience + %p + Change syntax themes, default project pages, and more in preferences. + = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary user-callout-btn' diff --git a/changelogs/unreleased/29595-customize-experience-callout.yml b/changelogs/unreleased/29595-customize-experience-callout.yml new file mode 100644 index 00000000000..ec8393142c6 --- /dev/null +++ b/changelogs/unreleased/29595-customize-experience-callout.yml @@ -0,0 +1,4 @@ +--- +title: 29595 Update callout design +merge_request: +author: From 6c055805ea9b0ffeb56d4bc0bb87f16a139167ad Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Thu, 13 Apr 2017 16:43:07 -0500 Subject: [PATCH 084/168] Fixed tests --- app/views/shared/_user_callout.html.haml | 4 ++-- spec/javascripts/user_callout_spec.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/shared/_user_callout.html.haml b/app/views/shared/_user_callout.html.haml index 108643f2f6f..8308baa7829 100644 --- a/app/views/shared/_user_callout.html.haml +++ b/app/views/shared/_user_callout.html.haml @@ -1,6 +1,6 @@ .user-callout .bordered-box.landing.content-block - %button.btn.btn-default.close.close-user-callout{ type: 'button', + %button.btn.btn-default.close.js-close-callout{ type: 'button', 'aria-label' => 'Dismiss customize experience box' } = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') .svg-container @@ -10,4 +10,4 @@ Customize your experience %p Change syntax themes, default project pages, and more in preferences. - = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary user-callout-btn' + = link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout' diff --git a/spec/javascripts/user_callout_spec.js b/spec/javascripts/user_callout_spec.js index c0375ebc61c..28d0c7dcd99 100644 --- a/spec/javascripts/user_callout_spec.js +++ b/spec/javascripts/user_callout_spec.js @@ -14,7 +14,6 @@ describe('UserCallout', function () { this.userCallout = new UserCallout(); this.closeButton = $('.js-close-callout.close'); this.userCalloutBtn = $('.js-close-callout:not(.close)'); - this.userCalloutContainer = $('.user-callout'); }); it('hides when user clicks on the dismiss-icon', (done) => { From 3545af219b84845b075534ae5f9255f27cf92b89 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Fri, 14 Apr 2017 14:33:56 -0700 Subject: [PATCH 085/168] Remove lighten blue and add blue-25 for background --- app/assets/stylesheets/pages/profile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index a8dad58b1d3..fe084eb9397 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -291,7 +291,7 @@ table.u2f-registrations { .bordered-box { border: 1px solid $blue-300; border-radius: $border-radius-default; - background-color: lighten($blue-50, 3%); + background-color: $blue-25; position: relative; display: flex; justify-content: center; From 00dd2ba95e9607ce4a1cd1a19aecee9b3e083768 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Apr 2017 14:54:36 -0700 Subject: [PATCH 086/168] Turn on caching of classes in Knapsack specs Enabling caching of classes slows start-up time because all controllers are loaded at initialization, but it reduces memory and load because files are not reloaded with every request. For example, caching is not necessary for loading database migrations but useful for handling Knapsack specs. Addresses gitlab-org/gitlab-ee#2162 --- .gitlab-ci.yml | 2 ++ config/environments/test.rb | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e6fcab3808..d84725b202a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,6 +67,7 @@ stages: - export CI_NODE_TOTAL=${JOB_NAME[2]} - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true + - export CACHE_CLASSES=true - cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - knapsack rspec "--color --format documentation" artifacts: @@ -87,6 +88,7 @@ stages: - export CI_NODE_TOTAL=${JOB_NAME[2]} - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true + - export CACHE_CLASSES=true - cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' artifacts: diff --git a/config/environments/test.rb b/config/environments/test.rb index a25c5016a3b..c3b788c038e 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -8,7 +8,12 @@ Rails.application.configure do # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! - config.cache_classes = false + + # Enabling caching of classes slows start-up time because all controllers + # are loaded at initalization, but it reduces memory and load because files + # are not reloaded with every request. For example, caching is not necessary + # for loading database migrations but useful for handling Knapsack specs. + config.cache_classes = ENV['CACHE_CLASSES'] == 'true' # Configure static asset server for tests with Cache-Control for performance config.assets.digest = false From e89d4741d38bdbb645d5bf92cfdac5d66e8438b0 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Apr 2017 06:04:15 -0700 Subject: [PATCH 087/168] Fix regression in rendering Markdown references that do not exist Closes #30972 --- lib/banzai/reference_parser/base_parser.rb | 3 ++- .../reference_parser/base_parser_spec.rb | 23 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index dabf71d6aeb..c2503fa2adc 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -136,7 +136,8 @@ module Banzai nodes.each_with_object({}) do |node, hash| if node.has_attribute?(attribute) - hash[node] = objects_by_id[node.attr(attribute).to_i] + obj = objects_by_id[node.attr(attribute).to_i] + hash[node] = obj if obj end end end diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index a3141894c74..d5746107ee1 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -114,8 +114,27 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do expect(hash).to eq({ link => user }) end - it 'returns an empty Hash when the list of nodes is empty' do - expect(subject.grouped_objects_for_nodes([], User, 'data-user')).to eq({}) + it 'returns an empty Hash when entry does not exist in the database' do + link = double(:link) + + expect(link).to receive(:has_attribute?). + with('data-user'). + and_return(true) + + expect(link).to receive(:attr). + with('data-user'). + and_return('1') + + nodes = [link] + bad_id = user.id + 100 + + expect(subject).to receive(:unique_attribute_values). + with(nodes, 'data-user'). + and_return([bad_id.to_s]) + + hash = subject.grouped_objects_for_nodes(nodes, User, 'data-user') + + expect(hash).to eq({}) end end From 8918433af66b77ab101bd5ba3b4a3c716be0f864 Mon Sep 17 00:00:00 2001 From: Regis Boudinot Date: Sat, 15 Apr 2017 15:11:04 +0000 Subject: [PATCH 088/168] Issue Title Vue: convert to .vue - use 'render' with 'createElement' in index --- app/assets/javascripts/issue_show/index.js | 36 ++++++++----------- .../{issue_title.js => issue_title.vue} | 9 +++-- app/views/projects/issues/show.html.haml | 1 + config/webpack.config.js | 1 + .../issue_show/issue_title_spec.js | 2 +- 5 files changed, 24 insertions(+), 25 deletions(-) rename app/assets/javascripts/issue_show/{issue_title.js => issue_title.vue} (95%) diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js index b6ce8e83729..4d491e70d83 100644 --- a/app/assets/javascripts/issue_show/index.js +++ b/app/assets/javascripts/issue_show/index.js @@ -1,26 +1,20 @@ import Vue from 'vue'; -import IssueTitle from './issue_title'; +import IssueTitle from './issue_title.vue'; import '../vue_shared/vue_resource_interceptor'; -const vueOptions = () => ({ - el: '.issue-title-entrypoint', - components: { - IssueTitle, - }, - data() { - const issueTitleData = document.querySelector('.issue-title-data').dataset; +(() => { + const issueTitleData = document.querySelector('.issue-title-data').dataset; + const { initialTitle, endpoint } = issueTitleData; - return { - initialTitle: issueTitleData.initialTitle, - endpoint: issueTitleData.endpoint, - }; - }, - template: ` - - `, -}); + const vm = new Vue({ + el: '.issue-title-entrypoint', + render: createElement => createElement(IssueTitle, { + props: { + initialTitle, + endpoint, + }, + }), + }); -(() => new Vue(vueOptions()))(); + return vm; +})(); diff --git a/app/assets/javascripts/issue_show/issue_title.js b/app/assets/javascripts/issue_show/issue_title.vue similarity index 95% rename from app/assets/javascripts/issue_show/issue_title.js rename to app/assets/javascripts/issue_show/issue_title.vue index 1184c8956dc..ba54178a310 100644 --- a/app/assets/javascripts/issue_show/issue_title.js +++ b/app/assets/javascripts/issue_show/issue_title.vue @@ -1,3 +1,4 @@ + + + diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 885795ccb5c..fcbd8829595 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -79,4 +79,5 @@ = render 'shared/issuable/sidebar', issuable: @issue += page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('issue_show') diff --git a/config/webpack.config.js b/config/webpack.config.js index ffb16190093..64a04dc342e 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -125,6 +125,7 @@ var config = { 'notebook_viewer', 'pdf_viewer', 'vue_pipelines', + 'issue_show', ], minChunks: function(module, count) { return module.resource && (/vue_shared/).test(module.resource); diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_spec.js index 806d728a874..03edbf9f947 100644 --- a/spec/javascripts/issue_show/issue_title_spec.js +++ b/spec/javascripts/issue_show/issue_title_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import issueTitle from '~/issue_show/issue_title'; +import issueTitle from '~/issue_show/issue_title.vue'; describe('Issue Title', () => { let IssueTitleComponent; From de62d3b48c45030b91113e61bae0f3a2f24797e9 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Sun, 16 Apr 2017 11:42:08 +0200 Subject: [PATCH 089/168] Expand/collapse button -> Change to make it look like a toggle Changed the expand/collapse button to look different when is opened/closed --- app/assets/javascripts/behaviors/toggler_behavior.js | 1 + app/assets/stylesheets/pages/commits.scss | 7 ++++++- changelogs/unreleased/28575-expand-collapse-look.yml | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/28575-expand-collapse-look.yml diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 4c9ad128e6c..77e92ff8caf 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -22,6 +22,7 @@ $(() => { } $('body').on('click', '.js-toggle-button', function toggleButton(e) { + e.target.classList.toggle('open'); toggleContainer($(this).closest('.js-toggle-container')); const targetTag = e.currentTarget.tagName.toLowerCase(); diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 0dad91ba128..9e3142c8aa3 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -135,7 +135,7 @@ .text-expander { display: inline-block; - background: $gray-light; + background: $white-light; color: $gl-text-color-secondary; padding: 0 5px; cursor: pointer; @@ -146,6 +146,11 @@ line-height: $gl-font-size; outline: none; + &.open { + background: $gray-light; + box-shadow: inset 0 0 2px rgba($black, 0.2); + } + &:hover { background-color: darken($gray-light, 10%); text-decoration: none; diff --git a/changelogs/unreleased/28575-expand-collapse-look.yml b/changelogs/unreleased/28575-expand-collapse-look.yml new file mode 100644 index 00000000000..d8943316300 --- /dev/null +++ b/changelogs/unreleased/28575-expand-collapse-look.yml @@ -0,0 +1,4 @@ +--- +title: Expand/collapse button -> Change to make it look like a toggle +merge_request: 10720 +author: Jacopo Beschi @jacopo-beschi From 60eee739f040e997d6dac3f4e21ef90daa6c7f30 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 16 Apr 2017 08:21:40 -0700 Subject: [PATCH 090/168] Hard delete users' associated records deleted from AbuseReports In the case of spammers, we really want a hard delete to avoid retaining spam. Closes #31021 --- app/models/abuse_report.rb | 2 +- app/services/users/destroy_service.rb | 2 +- spec/models/abuse_report_spec.rb | 3 ++- spec/services/users/destroy_service_spec.rb | 6 ++++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index 2340453831e..0d7c2d20029 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -16,7 +16,7 @@ class AbuseReport < ActiveRecord::Base def remove_user(deleted_by:) user.block - DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true) + DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true, hard_delete: true) end def notify diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index ba58b174cc0..9eb6a600f6b 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -26,7 +26,7 @@ module Users ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute end - MigrateToGhostUserService.new(user).execute + MigrateToGhostUserService.new(user).execute unless options[:hard_delete] # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing namespace = user.namespace diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 4e71597521d..ced93c8f762 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -29,7 +29,8 @@ RSpec.describe AbuseReport, type: :model do it 'lets a worker delete the user' do expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id, - delete_solo_owned_groups: true) + delete_solo_owned_groups: true, + hard_delete: true) subject.remove_user(deleted_by: user) end diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 43c18992d1a..4bc30018ebd 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -152,6 +152,12 @@ describe Users::DestroyService, services: true do service.execute(user) end + + it 'does not run `MigrateToGhostUser` if hard_delete option is given' do + expect_any_instance_of(Users::MigrateToGhostUserService).not_to receive(:execute) + + service.execute(user, hard_delete: true) + end end end end From ee3b0c3a9ad0823f86b5c798c47c343a812228d0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Apr 2017 14:30:42 +0800 Subject: [PATCH 091/168] Make sure we're giving Encoding.default_external --- lib/gitlab/ci/trace/stream.rb | 4 ++-- spec/lib/gitlab/ci/trace/stream_spec.rb | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index 3b335cdfd01..33141d0d88d 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -14,6 +14,7 @@ module Gitlab def initialize @stream = yield + @stream.binmode end def valid? @@ -51,7 +52,7 @@ module Gitlab read_last_lines(last_lines) else stream.read - end + end.force_encoding(Encoding.default_external) end def html_with_state(state = nil) @@ -113,7 +114,6 @@ module Gitlab end chunks.join.lines.last(last_lines).join - .force_encoding(Encoding.default_external) end end end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 9e3bd6d662f..4bbca6d2ea2 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Trace::Stream do describe 'delegates' do - subject { described_class.new { nil } } + subject { described_class.new { StringIO.new } } it { is_expected.to delegate_method(:close).to(:stream) } it { is_expected.to delegate_method(:tell).to(:stream) } @@ -43,13 +43,19 @@ describe Gitlab::Ci::Trace::Stream do it 'forwards to the next linefeed, case 1' do stream.limit(7) - expect(stream.raw).to eq('') + result = stream.raw + + expect(result).to eq('') + expect(result.encoding).to eq(Encoding.default_external) end it 'forwards to the next linefeed, case 2' do stream.limit(29) - expect(stream.raw).to eq("\e[01;32m許功蓋\e[0m\n") + result = stream.raw + + expect(result).to eq("\e[01;32m許功蓋\e[0m\n") + expect(result.encoding).to eq(Encoding.default_external) end end end From e8561287dc9527095ff4d939b0b31705a23c3a90 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 17 Apr 2017 01:35:56 -0500 Subject: [PATCH 092/168] Remove IIFEs from diff_notes_bundle.js --- .../components/comment_resolve_btn.js | 112 +++--- .../components/diff_note_avatars.js | 278 ++++++++------- .../components/jump_to_discussion.js | 334 +++++++++--------- .../components/new_issue_for_discussion.js | 42 ++- .../diff_notes/components/resolve_btn.js | 204 ++++++----- .../diff_notes/components/resolve_count.js | 36 +- .../components/resolve_discussion_btn.js | 98 +++-- .../diff_notes/mixins/discussion.js | 58 ++- .../diff_notes/services/resolve.js | 138 ++++---- .../javascripts/diff_notes/stores/comments.js | 102 +++--- spec/javascripts/diff_comments_store_spec.js | 214 ++++++----- 11 files changed, 797 insertions(+), 819 deletions(-) diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js index eb76b7d15fd..aed7cac4e62 100644 --- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js @@ -3,65 +3,63 @@ import Vue from 'vue'; -(() => { - const CommentAndResolveBtn = Vue.extend({ - props: { - discussionId: String, - }, - data() { - return { - textareaIsEmpty: true, - discussion: {}, - }; - }, - computed: { - showButton: function () { - if (this.discussion) { - return this.discussion.isResolvable(); - } else { - return false; - } - }, - isDiscussionResolved: function () { - return this.discussion.isResolved(); - }, - buttonText: function () { - if (this.isDiscussionResolved) { - if (this.textareaIsEmpty) { - return "Unresolve discussion"; - } else { - return "Comment & unresolve discussion"; - } - } else { - if (this.textareaIsEmpty) { - return "Resolve discussion"; - } else { - return "Comment & resolve discussion"; - } - } +const CommentAndResolveBtn = Vue.extend({ + props: { + discussionId: String, + }, + data() { + return { + textareaIsEmpty: true, + discussion: {}, + }; + }, + computed: { + showButton: function () { + if (this.discussion) { + return this.discussion.isResolvable(); + } else { + return false; } }, - created() { - if (this.discussionId) { - this.discussion = CommentsStore.state[this.discussionId]; + isDiscussionResolved: function () { + return this.discussion.isResolved(); + }, + buttonText: function () { + if (this.isDiscussionResolved) { + if (this.textareaIsEmpty) { + return "Unresolve discussion"; + } else { + return "Comment & unresolve discussion"; + } + } else { + if (this.textareaIsEmpty) { + return "Resolve discussion"; + } else { + return "Comment & resolve discussion"; + } } - }, - mounted: function () { - if (!this.discussionId) return; - - const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`); - this.textareaIsEmpty = $textarea.val() === ''; - - $textarea.on('input.comment-and-resolve-btn', () => { - this.textareaIsEmpty = $textarea.val() === ''; - }); - }, - destroyed: function () { - if (!this.discussionId) return; - - $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn'); } - }); + }, + created() { + if (this.discussionId) { + this.discussion = CommentsStore.state[this.discussionId]; + } + }, + mounted: function () { + if (!this.discussionId) return; - Vue.component('comment-and-resolve-btn', CommentAndResolveBtn); -})(window); + const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`); + this.textareaIsEmpty = $textarea.val() === ''; + + $textarea.on('input.comment-and-resolve-btn', () => { + this.textareaIsEmpty = $textarea.val() === ''; + }); + }, + destroyed: function () { + if (!this.discussionId) return; + + $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn'); + } +}); + +Vue.component('comment-and-resolve-btn', CommentAndResolveBtn); diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js index 0297add94d5..f3a688fbf2f 100644 --- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js +++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js @@ -4,155 +4,153 @@ import Vue from 'vue'; import collapseIcon from '../icons/collapse_icon.svg'; -(() => { - const DiffNoteAvatars = Vue.extend({ - props: ['discussionId'], - data() { - return { - isVisible: false, - lineType: '', - storeState: CommentsStore.state, - shownAvatars: 3, - collapseIcon, - }; - }, - template: ` -

        -
        - - {{ moreText }} -
        - + :title="note.authorName + ': ' + note.noteTruncated" + :src="note.authorAvatar" + @click="clickedAvatar($event)" /> + {{ moreText }}
        - `, - mounted() { + +
+ `, + mounted() { + this.$nextTick(() => { + this.addNoCommentClass(); + this.setDiscussionVisible(); + + this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new'; + }); + + $(document).on('toggle.comments', () => { + this.$nextTick(() => { + this.setDiscussionVisible(); + }); + }); + }, + destroyed() { + $(document).off('toggle.comments'); + }, + watch: { + storeState: { + handler() { + this.$nextTick(() => { + $('.has-tooltip', this.$el).tooltip('fixTitle'); + + // We need to add/remove a class to an element that is outside the Vue instance + this.addNoCommentClass(); + }); + }, + deep: true, + }, + }, + computed: { + notesSubset() { + let notes = []; + + if (this.discussion) { + notes = Object.keys(this.discussion.notes) + .slice(0, this.shownAvatars) + .map(noteId => this.discussion.notes[noteId]); + } + + return notes; + }, + extraNotesTitle() { + if (this.discussion) { + const extra = this.discussion.notesCount() - this.shownAvatars; + + return `${extra} more comment${extra > 1 ? 's' : ''}`; + } + + return ''; + }, + discussion() { + return this.storeState[this.discussionId]; + }, + notesCount() { + if (this.discussion) { + return this.discussion.notesCount(); + } + + return 0; + }, + moreText() { + const plusSign = this.notesCount < 100 ? '+' : ''; + + return `${plusSign}${this.notesCount - this.shownAvatars}`; + }, + }, + methods: { + clickedAvatar(e) { + notes.addDiffNote(e); + + // Toggle the active state of the toggle all button + this.toggleDiscussionsToggleState(); + this.$nextTick(() => { - this.addNoCommentClass(); this.setDiscussionVisible(); - this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new'; - }); - - $(document).on('toggle.comments', () => { - this.$nextTick(() => { - this.setDiscussionVisible(); - }); + $('.has-tooltip', this.$el).tooltip('fixTitle'); + $('.has-tooltip', this.$el).tooltip('hide'); }); }, - destroyed() { - $(document).off('toggle.comments'); + addNoCommentClass() { + const notesCount = this.notesCount; + + $(this.$el).closest('.js-avatar-container') + .toggleClass('js-no-comment-btn', notesCount > 0) + .nextUntil('.js-avatar-container') + .toggleClass('js-no-comment-btn', notesCount > 0); }, - watch: { - storeState: { - handler() { - this.$nextTick(() => { - $('.has-tooltip', this.$el).tooltip('fixTitle'); + toggleDiscussionsToggleState() { + const $notesHolders = $(this.$el).closest('.code').find('.notes_holder'); + const $visibleNotesHolders = $notesHolders.filter(':visible'); + const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments'); - // We need to add/remove a class to an element that is outside the Vue instance - this.addNoCommentClass(); - }); - }, - deep: true, - }, + $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length); }, - computed: { - notesSubset() { - let notes = []; - - if (this.discussion) { - notes = Object.keys(this.discussion.notes) - .slice(0, this.shownAvatars) - .map(noteId => this.discussion.notes[noteId]); - } - - return notes; - }, - extraNotesTitle() { - if (this.discussion) { - const extra = this.discussion.notesCount() - this.shownAvatars; - - return `${extra} more comment${extra > 1 ? 's' : ''}`; - } - - return ''; - }, - discussion() { - return this.storeState[this.discussionId]; - }, - notesCount() { - if (this.discussion) { - return this.discussion.notesCount(); - } - - return 0; - }, - moreText() { - const plusSign = this.notesCount < 100 ? '+' : ''; - - return `${plusSign}${this.notesCount - this.shownAvatars}`; - }, + setDiscussionVisible() { + this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible'); }, - methods: { - clickedAvatar(e) { - notes.addDiffNote(e); + }, +}); - // Toggle the active state of the toggle all button - this.toggleDiscussionsToggleState(); - - this.$nextTick(() => { - this.setDiscussionVisible(); - - $('.has-tooltip', this.$el).tooltip('fixTitle'); - $('.has-tooltip', this.$el).tooltip('hide'); - }); - }, - addNoCommentClass() { - const notesCount = this.notesCount; - - $(this.$el).closest('.js-avatar-container') - .toggleClass('js-no-comment-btn', notesCount > 0) - .nextUntil('.js-avatar-container') - .toggleClass('js-no-comment-btn', notesCount > 0); - }, - toggleDiscussionsToggleState() { - const $notesHolders = $(this.$el).closest('.code').find('.notes_holder'); - const $visibleNotesHolders = $notesHolders.filter(':visible'); - const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments'); - - $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length); - }, - setDiscussionVisible() { - this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible'); - }, - }, - }); - - Vue.component('diff-note-avatars', DiffNoteAvatars); -})(); +Vue.component('diff-note-avatars', DiffNoteAvatars); diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 8edc45130fc..8a0fd3bb4a7 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -4,192 +4,190 @@ import Vue from 'vue'; -(() => { - const JumpToDiscussion = Vue.extend({ - mixins: [DiscussionMixins], - props: { - discussionId: String +const JumpToDiscussion = Vue.extend({ + mixins: [DiscussionMixins], + props: { + discussionId: String + }, + data: function () { + return { + discussions: CommentsStore.state, + discussion: {}, + }; + }, + computed: { + allResolved: function () { + return this.unresolvedDiscussionCount === 0; }, - data: function () { - return { - discussions: CommentsStore.state, - discussion: {}, - }; - }, - computed: { - allResolved: function () { - return this.unresolvedDiscussionCount === 0; - }, - showButton: function () { - if (this.discussionId) { - if (this.unresolvedDiscussionCount > 1) { - return true; - } else { - return this.discussionId !== this.lastResolvedId; - } + showButton: function () { + if (this.discussionId) { + if (this.unresolvedDiscussionCount > 1) { + return true; } else { - return this.unresolvedDiscussionCount >= 1; + return this.discussionId !== this.lastResolvedId; } - }, - lastResolvedId: function () { - let lastId; - for (const discussionId in this.discussions) { - const discussion = this.discussions[discussionId]; - - if (!discussion.isResolved()) { - lastId = discussion.id; - } - } - return lastId; + } else { + return this.unresolvedDiscussionCount >= 1; } }, - methods: { - jumpToNextUnresolvedDiscussion: function () { - let discussionsSelector; - let discussionIdsInScope; - let firstUnresolvedDiscussionId; - let nextUnresolvedDiscussionId; - let activeTab = window.mrTabs.currentAction; - let hasDiscussionsToJumpTo = true; - let jumpToFirstDiscussion = !this.discussionId; + lastResolvedId: function () { + let lastId; + for (const discussionId in this.discussions) { + const discussion = this.discussions[discussionId]; - const discussionIdsForElements = function(elements) { - return elements.map(function() { - return $(this).attr('data-discussion-id'); - }).toArray(); - }; - - const discussions = this.discussions; - - if (activeTab === 'diffs') { - discussionsSelector = '.diffs .notes[data-discussion-id]'; - discussionIdsInScope = discussionIdsForElements($(discussionsSelector)); - - let unresolvedDiscussionCount = 0; - - for (let i = 0; i < discussionIdsInScope.length; i += 1) { - const discussionId = discussionIdsInScope[i]; - const discussion = discussions[discussionId]; - if (discussion && !discussion.isResolved()) { - unresolvedDiscussionCount += 1; - } - } - - if (this.discussionId && !this.discussion.isResolved()) { - // If this is the last unresolved discussion on the diffs tab, - // there are no discussions to jump to. - if (unresolvedDiscussionCount === 1) { - hasDiscussionsToJumpTo = false; - } - } else { - // If there are no unresolved discussions on the diffs tab at all, - // there are no discussions to jump to. - if (unresolvedDiscussionCount === 0) { - hasDiscussionsToJumpTo = false; - } - } - } else if (activeTab !== 'notes') { - // If we are on the commits or builds tabs, - // there are no discussions to jump to. - hasDiscussionsToJumpTo = false; + if (!discussion.isResolved()) { + lastId = discussion.id; } + } + return lastId; + } + }, + methods: { + jumpToNextUnresolvedDiscussion: function () { + let discussionsSelector; + let discussionIdsInScope; + let firstUnresolvedDiscussionId; + let nextUnresolvedDiscussionId; + let activeTab = window.mrTabs.currentAction; + let hasDiscussionsToJumpTo = true; + let jumpToFirstDiscussion = !this.discussionId; - if (!hasDiscussionsToJumpTo) { - // If there are no discussions to jump to on the current page, - // switch to the notes tab and jump to the first disucssion there. - window.mrTabs.activateTab('notes'); - activeTab = 'notes'; - jumpToFirstDiscussion = true; - } + const discussionIdsForElements = function(elements) { + return elements.map(function() { + return $(this).attr('data-discussion-id'); + }).toArray(); + }; - if (activeTab === 'notes') { - discussionsSelector = '.discussion[data-discussion-id]'; - discussionIdsInScope = discussionIdsForElements($(discussionsSelector)); - } + const discussions = this.discussions; + + if (activeTab === 'diffs') { + discussionsSelector = '.diffs .notes[data-discussion-id]'; + discussionIdsInScope = discussionIdsForElements($(discussionsSelector)); + + let unresolvedDiscussionCount = 0; - let currentDiscussionFound = false; for (let i = 0; i < discussionIdsInScope.length; i += 1) { const discussionId = discussionIdsInScope[i]; const discussion = discussions[discussionId]; - - if (!discussion) { - // Discussions for comments on commits in this MR don't have a resolved status. - continue; - } - - if (!firstUnresolvedDiscussionId && !discussion.isResolved()) { - firstUnresolvedDiscussionId = discussionId; - - if (jumpToFirstDiscussion) { - break; - } - } - - if (!jumpToFirstDiscussion) { - if (currentDiscussionFound) { - if (!discussion.isResolved()) { - nextUnresolvedDiscussionId = discussionId; - break; - } - else { - continue; - } - } - - if (discussionId === this.discussionId) { - currentDiscussionFound = true; - } + if (discussion && !discussion.isResolved()) { + unresolvedDiscussionCount += 1; } } - nextUnresolvedDiscussionId = nextUnresolvedDiscussionId || firstUnresolvedDiscussionId; - - if (!nextUnresolvedDiscussionId) { - return; - } - - let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`); - - if (activeTab === 'notes') { - $target = $target.closest('.note-discussion'); - - // If the next discussion is closed, toggle it open. - if ($target.find('.js-toggle-content').is(':hidden')) { - $target.find('.js-toggle-button i').trigger('click'); + if (this.discussionId && !this.discussion.isResolved()) { + // If this is the last unresolved discussion on the diffs tab, + // there are no discussions to jump to. + if (unresolvedDiscussionCount === 1) { + hasDiscussionsToJumpTo = false; } - } else if (activeTab === 'diffs') { - // Resolved discussions are hidden in the diffs tab by default. - // If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab. - // When jumping between unresolved discussions on the diffs tab, we show them. - $target.closest(".content").show(); - - $target = $target.closest("tr.notes_holder"); - $target.show(); - - // If we are on the diffs tab, we don't scroll to the discussion itself, but to - // 4 diff lines above it: the line the discussion was in response to + 3 context - let prevEl; - for (let i = 0; i < 4; i += 1) { - prevEl = $target.prev(); - - // If the discussion doesn't have 4 lines above it, we'll have to do with fewer. - if (!prevEl.hasClass("line_holder")) { - break; - } - - $target = prevEl; + } else { + // If there are no unresolved discussions on the diffs tab at all, + // there are no discussions to jump to. + if (unresolvedDiscussionCount === 0) { + hasDiscussionsToJumpTo = false; } } - - $.scrollTo($target, { - offset: 0 - }); + } else if (activeTab !== 'notes') { + // If we are on the commits or builds tabs, + // there are no discussions to jump to. + hasDiscussionsToJumpTo = false; } - }, - created() { - this.discussion = this.discussions[this.discussionId]; - }, - }); - Vue.component('jump-to-discussion', JumpToDiscussion); -})(); + if (!hasDiscussionsToJumpTo) { + // If there are no discussions to jump to on the current page, + // switch to the notes tab and jump to the first disucssion there. + window.mrTabs.activateTab('notes'); + activeTab = 'notes'; + jumpToFirstDiscussion = true; + } + + if (activeTab === 'notes') { + discussionsSelector = '.discussion[data-discussion-id]'; + discussionIdsInScope = discussionIdsForElements($(discussionsSelector)); + } + + let currentDiscussionFound = false; + for (let i = 0; i < discussionIdsInScope.length; i += 1) { + const discussionId = discussionIdsInScope[i]; + const discussion = discussions[discussionId]; + + if (!discussion) { + // Discussions for comments on commits in this MR don't have a resolved status. + continue; + } + + if (!firstUnresolvedDiscussionId && !discussion.isResolved()) { + firstUnresolvedDiscussionId = discussionId; + + if (jumpToFirstDiscussion) { + break; + } + } + + if (!jumpToFirstDiscussion) { + if (currentDiscussionFound) { + if (!discussion.isResolved()) { + nextUnresolvedDiscussionId = discussionId; + break; + } + else { + continue; + } + } + + if (discussionId === this.discussionId) { + currentDiscussionFound = true; + } + } + } + + nextUnresolvedDiscussionId = nextUnresolvedDiscussionId || firstUnresolvedDiscussionId; + + if (!nextUnresolvedDiscussionId) { + return; + } + + let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`); + + if (activeTab === 'notes') { + $target = $target.closest('.note-discussion'); + + // If the next discussion is closed, toggle it open. + if ($target.find('.js-toggle-content').is(':hidden')) { + $target.find('.js-toggle-button i').trigger('click'); + } + } else if (activeTab === 'diffs') { + // Resolved discussions are hidden in the diffs tab by default. + // If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab. + // When jumping between unresolved discussions on the diffs tab, we show them. + $target.closest(".content").show(); + + $target = $target.closest("tr.notes_holder"); + $target.show(); + + // If we are on the diffs tab, we don't scroll to the discussion itself, but to + // 4 diff lines above it: the line the discussion was in response to + 3 context + let prevEl; + for (let i = 0; i < 4; i += 1) { + prevEl = $target.prev(); + + // If the discussion doesn't have 4 lines above it, we'll have to do with fewer. + if (!prevEl.hasClass("line_holder")) { + break; + } + + $target = prevEl; + } + } + + $.scrollTo($target, { + offset: 0 + }); + } + }, + created() { + this.discussion = this.discussions[this.discussionId]; + }, +}); + +Vue.component('jump-to-discussion', JumpToDiscussion); diff --git a/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js b/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js index 8eb0e10b832..e0c09aa0eee 100644 --- a/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js +++ b/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js @@ -2,29 +2,27 @@ import Vue from 'vue'; -(() => { - const NewIssueForDiscussion = Vue.extend({ - props: { - discussionId: { - type: String, - required: true, - }, +const NewIssueForDiscussion = Vue.extend({ + props: { + discussionId: { + type: String, + required: true, }, - data() { - return { - discussions: CommentsStore.state, - }; + }, + data() { + return { + discussions: CommentsStore.state, + }; + }, + computed: { + discussion() { + return this.discussions[this.discussionId]; }, - computed: { - discussion() { - return this.discussions[this.discussionId]; - }, - showButton() { - if (this.discussion) return !this.discussion.isResolved(); - return false; - }, + showButton() { + if (this.discussion) return !this.discussion.isResolved(); + return false; }, - }); + }, +}); - Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion); -})(); +Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion); diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js index dcfc40c1013..8fafd13c6c2 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js @@ -5,117 +5,115 @@ import Vue from 'vue'; -(() => { - const ResolveBtn = Vue.extend({ - props: { - noteId: Number, - discussionId: String, - resolved: Boolean, - canResolve: Boolean, - resolvedBy: String, - authorName: String, - authorAvatar: String, - noteTruncated: String, +const ResolveBtn = Vue.extend({ + props: { + noteId: Number, + discussionId: String, + resolved: Boolean, + canResolve: Boolean, + resolvedBy: String, + authorName: String, + authorAvatar: String, + noteTruncated: String, + }, + data: function () { + return { + discussions: CommentsStore.state, + loading: false + }; + }, + watch: { + 'discussions': { + handler: 'updateTooltip', + deep: true + } + }, + computed: { + discussion: function () { + return this.discussions[this.discussionId]; }, - data: function () { - return { - discussions: CommentsStore.state, - loading: false - }; + note: function () { + return this.discussion ? this.discussion.getNote(this.noteId) : {}; }, - watch: { - 'discussions': { - handler: 'updateTooltip', - deep: true + buttonText: function () { + if (this.isResolved) { + return `Resolved by ${this.resolvedByName}`; + } else if (this.canResolve) { + return 'Mark as resolved'; + } else { + return 'Unable to resolve'; } }, - computed: { - discussion: function () { - return this.discussions[this.discussionId]; - }, - note: function () { - return this.discussion ? this.discussion.getNote(this.noteId) : {}; - }, - buttonText: function () { - if (this.isResolved) { - return `Resolved by ${this.resolvedByName}`; - } else if (this.canResolve) { - return 'Mark as resolved'; - } else { - return 'Unable to resolve'; - } - }, - isResolved: function () { - if (this.note) { - return this.note.resolved; - } else { - return false; - } - }, - resolvedByName: function () { - return this.note.resolved_by; - }, - }, - methods: { - updateTooltip: function () { - this.$nextTick(() => { - $(this.$refs.button) - .tooltip('hide') - .tooltip('fixTitle'); - }); - }, - resolve: function () { - if (!this.canResolve) return; - - let promise; - this.loading = true; - - if (this.isResolved) { - promise = ResolveService - .unresolve(this.noteId); - } else { - promise = ResolveService - .resolve(this.noteId); - } - - promise.then((response) => { - this.loading = false; - - if (response.status === 200) { - const data = response.json(); - const resolved_by = data ? data.resolved_by : null; - - CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by); - this.discussion.updateHeadline(data); - } else { - new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert'); - } - - this.updateTooltip(); - }); + isResolved: function () { + if (this.note) { + return this.note.resolved; + } else { + return false; } }, - mounted: function () { - $(this.$refs.button).tooltip({ - container: 'body' + resolvedByName: function () { + return this.note.resolved_by; + }, + }, + methods: { + updateTooltip: function () { + this.$nextTick(() => { + $(this.$refs.button) + .tooltip('hide') + .tooltip('fixTitle'); }); }, - beforeDestroy: function () { - CommentsStore.delete(this.discussionId, this.noteId); - }, - created: function () { - CommentsStore.create({ - discussionId: this.discussionId, - noteId: this.noteId, - canResolve: this.canResolve, - resolved: this.resolved, - resolvedBy: this.resolvedBy, - authorName: this.authorName, - authorAvatar: this.authorAvatar, - noteTruncated: this.noteTruncated, + resolve: function () { + if (!this.canResolve) return; + + let promise; + this.loading = true; + + if (this.isResolved) { + promise = ResolveService + .unresolve(this.noteId); + } else { + promise = ResolveService + .resolve(this.noteId); + } + + promise.then((response) => { + this.loading = false; + + if (response.status === 200) { + const data = response.json(); + const resolved_by = data ? data.resolved_by : null; + + CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by); + this.discussion.updateHeadline(data); + } else { + new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert'); + } + + this.updateTooltip(); }); } - }); + }, + mounted: function () { + $(this.$refs.button).tooltip({ + container: 'body' + }); + }, + beforeDestroy: function () { + CommentsStore.delete(this.discussionId, this.noteId); + }, + created: function () { + CommentsStore.create({ + discussionId: this.discussionId, + noteId: this.noteId, + canResolve: this.canResolve, + resolved: this.resolved, + resolvedBy: this.resolvedBy, + authorName: this.authorName, + authorAvatar: this.authorAvatar, + noteTruncated: this.noteTruncated, + }); + } +}); - Vue.component('resolve-btn', ResolveBtn); -})(); +Vue.component('resolve-btn', ResolveBtn); diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js b/app/assets/javascripts/diff_notes/components/resolve_count.js index 27147ac6b5c..96e5a440357 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_count.js +++ b/app/assets/javascripts/diff_notes/components/resolve_count.js @@ -4,24 +4,22 @@ import Vue from 'vue'; -((w) => { - w.ResolveCount = Vue.extend({ - mixins: [DiscussionMixins], - props: { - loggedOut: Boolean +window.ResolveCount = Vue.extend({ + mixins: [DiscussionMixins], + props: { + loggedOut: Boolean + }, + data: function () { + return { + discussions: CommentsStore.state + }; + }, + computed: { + allResolved: function () { + return this.resolvedDiscussionCount === this.discussionCount; }, - data: function () { - return { - discussions: CommentsStore.state - }; - }, - computed: { - allResolved: function () { - return this.resolvedDiscussionCount === this.discussionCount; - }, - resolvedCountText() { - return this.discussionCount === 1 ? 'discussion' : 'discussions'; - } + resolvedCountText() { + return this.discussionCount === 1 ? 'discussion' : 'discussions'; } - }); -})(window); + } +}); diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js index a964b7d0c6b..6a036e96171 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js @@ -4,59 +4,57 @@ import Vue from 'vue'; -(() => { - const ResolveDiscussionBtn = Vue.extend({ - props: { - discussionId: String, - mergeRequestId: Number, - canResolve: Boolean, - }, - data: function() { - return { - discussion: {}, - }; - }, - computed: { - showButton: function () { - if (this.discussion) { - return this.discussion.isResolvable(); - } else { - return false; - } - }, - isDiscussionResolved: function () { - if (this.discussion) { - return this.discussion.isResolved(); - } else { - return false; - } - }, - buttonText: function () { - if (this.isDiscussionResolved) { - return "Unresolve discussion"; - } else { - return "Resolve discussion"; - } - }, - loading: function () { - if (this.discussion) { - return this.discussion.loading; - } else { - return false; - } +const ResolveDiscussionBtn = Vue.extend({ + props: { + discussionId: String, + mergeRequestId: Number, + canResolve: Boolean, + }, + data: function() { + return { + discussion: {}, + }; + }, + computed: { + showButton: function () { + if (this.discussion) { + return this.discussion.isResolvable(); + } else { + return false; } }, - methods: { - resolve: function () { - ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId); + isDiscussionResolved: function () { + if (this.discussion) { + return this.discussion.isResolved(); + } else { + return false; } }, - created: function () { - CommentsStore.createDiscussion(this.discussionId, this.canResolve); - - this.discussion = CommentsStore.state[this.discussionId]; + buttonText: function () { + if (this.isDiscussionResolved) { + return "Unresolve discussion"; + } else { + return "Resolve discussion"; + } + }, + loading: function () { + if (this.discussion) { + return this.discussion.loading; + } else { + return false; + } } - }); + }, + methods: { + resolve: function () { + ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId); + } + }, + created: function () { + CommentsStore.createDiscussion(this.discussionId, this.canResolve); - Vue.component('resolve-discussion-btn', ResolveDiscussionBtn); -})(); + this.discussion = CommentsStore.state[this.discussionId]; + } +}); + +Vue.component('resolve-discussion-btn', ResolveDiscussionBtn); diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js b/app/assets/javascripts/diff_notes/mixins/discussion.js index 3c08c222f46..36c4abf02cf 100644 --- a/app/assets/javascripts/diff_notes/mixins/discussion.js +++ b/app/assets/javascripts/diff_notes/mixins/discussion.js @@ -1,37 +1,35 @@ /* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */ -((w) => { - w.DiscussionMixins = { - computed: { - discussionCount: function () { - return Object.keys(this.discussions).length; - }, - resolvedDiscussionCount: function () { - let resolvedCount = 0; +window.DiscussionMixins = { + computed: { + discussionCount: function () { + return Object.keys(this.discussions).length; + }, + resolvedDiscussionCount: function () { + let resolvedCount = 0; - for (const discussionId in this.discussions) { - const discussion = this.discussions[discussionId]; + for (const discussionId in this.discussions) { + const discussion = this.discussions[discussionId]; - if (discussion.isResolved()) { - resolvedCount += 1; - } + if (discussion.isResolved()) { + resolvedCount += 1; } - - return resolvedCount; - }, - unresolvedDiscussionCount: function () { - let unresolvedCount = 0; - - for (const discussionId in this.discussions) { - const discussion = this.discussions[discussionId]; - - if (!discussion.isResolved()) { - unresolvedCount += 1; - } - } - - return unresolvedCount; } + + return resolvedCount; + }, + unresolvedDiscussionCount: function () { + let unresolvedCount = 0; + + for (const discussionId in this.discussions) { + const discussion = this.discussions[discussionId]; + + if (!discussion.isResolved()) { + unresolvedCount += 1; + } + } + + return unresolvedCount; } - }; -})(window); + } +}; diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js index bfa4fc9037a..e1e2e3e93f9 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js +++ b/app/assets/javascripts/diff_notes/services/resolve.js @@ -9,76 +9,74 @@ require('../../vue_shared/vue_resource_interceptor'); Vue.use(VueResource); -(() => { - window.gl = window.gl || {}; +window.gl = window.gl || {}; - class ResolveServiceClass { - constructor(root) { - this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`); - this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`); - } - - resolve(noteId) { - return this.noteResource.save({ noteId }, {}); - } - - unresolve(noteId) { - return this.noteResource.delete({ noteId }, {}); - } - - toggleResolveForDiscussion(mergeRequestId, discussionId) { - const discussion = CommentsStore.state[discussionId]; - const isResolved = discussion.isResolved(); - let promise; - - if (isResolved) { - promise = this.unResolveAll(mergeRequestId, discussionId); - } else { - promise = this.resolveAll(mergeRequestId, discussionId); - } - - promise.then((response) => { - discussion.loading = false; - - if (response.status === 200) { - const data = response.json(); - const resolved_by = data ? data.resolved_by : null; - - if (isResolved) { - discussion.unResolveAllNotes(); - } else { - discussion.resolveAllNotes(resolved_by); - } - - discussion.updateHeadline(data); - } else { - new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert'); - } - }); - } - - resolveAll(mergeRequestId, discussionId) { - const discussion = CommentsStore.state[discussionId]; - - discussion.loading = true; - - return this.discussionResource.save({ - mergeRequestId, - discussionId - }, {}); - } - - unResolveAll(mergeRequestId, discussionId) { - const discussion = CommentsStore.state[discussionId]; - - discussion.loading = true; - - return this.discussionResource.delete({ - mergeRequestId, - discussionId - }, {}); - } +class ResolveServiceClass { + constructor(root) { + this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`); + this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`); } - gl.DiffNotesResolveServiceClass = ResolveServiceClass; -})(); + resolve(noteId) { + return this.noteResource.save({ noteId }, {}); + } + + unresolve(noteId) { + return this.noteResource.delete({ noteId }, {}); + } + + toggleResolveForDiscussion(mergeRequestId, discussionId) { + const discussion = CommentsStore.state[discussionId]; + const isResolved = discussion.isResolved(); + let promise; + + if (isResolved) { + promise = this.unResolveAll(mergeRequestId, discussionId); + } else { + promise = this.resolveAll(mergeRequestId, discussionId); + } + + promise.then((response) => { + discussion.loading = false; + + if (response.status === 200) { + const data = response.json(); + const resolved_by = data ? data.resolved_by : null; + + if (isResolved) { + discussion.unResolveAllNotes(); + } else { + discussion.resolveAllNotes(resolved_by); + } + + discussion.updateHeadline(data); + } else { + new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert'); + } + }); + } + + resolveAll(mergeRequestId, discussionId) { + const discussion = CommentsStore.state[discussionId]; + + discussion.loading = true; + + return this.discussionResource.save({ + mergeRequestId, + discussionId + }, {}); + } + + unResolveAll(mergeRequestId, discussionId) { + const discussion = CommentsStore.state[discussionId]; + + discussion.loading = true; + + return this.discussionResource.delete({ + mergeRequestId, + discussionId + }, {}); + } +} + +gl.DiffNotesResolveServiceClass = ResolveServiceClass; diff --git a/app/assets/javascripts/diff_notes/stores/comments.js b/app/assets/javascripts/diff_notes/stores/comments.js index e6cbda56c91..d802db7d3af 100644 --- a/app/assets/javascripts/diff_notes/stores/comments.js +++ b/app/assets/javascripts/diff_notes/stores/comments.js @@ -3,56 +3,54 @@ import Vue from 'vue'; -((w) => { - w.CommentsStore = { - state: {}, - get: function (discussionId, noteId) { - return this.state[discussionId].getNote(noteId); - }, - createDiscussion: function (discussionId, canResolve) { - let discussion = this.state[discussionId]; - if (!this.state[discussionId]) { - discussion = new DiscussionModel(discussionId); - Vue.set(this.state, discussionId, discussion); - } - - if (canResolve !== undefined) { - discussion.canResolve = canResolve; - } - - return discussion; - }, - create: function (noteObj) { - const discussion = this.createDiscussion(noteObj.discussionId); - - discussion.createNote(noteObj); - }, - update: function (discussionId, noteId, resolved, resolved_by) { - const discussion = this.state[discussionId]; - const note = discussion.getNote(noteId); - note.resolved = resolved; - note.resolved_by = resolved_by; - }, - delete: function (discussionId, noteId) { - const discussion = this.state[discussionId]; - discussion.deleteNote(noteId); - - if (discussion.notesCount() === 0) { - Vue.delete(this.state, discussionId); - } - }, - unresolvedDiscussionIds: function () { - const ids = []; - - for (const discussionId in this.state) { - const discussion = this.state[discussionId]; - - if (!discussion.isResolved()) { - ids.push(discussion.id); - } - } - - return ids; +window.CommentsStore = { + state: {}, + get: function (discussionId, noteId) { + return this.state[discussionId].getNote(noteId); + }, + createDiscussion: function (discussionId, canResolve) { + let discussion = this.state[discussionId]; + if (!this.state[discussionId]) { + discussion = new DiscussionModel(discussionId); + Vue.set(this.state, discussionId, discussion); } - }; -})(window); + + if (canResolve !== undefined) { + discussion.canResolve = canResolve; + } + + return discussion; + }, + create: function (noteObj) { + const discussion = this.createDiscussion(noteObj.discussionId); + + discussion.createNote(noteObj); + }, + update: function (discussionId, noteId, resolved, resolved_by) { + const discussion = this.state[discussionId]; + const note = discussion.getNote(noteId); + note.resolved = resolved; + note.resolved_by = resolved_by; + }, + delete: function (discussionId, noteId) { + const discussion = this.state[discussionId]; + discussion.deleteNote(noteId); + + if (discussion.notesCount() === 0) { + Vue.delete(this.state, discussionId); + } + }, + unresolvedDiscussionIds: function () { + const ids = []; + + for (const discussionId in this.state) { + const discussion = this.state[discussionId]; + + if (!discussion.isResolved()) { + ids.push(discussion.id); + } + } + + return ids; + } +}; diff --git a/spec/javascripts/diff_comments_store_spec.js b/spec/javascripts/diff_comments_store_spec.js index 84cf98c930a..66ece7e4f41 100644 --- a/spec/javascripts/diff_comments_store_spec.js +++ b/spec/javascripts/diff_comments_store_spec.js @@ -5,129 +5,127 @@ require('~/diff_notes/models/discussion'); require('~/diff_notes/models/note'); require('~/diff_notes/stores/comments'); -(() => { - function createDiscussion(noteId = 1, resolved = true) { - CommentsStore.create({ - discussionId: 'a', - noteId, - canResolve: true, - resolved, - resolvedBy: 'test', - authorName: 'test', - authorAvatar: 'test', - noteTruncated: 'test...', - }); - } +function createDiscussion(noteId = 1, resolved = true) { + CommentsStore.create({ + discussionId: 'a', + noteId, + canResolve: true, + resolved, + resolvedBy: 'test', + authorName: 'test', + authorAvatar: 'test', + noteTruncated: 'test...', + }); +} +beforeEach(() => { + CommentsStore.state = {}; +}); + +describe('New discussion', () => { + it('creates new discussion', () => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); + expect(Object.keys(CommentsStore.state).length).toBe(1); + }); + + it('creates new note in discussion', () => { + createDiscussion(); + createDiscussion(2); + + const discussion = CommentsStore.state['a']; + expect(Object.keys(discussion.notes).length).toBe(2); + }); +}); + +describe('Get note', () => { beforeEach(() => { - CommentsStore.state = {}; + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); }); - describe('New discussion', () => { - it('creates new discussion', () => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - expect(Object.keys(CommentsStore.state).length).toBe(1); - }); + it('gets note by ID', () => { + const note = CommentsStore.get('a', 1); + expect(note).toBeDefined(); + expect(note.id).toBe(1); + }); +}); - it('creates new note in discussion', () => { - createDiscussion(); - createDiscussion(2); - - const discussion = CommentsStore.state['a']; - expect(Object.keys(discussion.notes).length).toBe(2); - }); +describe('Delete discussion', () => { + beforeEach(() => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); }); - describe('Get note', () => { - beforeEach(() => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - }); - - it('gets note by ID', () => { - const note = CommentsStore.get('a', 1); - expect(note).toBeDefined(); - expect(note.id).toBe(1); - }); + it('deletes discussion by ID', () => { + CommentsStore.delete('a', 1); + expect(Object.keys(CommentsStore.state).length).toBe(0); }); - describe('Delete discussion', () => { - beforeEach(() => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - }); + it('deletes discussion when no more notes', () => { + createDiscussion(); + createDiscussion(2); + expect(Object.keys(CommentsStore.state).length).toBe(1); + expect(Object.keys(CommentsStore.state['a'].notes).length).toBe(2); - it('deletes discussion by ID', () => { - CommentsStore.delete('a', 1); - expect(Object.keys(CommentsStore.state).length).toBe(0); - }); + CommentsStore.delete('a', 1); + CommentsStore.delete('a', 2); + expect(Object.keys(CommentsStore.state).length).toBe(0); + }); +}); - it('deletes discussion when no more notes', () => { - createDiscussion(); - createDiscussion(2); - expect(Object.keys(CommentsStore.state).length).toBe(1); - expect(Object.keys(CommentsStore.state['a'].notes).length).toBe(2); - - CommentsStore.delete('a', 1); - CommentsStore.delete('a', 2); - expect(Object.keys(CommentsStore.state).length).toBe(0); - }); +describe('Update note', () => { + beforeEach(() => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); }); - describe('Update note', () => { - beforeEach(() => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - }); + it('updates note to be unresolved', () => { + CommentsStore.update('a', 1, false, 'test'); - it('updates note to be unresolved', () => { - CommentsStore.update('a', 1, false, 'test'); + const note = CommentsStore.get('a', 1); + expect(note.resolved).toBe(false); + }); +}); - const note = CommentsStore.get('a', 1); - expect(note.resolved).toBe(false); - }); +describe('Discussion resolved', () => { + beforeEach(() => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); }); - describe('Discussion resolved', () => { - beforeEach(() => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - }); - - it('is resolved with single note', () => { - const discussion = CommentsStore.state['a']; - expect(discussion.isResolved()).toBe(true); - }); - - it('is unresolved with 2 notes', () => { - const discussion = CommentsStore.state['a']; - createDiscussion(2, false); - - expect(discussion.isResolved()).toBe(false); - }); - - it('is resolved with 2 notes', () => { - const discussion = CommentsStore.state['a']; - createDiscussion(2); - - expect(discussion.isResolved()).toBe(true); - }); - - it('resolve all notes', () => { - const discussion = CommentsStore.state['a']; - createDiscussion(2, false); - - discussion.resolveAllNotes(); - expect(discussion.isResolved()).toBe(true); - }); - - it('unresolve all notes', () => { - const discussion = CommentsStore.state['a']; - createDiscussion(2); - - discussion.unResolveAllNotes(); - expect(discussion.isResolved()).toBe(false); - }); + it('is resolved with single note', () => { + const discussion = CommentsStore.state['a']; + expect(discussion.isResolved()).toBe(true); }); -})(); + + it('is unresolved with 2 notes', () => { + const discussion = CommentsStore.state['a']; + createDiscussion(2, false); + + expect(discussion.isResolved()).toBe(false); + }); + + it('is resolved with 2 notes', () => { + const discussion = CommentsStore.state['a']; + createDiscussion(2); + + expect(discussion.isResolved()).toBe(true); + }); + + it('resolve all notes', () => { + const discussion = CommentsStore.state['a']; + createDiscussion(2, false); + + discussion.resolveAllNotes(); + expect(discussion.isResolved()).toBe(true); + }); + + it('unresolve all notes', () => { + const discussion = CommentsStore.state['a']; + createDiscussion(2); + + discussion.unResolveAllNotes(); + expect(discussion.isResolved()).toBe(false); + }); +}); From 08a09c6b62b7052b657b0e56fcaa0acb53991a10 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 17 Apr 2017 02:01:11 -0500 Subject: [PATCH 093/168] Remove IIFEs in filtered_search_bundle.js --- .../filtered_search/dropdown_hint.js | 130 ++- .../filtered_search/dropdown_non_user.js | 82 +- .../filtered_search/dropdown_user.js | 124 ++- .../filtered_search/dropdown_utils.js | 348 ++++--- .../filtered_search_dropdown.js | 222 +++-- .../filtered_search_dropdown_manager.js | 360 ++++---- .../filtered_search_manager.js | 860 +++++++++--------- .../filtered_search_token_keys.js | 186 ++-- .../filtered_search_tokenizer.js | 102 +-- .../filtered_search/dropdown_user_spec.js | 108 ++- .../filtered_search/dropdown_utils_spec.js | 544 ++++++----- .../filtered_search_dropdown_manager_spec.js | 144 ++- .../filtered_search_manager_spec.js | 474 +++++----- .../filtered_search_token_keys_spec.js | 198 ++-- .../filtered_search_tokenizer_spec.js | 214 +++-- 15 files changed, 2033 insertions(+), 2063 deletions(-) diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js index 381c40c03d8..3e7a892756c 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js @@ -2,82 +2,80 @@ import Filter from '~/droplab/plugins/filter'; require('./filtered_search_dropdown'); -(() => { - class DropdownHint extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input, filter) { - super(droplab, dropdown, input, filter); - this.config = { - Filter: { - template: 'hint', - filterFunction: gl.DropdownUtils.filterHint.bind(null, input), - }, - }; - } +class DropdownHint extends gl.FilteredSearchDropdown { + constructor(droplab, dropdown, input, filter) { + super(droplab, dropdown, input, filter); + this.config = { + Filter: { + template: 'hint', + filterFunction: gl.DropdownUtils.filterHint.bind(null, input), + }, + }; + } - itemClicked(e) { - const { selected } = e.detail; + itemClicked(e) { + const { selected } = e.detail; - if (selected.tagName === 'LI') { - if (selected.hasAttribute('data-value')) { - this.dismissDropdown(); - } else if (selected.getAttribute('data-action') === 'submit') { - this.dismissDropdown(); - this.dispatchFormSubmitEvent(); - } else { - const token = selected.querySelector('.js-filter-hint').innerText.trim(); - const tag = selected.querySelector('.js-filter-tag').innerText.trim(); + if (selected.tagName === 'LI') { + if (selected.hasAttribute('data-value')) { + this.dismissDropdown(); + } else if (selected.getAttribute('data-action') === 'submit') { + this.dismissDropdown(); + this.dispatchFormSubmitEvent(); + } else { + const token = selected.querySelector('.js-filter-hint').innerText.trim(); + const tag = selected.querySelector('.js-filter-tag').innerText.trim(); - if (tag.length) { - // Get previous input values in the input field and convert them into visual tokens - const previousInputValues = this.input.value.split(' '); - const searchTerms = []; + if (tag.length) { + // Get previous input values in the input field and convert them into visual tokens + const previousInputValues = this.input.value.split(' '); + const searchTerms = []; - previousInputValues.forEach((value, index) => { - searchTerms.push(value); + previousInputValues.forEach((value, index) => { + searchTerms.push(value); - if (index === previousInputValues.length - 1 - && token.indexOf(value.toLowerCase()) !== -1) { - searchTerms.pop(); - } - }); - - if (searchTerms.length > 0) { - gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' ')); + if (index === previousInputValues.length - 1 + && token.indexOf(value.toLowerCase()) !== -1) { + searchTerms.pop(); } + }); - gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container); + if (searchTerms.length > 0) { + gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' ')); } - this.dismissDropdown(); - this.dispatchInputEvent(); + + gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container); } + this.dismissDropdown(); + this.dispatchInputEvent(); } } - - renderContent() { - const dropdownData = []; - - [].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => { - const { icon, hint, tag, type } = dropdownMenu.dataset; - if (icon && hint && tag) { - dropdownData.push( - Object.assign({ - icon: `fa-${icon}`, - hint, - tag: `<${tag}>`, - }, type && { type }), - ); - } - }); - - this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config); - this.droplab.setData(this.hookId, dropdownData); - } - - init() { - this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init(); - } } - window.gl = window.gl || {}; - gl.DropdownHint = DropdownHint; -})(); + renderContent() { + const dropdownData = []; + + [].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => { + const { icon, hint, tag, type } = dropdownMenu.dataset; + if (icon && hint && tag) { + dropdownData.push( + Object.assign({ + icon: `fa-${icon}`, + hint, + tag: `<${tag}>`, + }, type && { type }), + ); + } + }); + + this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config); + this.droplab.setData(this.hookId, dropdownData); + } + + init() { + this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init(); + } +} + +window.gl = window.gl || {}; +gl.DropdownHint = DropdownHint; diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js index 6296965b911..982dc4b61be 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js @@ -5,48 +5,46 @@ import Filter from '~/droplab/plugins/filter'; require('./filtered_search_dropdown'); -(() => { - class DropdownNonUser extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input, filter, endpoint, symbol) { - super(droplab, dropdown, input, filter); - this.symbol = symbol; - this.config = { - Ajax: { - endpoint, - method: 'setData', - loadingTemplate: this.loadingTemplate, - onError() { - /* eslint-disable no-new */ - new Flash('An error occured fetching the dropdown data.'); - /* eslint-enable no-new */ - }, +class DropdownNonUser extends gl.FilteredSearchDropdown { + constructor(droplab, dropdown, input, filter, endpoint, symbol) { + super(droplab, dropdown, input, filter); + this.symbol = symbol; + this.config = { + Ajax: { + endpoint, + method: 'setData', + loadingTemplate: this.loadingTemplate, + onError() { + /* eslint-disable no-new */ + new Flash('An error occured fetching the dropdown data.'); + /* eslint-enable no-new */ }, - Filter: { - filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input), - template: 'title', - }, - }; - } - - itemClicked(e) { - super.itemClicked(e, (selected) => { - const title = selected.querySelector('.js-data-value').innerText.trim(); - return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`; - }); - } - - renderContent(forceShowList = false) { - this.droplab - .changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config); - super.renderContent(forceShowList); - } - - init() { - this.droplab - .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); - } + }, + Filter: { + filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input), + template: 'title', + }, + }; } - window.gl = window.gl || {}; - gl.DropdownNonUser = DropdownNonUser; -})(); + itemClicked(e) { + super.itemClicked(e, (selected) => { + const title = selected.querySelector('.js-data-value').innerText.trim(); + return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`; + }); + } + + renderContent(forceShowList = false) { + this.droplab + .changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config); + super.renderContent(forceShowList); + } + + init() { + this.droplab + .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); + } +} + +window.gl = window.gl || {}; +gl.DropdownNonUser = DropdownNonUser; diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js index 38b5d315bcf..74cec3d75fe 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_user.js @@ -4,69 +4,67 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter'; require('./filtered_search_dropdown'); -(() => { - class DropdownUser extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input, filter) { - super(droplab, dropdown, input, filter); - this.config = { - AjaxFilter: { - endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`, - searchKey: 'search', - params: { - per_page: 20, - active: true, - project_id: this.getProjectId(), - current_user: true, - }, - searchValueFunction: this.getSearchInput.bind(this), - loadingTemplate: this.loadingTemplate, - onError() { - /* eslint-disable no-new */ - new Flash('An error occured fetching the dropdown data.'); - /* eslint-enable no-new */ - }, +class DropdownUser extends gl.FilteredSearchDropdown { + constructor(droplab, dropdown, input, filter) { + super(droplab, dropdown, input, filter); + this.config = { + AjaxFilter: { + endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`, + searchKey: 'search', + params: { + per_page: 20, + active: true, + project_id: this.getProjectId(), + current_user: true, }, - }; - } - - itemClicked(e) { - super.itemClicked(e, - selected => selected.querySelector('.dropdown-light-content').innerText.trim()); - } - - renderContent(forceShowList = false) { - this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config); - super.renderContent(forceShowList); - } - - getProjectId() { - return this.input.getAttribute('data-project-id'); - } - - getSearchInput() { - const query = gl.DropdownUtils.getSearchInput(this.input); - const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); - - let value = lastToken || ''; - - if (value[0] === '@') { - value = value.slice(1); - } - - // Removes the first character if it is a quotation so that we can search - // with multiple words - if (value[0] === '"' || value[0] === '\'') { - value = value.slice(1); - } - - return value; - } - - init() { - this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init(); - } + searchValueFunction: this.getSearchInput.bind(this), + loadingTemplate: this.loadingTemplate, + onError() { + /* eslint-disable no-new */ + new Flash('An error occured fetching the dropdown data.'); + /* eslint-enable no-new */ + }, + }, + }; } - window.gl = window.gl || {}; - gl.DropdownUser = DropdownUser; -})(); + itemClicked(e) { + super.itemClicked(e, + selected => selected.querySelector('.dropdown-light-content').innerText.trim()); + } + + renderContent(forceShowList = false) { + this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config); + super.renderContent(forceShowList); + } + + getProjectId() { + return this.input.getAttribute('data-project-id'); + } + + getSearchInput() { + const query = gl.DropdownUtils.getSearchInput(this.input); + const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); + + let value = lastToken || ''; + + if (value[0] === '@') { + value = value.slice(1); + } + + // Removes the first character if it is a quotation so that we can search + // with multiple words + if (value[0] === '"' || value[0] === '\'') { + value = value.slice(1); + } + + return value; + } + + init() { + this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init(); + } +} + +window.gl = window.gl || {}; +gl.DropdownUser = DropdownUser; diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index 6c5c20447f7..bc7c1dffece 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -1,183 +1,181 @@ import FilteredSearchContainer from './container'; -(() => { - class DropdownUtils { - static getEscapedText(text) { - let escapedText = text; - const hasSpace = text.indexOf(' ') !== -1; - const hasDoubleQuote = text.indexOf('"') !== -1; +class DropdownUtils { + static getEscapedText(text) { + let escapedText = text; + const hasSpace = text.indexOf(' ') !== -1; + const hasDoubleQuote = text.indexOf('"') !== -1; - // Encapsulate value with quotes if it has spaces - // Known side effect: values's with both single and double quotes - // won't escape properly - if (hasSpace) { - if (hasDoubleQuote) { - escapedText = `'${text}'`; - } else { - // Encapsulate singleQuotes or if it hasSpace - escapedText = `"${text}"`; - } + // Encapsulate value with quotes if it has spaces + // Known side effect: values's with both single and double quotes + // won't escape properly + if (hasSpace) { + if (hasDoubleQuote) { + escapedText = `'${text}'`; + } else { + // Encapsulate singleQuotes or if it hasSpace + escapedText = `"${text}"`; } - - return escapedText; } - static filterWithSymbol(filterSymbol, input, item) { - const updatedItem = item; - const searchInput = gl.DropdownUtils.getSearchInput(input); - - const title = updatedItem.title.toLowerCase(); - let value = searchInput.toLowerCase(); - let symbol = ''; - - // Remove the symbol for filter - if (value[0] === filterSymbol) { - symbol = value[0]; - value = value.slice(1); - } - - // Removes the first character if it is a quotation so that we can search - // with multiple words - if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) { - value = value.slice(1); - } - - // Eg. filterSymbol = ~ for labels - const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1; - const match = title.indexOf(`${symbol}${value}`) !== -1; - - updatedItem.droplab_hidden = !match && !matchWithoutSymbol; - - return updatedItem; - } - - static filterHint(input, item) { - const updatedItem = item; - const searchInput = gl.DropdownUtils.getSearchQuery(input); - const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput); - const lastKey = lastToken.key || lastToken || ''; - const allowMultiple = item.type === 'array'; - const itemInExistingTokens = tokens.some(t => t.key === item.hint); - - if (!allowMultiple && itemInExistingTokens) { - updatedItem.droplab_hidden = true; - } else if (!lastKey || searchInput.split('').last() === ' ') { - updatedItem.droplab_hidden = false; - } else if (lastKey) { - const split = lastKey.split(':'); - const tokenName = split[0].split(' ').last(); - - const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; - updatedItem.droplab_hidden = tokenName ? match : false; - } - - return updatedItem; - } - - static setDataValueIfSelected(filter, selected) { - const dataValue = selected.getAttribute('data-value'); - - if (dataValue) { - gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true); - } - - // Return boolean based on whether it was set - return dataValue !== null; - } - - // Determines the full search query (visual tokens + input) - static getSearchQuery(untilInput = false) { - const container = FilteredSearchContainer.container; - const tokens = [].slice.call(container.querySelectorAll('.tokens-container li')); - const values = []; - - if (untilInput) { - const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token')); - // Add one to include input-token to the tokens array - tokens.splice(inputIndex + 1); - } - - tokens.forEach((token) => { - if (token.classList.contains('js-visual-token')) { - const name = token.querySelector('.name'); - const value = token.querySelector('.value'); - const symbol = value && value.dataset.symbol ? value.dataset.symbol : ''; - let valueText = ''; - - if (value && value.innerText) { - valueText = value.innerText; - } - - if (token.className.indexOf('filtered-search-token') !== -1) { - values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`); - } else { - values.push(name.innerText); - } - } else if (token.classList.contains('input-token')) { - const { isLastVisualTokenValid } = - gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); - - const input = FilteredSearchContainer.container.querySelector('.filtered-search'); - const inputValue = input && input.value; - - if (isLastVisualTokenValid) { - values.push(inputValue); - } else { - const previous = values.pop(); - values.push(`${previous}${inputValue}`); - } - } - }); - - return values - .map(value => value.trim()) - .join(' '); - } - - static getSearchInput(filteredSearchInput) { - const inputValue = filteredSearchInput.value; - const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput); - - return inputValue.slice(0, right); - } - - static getInputSelectionPosition(input) { - const selectionStart = input.selectionStart; - let inputValue = input.value; - // Replace all spaces inside quote marks with underscores - // (will continue to match entire string until an end quote is found if any) - // This helps with matching the beginning & end of a token:key - inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_')); - - // Get the right position for the word selected - // Regex matches first space - let right = inputValue.slice(selectionStart).search(/\s/); - - if (right >= 0) { - right += selectionStart; - } else if (right < 0) { - right = inputValue.length; - } - - // Get the left position for the word selected - // Regex matches last non-whitespace character - let left = inputValue.slice(0, right).search(/\S+$/); - - if (selectionStart === 0) { - left = 0; - } else if (selectionStart === inputValue.length && left < 0) { - left = inputValue.length; - } else if (left < 0) { - left = selectionStart; - } - - return { - left, - right, - }; - } + return escapedText; } - window.gl = window.gl || {}; - gl.DropdownUtils = DropdownUtils; -})(); + static filterWithSymbol(filterSymbol, input, item) { + const updatedItem = item; + const searchInput = gl.DropdownUtils.getSearchInput(input); + + const title = updatedItem.title.toLowerCase(); + let value = searchInput.toLowerCase(); + let symbol = ''; + + // Remove the symbol for filter + if (value[0] === filterSymbol) { + symbol = value[0]; + value = value.slice(1); + } + + // Removes the first character if it is a quotation so that we can search + // with multiple words + if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) { + value = value.slice(1); + } + + // Eg. filterSymbol = ~ for labels + const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1; + const match = title.indexOf(`${symbol}${value}`) !== -1; + + updatedItem.droplab_hidden = !match && !matchWithoutSymbol; + + return updatedItem; + } + + static filterHint(input, item) { + const updatedItem = item; + const searchInput = gl.DropdownUtils.getSearchQuery(input); + const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput); + const lastKey = lastToken.key || lastToken || ''; + const allowMultiple = item.type === 'array'; + const itemInExistingTokens = tokens.some(t => t.key === item.hint); + + if (!allowMultiple && itemInExistingTokens) { + updatedItem.droplab_hidden = true; + } else if (!lastKey || searchInput.split('').last() === ' ') { + updatedItem.droplab_hidden = false; + } else if (lastKey) { + const split = lastKey.split(':'); + const tokenName = split[0].split(' ').last(); + + const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; + updatedItem.droplab_hidden = tokenName ? match : false; + } + + return updatedItem; + } + + static setDataValueIfSelected(filter, selected) { + const dataValue = selected.getAttribute('data-value'); + + if (dataValue) { + gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true); + } + + // Return boolean based on whether it was set + return dataValue !== null; + } + + // Determines the full search query (visual tokens + input) + static getSearchQuery(untilInput = false) { + const container = FilteredSearchContainer.container; + const tokens = [].slice.call(container.querySelectorAll('.tokens-container li')); + const values = []; + + if (untilInput) { + const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token')); + // Add one to include input-token to the tokens array + tokens.splice(inputIndex + 1); + } + + tokens.forEach((token) => { + if (token.classList.contains('js-visual-token')) { + const name = token.querySelector('.name'); + const value = token.querySelector('.value'); + const symbol = value && value.dataset.symbol ? value.dataset.symbol : ''; + let valueText = ''; + + if (value && value.innerText) { + valueText = value.innerText; + } + + if (token.className.indexOf('filtered-search-token') !== -1) { + values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`); + } else { + values.push(name.innerText); + } + } else if (token.classList.contains('input-token')) { + const { isLastVisualTokenValid } = + gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + + const input = FilteredSearchContainer.container.querySelector('.filtered-search'); + const inputValue = input && input.value; + + if (isLastVisualTokenValid) { + values.push(inputValue); + } else { + const previous = values.pop(); + values.push(`${previous}${inputValue}`); + } + } + }); + + return values + .map(value => value.trim()) + .join(' '); + } + + static getSearchInput(filteredSearchInput) { + const inputValue = filteredSearchInput.value; + const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput); + + return inputValue.slice(0, right); + } + + static getInputSelectionPosition(input) { + const selectionStart = input.selectionStart; + let inputValue = input.value; + // Replace all spaces inside quote marks with underscores + // (will continue to match entire string until an end quote is found if any) + // This helps with matching the beginning & end of a token:key + inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_')); + + // Get the right position for the word selected + // Regex matches first space + let right = inputValue.slice(selectionStart).search(/\s/); + + if (right >= 0) { + right += selectionStart; + } else if (right < 0) { + right = inputValue.length; + } + + // Get the left position for the word selected + // Regex matches last non-whitespace character + let left = inputValue.slice(0, right).search(/\S+$/); + + if (selectionStart === 0) { + left = 0; + } else if (selectionStart === inputValue.length && left < 0) { + left = inputValue.length; + } else if (left < 0) { + left = selectionStart; + } + + return { + left, + right, + }; + } +} + +window.gl = window.gl || {}; +gl.DropdownUtils = DropdownUtils; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js index d58eeeebf81..4209ca0d6e2 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js @@ -1,124 +1,122 @@ -(() => { - const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger'; +const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger'; - class FilteredSearchDropdown { - constructor(droplab, dropdown, input, filter) { - this.droplab = droplab; - this.hookId = input && input.id; - this.input = input; - this.filter = filter; - this.dropdown = dropdown; - this.loadingTemplate = `
- -
`; - this.bindEvents(); - } +class FilteredSearchDropdown { + constructor(droplab, dropdown, input, filter) { + this.droplab = droplab; + this.hookId = input && input.id; + this.input = input; + this.filter = filter; + this.dropdown = dropdown; + this.loadingTemplate = `
+ +
`; + this.bindEvents(); + } - bindEvents() { - this.itemClickedWrapper = this.itemClicked.bind(this); - this.dropdown.addEventListener('click.dl', this.itemClickedWrapper); - } + bindEvents() { + this.itemClickedWrapper = this.itemClicked.bind(this); + this.dropdown.addEventListener('click.dl', this.itemClickedWrapper); + } - unbindEvents() { - this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper); - } + unbindEvents() { + this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper); + } - getCurrentHook() { - return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null; - } + getCurrentHook() { + return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null; + } - itemClicked(e, getValueFunction) { - const { selected } = e.detail; + itemClicked(e, getValueFunction) { + const { selected } = e.detail; - if (selected.tagName === 'LI' && selected.innerHTML) { - const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected); + if (selected.tagName === 'LI' && selected.innerHTML) { + const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected); - if (!dataValueSet) { - const value = getValueFunction(selected); - gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true); - } - - this.resetFilters(); - this.dismissDropdown(); - this.dispatchInputEvent(); + if (!dataValueSet) { + const value = getValueFunction(selected); + gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true); } - } - setAsDropdown() { - this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`); - } - - setOffset(offset = 0) { - if (window.innerWidth > 480) { - this.dropdown.style.left = `${offset}px`; - } else { - this.dropdown.style.left = '0px'; - } - } - - renderContent(forceShowList = false) { - const currentHook = this.getCurrentHook(); - if (forceShowList && currentHook && currentHook.list.hidden) { - currentHook.list.show(); - } - } - - render(forceRenderContent = false, forceShowList = false) { - this.setAsDropdown(); - - const currentHook = this.getCurrentHook(); - const firstTimeInitialized = currentHook === null; - - if (firstTimeInitialized || forceRenderContent) { - this.renderContent(forceShowList); - } else if (currentHook.list.list.id !== this.dropdown.id) { - this.renderContent(forceShowList); - } - } - - dismissDropdown() { - // Focusing on the input will dismiss dropdown - // (default droplab functionality) - this.input.focus(); - } - - dispatchInputEvent() { - // Propogate input change to FilteredSearchDropdownManager - // so that it can determine which dropdowns to open - this.input.dispatchEvent(new CustomEvent('input', { - bubbles: true, - cancelable: true, - })); - } - - dispatchFormSubmitEvent() { - // dispatchEvent() is necessary as form.submit() does not - // trigger event handlers - this.input.form.dispatchEvent(new Event('submit')); - } - - hideDropdown() { - const currentHook = this.getCurrentHook(); - if (currentHook) { - currentHook.list.hide(); - } - } - - resetFilters() { - const hook = this.getCurrentHook(); - - if (hook) { - const data = hook.list.data || []; - const results = data.map((o) => { - const updated = o; - updated.droplab_hidden = false; - return updated; - }); - hook.list.render(results); - } + this.resetFilters(); + this.dismissDropdown(); + this.dispatchInputEvent(); } } - window.gl = window.gl || {}; - gl.FilteredSearchDropdown = FilteredSearchDropdown; -})(); + setAsDropdown() { + this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`); + } + + setOffset(offset = 0) { + if (window.innerWidth > 480) { + this.dropdown.style.left = `${offset}px`; + } else { + this.dropdown.style.left = '0px'; + } + } + + renderContent(forceShowList = false) { + const currentHook = this.getCurrentHook(); + if (forceShowList && currentHook && currentHook.list.hidden) { + currentHook.list.show(); + } + } + + render(forceRenderContent = false, forceShowList = false) { + this.setAsDropdown(); + + const currentHook = this.getCurrentHook(); + const firstTimeInitialized = currentHook === null; + + if (firstTimeInitialized || forceRenderContent) { + this.renderContent(forceShowList); + } else if (currentHook.list.list.id !== this.dropdown.id) { + this.renderContent(forceShowList); + } + } + + dismissDropdown() { + // Focusing on the input will dismiss dropdown + // (default droplab functionality) + this.input.focus(); + } + + dispatchInputEvent() { + // Propogate input change to FilteredSearchDropdownManager + // so that it can determine which dropdowns to open + this.input.dispatchEvent(new CustomEvent('input', { + bubbles: true, + cancelable: true, + })); + } + + dispatchFormSubmitEvent() { + // dispatchEvent() is necessary as form.submit() does not + // trigger event handlers + this.input.form.dispatchEvent(new Event('submit')); + } + + hideDropdown() { + const currentHook = this.getCurrentHook(); + if (currentHook) { + currentHook.list.hide(); + } + } + + resetFilters() { + const hook = this.getCurrentHook(); + + if (hook) { + const data = hook.list.data || []; + const results = data.map((o) => { + const updated = o; + updated.droplab_hidden = false; + return updated; + }); + hook.list.render(results); + } + } +} + +window.gl = window.gl || {}; +gl.FilteredSearchDropdown = FilteredSearchDropdown; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index ec481b9ef97..49a6cd1ac77 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -1,191 +1,189 @@ import DropLab from '~/droplab/drop_lab'; import FilteredSearchContainer from './container'; -(() => { - class FilteredSearchDropdownManager { - constructor(baseEndpoint = '', page) { - this.container = FilteredSearchContainer.container; - this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); - this.tokenizer = gl.FilteredSearchTokenizer; - this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; - this.filteredSearchInput = this.container.querySelector('.filtered-search'); - this.page = page; +class FilteredSearchDropdownManager { + constructor(baseEndpoint = '', page) { + this.container = FilteredSearchContainer.container; + this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); + this.tokenizer = gl.FilteredSearchTokenizer; + this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; + this.filteredSearchInput = this.container.querySelector('.filtered-search'); + this.page = page; - this.setupMapping(); + this.setupMapping(); - this.cleanupWrapper = this.cleanup.bind(this); - document.addEventListener('beforeunload', this.cleanupWrapper); - } + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('beforeunload', this.cleanupWrapper); + } - cleanup() { - if (this.droplab) { - this.droplab.destroy(); - this.droplab = null; - } - - this.setupMapping(); - - document.removeEventListener('beforeunload', this.cleanupWrapper); - } - - setupMapping() { - this.mapping = { - author: { - reference: null, - gl: 'DropdownUser', - element: this.container.querySelector('#js-dropdown-author'), - }, - assignee: { - reference: null, - gl: 'DropdownUser', - element: this.container.querySelector('#js-dropdown-assignee'), - }, - milestone: { - reference: null, - gl: 'DropdownNonUser', - extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'], - element: this.container.querySelector('#js-dropdown-milestone'), - }, - label: { - reference: null, - gl: 'DropdownNonUser', - extraArguments: [`${this.baseEndpoint}/labels.json`, '~'], - element: this.container.querySelector('#js-dropdown-label'), - }, - hint: { - reference: null, - gl: 'DropdownHint', - element: this.container.querySelector('#js-dropdown-hint'), - }, - }; - } - - static addWordToInput(tokenName, tokenValue = '', clicked = false) { - const input = FilteredSearchContainer.container.querySelector('.filtered-search'); - - gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue); - input.value = ''; - - if (clicked) { - gl.FilteredSearchVisualTokens.moveInputToTheRight(); - } - } - - updateCurrentDropdownOffset() { - this.updateDropdownOffset(this.currentDropdown); - } - - updateDropdownOffset(key) { - // Always align dropdown with the input field - let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left; - - const maxInputWidth = 240; - const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth; - - // Make sure offset never exceeds the input container - const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth; - if (offsetMaxWidth < offset) { - offset = offsetMaxWidth; - } - - this.mapping[key].reference.setOffset(offset); - } - - load(key, firstLoad = false) { - const mappingKey = this.mapping[key]; - const glClass = mappingKey.gl; - const element = mappingKey.element; - let forceShowList = false; - - if (!mappingKey.reference) { - const dl = this.droplab; - const defaultArguments = [null, dl, element, this.filteredSearchInput, key]; - const glArguments = defaultArguments.concat(mappingKey.extraArguments || []); - - // Passing glArguments to `new gl[glClass]()` - mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))(); - } - - if (firstLoad) { - mappingKey.reference.init(); - } - - if (this.currentDropdown === 'hint') { - // Force the dropdown to show if it was clicked from the hint dropdown - forceShowList = true; - } - - this.updateDropdownOffset(key); - mappingKey.reference.render(firstLoad, forceShowList); - - this.currentDropdown = key; - } - - loadDropdown(dropdownName = '') { - let firstLoad = false; - - if (!this.droplab) { - firstLoad = true; - this.droplab = new DropLab(); - } - - const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); - const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key - && this.mapping[match.key]; - const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; - - if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { - const key = match && match.key ? match.key : 'hint'; - this.load(key, firstLoad); - } - } - - setDropdown() { - const query = gl.DropdownUtils.getSearchQuery(true); - const { lastToken, searchToken } = this.tokenizer.processTokens(query); - - if (this.currentDropdown) { - this.updateCurrentDropdownOffset(); - } - - if (lastToken === searchToken && lastToken !== null) { - // Token is not fully initialized yet because it has no value - // Eg. token = 'label:' - - const split = lastToken.split(':'); - const dropdownName = split[0].split(' ').last(); - this.loadDropdown(split.length > 1 ? dropdownName : ''); - } else if (lastToken) { - // Token has been initialized into an object because it has a value - this.loadDropdown(lastToken.key); - } else { - this.loadDropdown('hint'); - } - } - - resetDropdowns() { - if (!this.currentDropdown) { - return; - } - - // Force current dropdown to hide - this.mapping[this.currentDropdown].reference.hideDropdown(); - - // Re-Load dropdown - this.setDropdown(); - - // Reset filters for current dropdown - this.mapping[this.currentDropdown].reference.resetFilters(); - - // Reposition dropdown so that it is aligned with cursor - this.updateDropdownOffset(this.currentDropdown); - } - - destroyDroplab() { + cleanup() { + if (this.droplab) { this.droplab.destroy(); + this.droplab = null; + } + + this.setupMapping(); + + document.removeEventListener('beforeunload', this.cleanupWrapper); + } + + setupMapping() { + this.mapping = { + author: { + reference: null, + gl: 'DropdownUser', + element: this.container.querySelector('#js-dropdown-author'), + }, + assignee: { + reference: null, + gl: 'DropdownUser', + element: this.container.querySelector('#js-dropdown-assignee'), + }, + milestone: { + reference: null, + gl: 'DropdownNonUser', + extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'], + element: this.container.querySelector('#js-dropdown-milestone'), + }, + label: { + reference: null, + gl: 'DropdownNonUser', + extraArguments: [`${this.baseEndpoint}/labels.json`, '~'], + element: this.container.querySelector('#js-dropdown-label'), + }, + hint: { + reference: null, + gl: 'DropdownHint', + element: this.container.querySelector('#js-dropdown-hint'), + }, + }; + } + + static addWordToInput(tokenName, tokenValue = '', clicked = false) { + const input = FilteredSearchContainer.container.querySelector('.filtered-search'); + + gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue); + input.value = ''; + + if (clicked) { + gl.FilteredSearchVisualTokens.moveInputToTheRight(); } } - window.gl = window.gl || {}; - gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager; -})(); + updateCurrentDropdownOffset() { + this.updateDropdownOffset(this.currentDropdown); + } + + updateDropdownOffset(key) { + // Always align dropdown with the input field + let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left; + + const maxInputWidth = 240; + const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth; + + // Make sure offset never exceeds the input container + const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth; + if (offsetMaxWidth < offset) { + offset = offsetMaxWidth; + } + + this.mapping[key].reference.setOffset(offset); + } + + load(key, firstLoad = false) { + const mappingKey = this.mapping[key]; + const glClass = mappingKey.gl; + const element = mappingKey.element; + let forceShowList = false; + + if (!mappingKey.reference) { + const dl = this.droplab; + const defaultArguments = [null, dl, element, this.filteredSearchInput, key]; + const glArguments = defaultArguments.concat(mappingKey.extraArguments || []); + + // Passing glArguments to `new gl[glClass]()` + mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))(); + } + + if (firstLoad) { + mappingKey.reference.init(); + } + + if (this.currentDropdown === 'hint') { + // Force the dropdown to show if it was clicked from the hint dropdown + forceShowList = true; + } + + this.updateDropdownOffset(key); + mappingKey.reference.render(firstLoad, forceShowList); + + this.currentDropdown = key; + } + + loadDropdown(dropdownName = '') { + let firstLoad = false; + + if (!this.droplab) { + firstLoad = true; + this.droplab = new DropLab(); + } + + const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); + const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key + && this.mapping[match.key]; + const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; + + if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { + const key = match && match.key ? match.key : 'hint'; + this.load(key, firstLoad); + } + } + + setDropdown() { + const query = gl.DropdownUtils.getSearchQuery(true); + const { lastToken, searchToken } = this.tokenizer.processTokens(query); + + if (this.currentDropdown) { + this.updateCurrentDropdownOffset(); + } + + if (lastToken === searchToken && lastToken !== null) { + // Token is not fully initialized yet because it has no value + // Eg. token = 'label:' + + const split = lastToken.split(':'); + const dropdownName = split[0].split(' ').last(); + this.loadDropdown(split.length > 1 ? dropdownName : ''); + } else if (lastToken) { + // Token has been initialized into an object because it has a value + this.loadDropdown(lastToken.key); + } else { + this.loadDropdown('hint'); + } + } + + resetDropdowns() { + if (!this.currentDropdown) { + return; + } + + // Force current dropdown to hide + this.mapping[this.currentDropdown].reference.hideDropdown(); + + // Re-Load dropdown + this.setDropdown(); + + // Reset filters for current dropdown + this.mapping[this.currentDropdown].reference.resetFilters(); + + // Reposition dropdown so that it is aligned with cursor + this.updateDropdownOffset(this.currentDropdown); + } + + destroyDroplab() { + this.droplab.destroy(); + } +} + +window.gl = window.gl || {}; +gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index b93a8f1d322..a5eb33dd9de 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -6,489 +6,487 @@ import RecentSearchesStore from './stores/recent_searches_store'; import RecentSearchesService from './services/recent_searches_service'; import eventHub from './event_hub'; -(() => { - class FilteredSearchManager { - constructor(page) { - this.container = FilteredSearchContainer.container; - this.filteredSearchInput = this.container.querySelector('.filtered-search'); - this.filteredSearchInputForm = this.filteredSearchInput.form; - this.clearSearchButton = this.container.querySelector('.clear-search'); - this.tokensContainer = this.container.querySelector('.tokens-container'); - this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; +class FilteredSearchManager { + constructor(page) { + this.container = FilteredSearchContainer.container; + this.filteredSearchInput = this.container.querySelector('.filtered-search'); + this.filteredSearchInputForm = this.filteredSearchInput.form; + this.clearSearchButton = this.container.querySelector('.clear-search'); + this.tokensContainer = this.container.querySelector('.tokens-container'); + this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; - this.recentSearchesStore = new RecentSearchesStore(); - let recentSearchesKey = 'issue-recent-searches'; - if (page === 'merge_requests') { - recentSearchesKey = 'merge-request-recent-searches'; - } - this.recentSearchesService = new RecentSearchesService(recentSearchesKey); + this.recentSearchesStore = new RecentSearchesStore(); + let recentSearchesKey = 'issue-recent-searches'; + if (page === 'merge_requests') { + recentSearchesKey = 'merge-request-recent-searches'; + } + this.recentSearchesService = new RecentSearchesService(recentSearchesKey); - // Fetch recent searches from localStorage - this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch() - .catch(() => { - // eslint-disable-next-line no-new - new Flash('An error occured while parsing recent searches'); - // Gracefully fail to empty array - return []; - }) - .then((searches) => { - // Put any searches that may have come in before - // we fetched the saved searches ahead of the already saved ones - const resultantSearches = this.recentSearchesStore.setRecentSearches( - this.recentSearchesStore.state.recentSearches.concat(searches), - ); - this.recentSearchesService.save(resultantSearches); - }); - - if (this.filteredSearchInput) { - this.tokenizer = gl.FilteredSearchTokenizer; - this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page); - - this.recentSearchesRoot = new RecentSearchesRoot( - this.recentSearchesStore, - this.recentSearchesService, - document.querySelector('.js-filtered-search-history-dropdown'), + // Fetch recent searches from localStorage + this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch() + .catch(() => { + // eslint-disable-next-line no-new + new Flash('An error occured while parsing recent searches'); + // Gracefully fail to empty array + return []; + }) + .then((searches) => { + // Put any searches that may have come in before + // we fetched the saved searches ahead of the already saved ones + const resultantSearches = this.recentSearchesStore.setRecentSearches( + this.recentSearchesStore.state.recentSearches.concat(searches), ); - this.recentSearchesRoot.init(); + this.recentSearchesService.save(resultantSearches); + }); - this.bindEvents(); - this.loadSearchParamsFromURL(); - this.dropdownManager.setDropdown(); + if (this.filteredSearchInput) { + this.tokenizer = gl.FilteredSearchTokenizer; + this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page); - this.cleanupWrapper = this.cleanup.bind(this); - document.addEventListener('beforeunload', this.cleanupWrapper); - } + this.recentSearchesRoot = new RecentSearchesRoot( + this.recentSearchesStore, + this.recentSearchesService, + document.querySelector('.js-filtered-search-history-dropdown'), + ); + this.recentSearchesRoot.init(); + + this.bindEvents(); + this.loadSearchParamsFromURL(); + this.dropdownManager.setDropdown(); + + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('beforeunload', this.cleanupWrapper); } + } - cleanup() { - this.unbindEvents(); - document.removeEventListener('beforeunload', this.cleanupWrapper); + cleanup() { + this.unbindEvents(); + document.removeEventListener('beforeunload', this.cleanupWrapper); - if (this.recentSearchesRoot) { - this.recentSearchesRoot.destroy(); - } + if (this.recentSearchesRoot) { + this.recentSearchesRoot.destroy(); } + } - bindEvents() { - this.handleFormSubmit = this.handleFormSubmit.bind(this); - this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); - this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this); - this.handleInputPlaceholderWrapper = this.handleInputPlaceholder.bind(this); - this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this); - this.checkForEnterWrapper = this.checkForEnter.bind(this); - this.onClearSearchWrapper = this.onClearSearch.bind(this); - this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); - this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this); - this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this); - this.editTokenWrapper = this.editToken.bind(this); - this.tokenChange = this.tokenChange.bind(this); - this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this); - this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this); - this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this); + bindEvents() { + this.handleFormSubmit = this.handleFormSubmit.bind(this); + this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); + this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this); + this.handleInputPlaceholderWrapper = this.handleInputPlaceholder.bind(this); + this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this); + this.checkForEnterWrapper = this.checkForEnter.bind(this); + this.onClearSearchWrapper = this.onClearSearch.bind(this); + this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); + this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this); + this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this); + this.editTokenWrapper = this.editToken.bind(this); + this.tokenChange = this.tokenChange.bind(this); + this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this); + this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this); + this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this); - this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit); - this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); - this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper); - this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper); - this.filteredSearchInput.addEventListener('input', this.handleInputVisualTokenWrapper); - this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper); - this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper); - this.filteredSearchInput.addEventListener('click', this.tokenChange); - this.filteredSearchInput.addEventListener('keyup', this.tokenChange); - this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper); - this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken); - this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper); - this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper); - document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens); - document.addEventListener('click', this.unselectEditTokensWrapper); - document.addEventListener('click', this.removeInputContainerFocusWrapper); - document.addEventListener('keydown', this.removeSelectedTokenWrapper); - eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper); - } + this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit); + this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); + this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper); + this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper); + this.filteredSearchInput.addEventListener('input', this.handleInputVisualTokenWrapper); + this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper); + this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper); + this.filteredSearchInput.addEventListener('click', this.tokenChange); + this.filteredSearchInput.addEventListener('keyup', this.tokenChange); + this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper); + this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken); + this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper); + this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper); + document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens); + document.addEventListener('click', this.unselectEditTokensWrapper); + document.addEventListener('click', this.removeInputContainerFocusWrapper); + document.addEventListener('keydown', this.removeSelectedTokenWrapper); + eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper); + } - unbindEvents() { - this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit); - this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); - this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper); - this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper); - this.filteredSearchInput.removeEventListener('input', this.handleInputVisualTokenWrapper); - this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper); - this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper); - this.filteredSearchInput.removeEventListener('click', this.tokenChange); - this.filteredSearchInput.removeEventListener('keyup', this.tokenChange); - this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper); - this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken); - this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper); - this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper); - document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens); - document.removeEventListener('click', this.unselectEditTokensWrapper); - document.removeEventListener('click', this.removeInputContainerFocusWrapper); - document.removeEventListener('keydown', this.removeSelectedTokenWrapper); - eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper); - } + unbindEvents() { + this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit); + this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); + this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper); + this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper); + this.filteredSearchInput.removeEventListener('input', this.handleInputVisualTokenWrapper); + this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper); + this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper); + this.filteredSearchInput.removeEventListener('click', this.tokenChange); + this.filteredSearchInput.removeEventListener('keyup', this.tokenChange); + this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper); + this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken); + this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper); + this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper); + document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens); + document.removeEventListener('click', this.unselectEditTokensWrapper); + document.removeEventListener('click', this.removeInputContainerFocusWrapper); + document.removeEventListener('keydown', this.removeSelectedTokenWrapper); + eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper); + } - checkForBackspace(e) { - // 8 = Backspace Key - // 46 = Delete Key - if (e.keyCode === 8 || e.keyCode === 46) { - const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + checkForBackspace(e) { + // 8 = Backspace Key + // 46 = Delete Key + if (e.keyCode === 8 || e.keyCode === 46) { + const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); - if (this.filteredSearchInput.value === '' && lastVisualToken) { - this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial(); - gl.FilteredSearchVisualTokens.removeLastTokenPartial(); - } - - // Reposition dropdown so that it is aligned with cursor - this.dropdownManager.updateCurrentDropdownOffset(); - } - } - - checkForEnter(e) { - if (e.keyCode === 38 || e.keyCode === 40) { - const selectionStart = this.filteredSearchInput.selectionStart; - - e.preventDefault(); - this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart); + if (this.filteredSearchInput.value === '' && lastVisualToken) { + this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial(); + gl.FilteredSearchVisualTokens.removeLastTokenPartial(); } - if (e.keyCode === 13) { - const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; - const dropdownEl = dropdown.element; - const activeElements = dropdownEl.querySelectorAll('.droplab-item-active'); - - e.preventDefault(); - - if (!activeElements.length) { - if (this.isHandledAsync) { - e.stopImmediatePropagation(); - - this.filteredSearchInput.blur(); - this.dropdownManager.resetDropdowns(); - } else { - // Prevent droplab from opening dropdown - this.dropdownManager.destroyDroplab(); - } - - this.search(); - } - } + // Reposition dropdown so that it is aligned with cursor + this.dropdownManager.updateCurrentDropdownOffset(); } + } - addInputContainerFocus() { - const inputContainer = this.filteredSearchInput.closest('.filtered-search-box'); + checkForEnter(e) { + if (e.keyCode === 38 || e.keyCode === 40) { + const selectionStart = this.filteredSearchInput.selectionStart; - if (inputContainer) { - inputContainer.classList.add('focus'); - } - } - - removeInputContainerFocus(e) { - const inputContainer = this.filteredSearchInput.closest('.filtered-search-box'); - const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target); - const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null; - const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null; - - if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown && - !isElementInStaticFilterDropdown && inputContainer) { - inputContainer.classList.remove('focus'); - } - } - - static selectToken(e) { - const button = e.target.closest('.selectable'); - - if (button) { - e.preventDefault(); - e.stopPropagation(); - gl.FilteredSearchVisualTokens.selectToken(button); - } - } - - unselectEditTokens(e) { - const inputContainer = this.container.querySelector('.filtered-search-box'); - const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target); - const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null; - const isElementTokensContainer = e.target.classList.contains('tokens-container'); - - if ((!isElementInFilteredSearch && !isElementInFilterDropdown) || isElementTokensContainer) { - gl.FilteredSearchVisualTokens.moveInputToTheRight(); - this.dropdownManager.resetDropdowns(); - } - } - - editToken(e) { - const token = e.target.closest('.js-visual-token'); - - if (token) { - gl.FilteredSearchVisualTokens.editToken(token); - this.tokenChange(); - } - } - - toggleClearSearchButton() { - const query = gl.DropdownUtils.getSearchQuery(); - const hidden = 'hidden'; - const hasHidden = this.clearSearchButton.classList.contains(hidden); - - if (query.length === 0 && !hasHidden) { - this.clearSearchButton.classList.add(hidden); - } else if (query.length && hasHidden) { - this.clearSearchButton.classList.remove(hidden); - } - } - - handleInputPlaceholder() { - const query = gl.DropdownUtils.getSearchQuery(); - const placeholder = 'Search or filter results...'; - const currentPlaceholder = this.filteredSearchInput.placeholder; - - if (query.length === 0 && currentPlaceholder !== placeholder) { - this.filteredSearchInput.placeholder = placeholder; - } else if (query.length > 0 && currentPlaceholder !== '') { - this.filteredSearchInput.placeholder = ''; - } - } - - removeSelectedToken(e) { - // 8 = Backspace Key - // 46 = Delete Key - if (e.keyCode === 8 || e.keyCode === 46) { - gl.FilteredSearchVisualTokens.removeSelectedToken(); - this.handleInputPlaceholder(); - this.toggleClearSearchButton(); - } - } - - onClearSearch(e) { e.preventDefault(); - this.clearSearch(); + this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart); } - clearSearch() { - this.filteredSearchInput.value = ''; + if (e.keyCode === 13) { + const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; + const dropdownEl = dropdown.element; + const activeElements = dropdownEl.querySelectorAll('.droplab-item-active'); - const removeElements = []; + e.preventDefault(); - [].forEach.call(this.tokensContainer.children, (t) => { - if (t.classList.contains('js-visual-token')) { - removeElements.push(t); + if (!activeElements.length) { + if (this.isHandledAsync) { + e.stopImmediatePropagation(); + + this.filteredSearchInput.blur(); + this.dropdownManager.resetDropdowns(); + } else { + // Prevent droplab from opening dropdown + this.dropdownManager.destroyDroplab(); } - }); - removeElements.forEach((el) => { - el.parentElement.removeChild(el); - }); - - this.clearSearchButton.classList.add('hidden'); - this.handleInputPlaceholder(); - - this.dropdownManager.resetDropdowns(); - - if (this.isHandledAsync) { this.search(); } } + } - handleInputVisualToken() { - const input = this.filteredSearchInput; - const { tokens, searchToken } - = gl.FilteredSearchTokenizer.processTokens(input.value); - const { isLastVisualTokenValid } - = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + addInputContainerFocus() { + const inputContainer = this.filteredSearchInput.closest('.filtered-search-box'); - if (isLastVisualTokenValid) { - tokens.forEach((t) => { - input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, ''); - gl.FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`); - }); - - const fragments = searchToken.split(':'); - if (fragments.length > 1) { - const inputValues = fragments[0].split(' '); - const tokenKey = inputValues.last(); - - if (inputValues.length > 1) { - inputValues.pop(); - const searchTerms = inputValues.join(' '); - - input.value = input.value.replace(searchTerms, ''); - gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms); - } - - gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenKey); - input.value = input.value.replace(`${tokenKey}:`, ''); - } - } else { - // Keep listening to token until we determine that the user is done typing the token value - const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g; - - if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') { - gl.FilteredSearchVisualTokens.addFilterVisualToken(searchToken); - - // Trim the last space as seen in the if statement above - input.value = input.value.replace(searchToken, '').trim(); - } - } + if (inputContainer) { + inputContainer.classList.add('focus'); } + } - handleFormSubmit(e) { + removeInputContainerFocus(e) { + const inputContainer = this.filteredSearchInput.closest('.filtered-search-box'); + const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target); + const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null; + const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null; + + if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown && + !isElementInStaticFilterDropdown && inputContainer) { + inputContainer.classList.remove('focus'); + } + } + + static selectToken(e) { + const button = e.target.closest('.selectable'); + + if (button) { e.preventDefault(); - this.search(); + e.stopPropagation(); + gl.FilteredSearchVisualTokens.selectToken(button); } + } - saveCurrentSearchQuery() { - // Don't save before we have fetched the already saved searches - this.fetchingRecentSearchesPromise.then(() => { - const searchQuery = gl.DropdownUtils.getSearchQuery(); - if (searchQuery.length > 0) { - const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery); - this.recentSearchesService.save(resultantSearches); - } - }); + unselectEditTokens(e) { + const inputContainer = this.container.querySelector('.filtered-search-box'); + const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target); + const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null; + const isElementTokensContainer = e.target.classList.contains('tokens-container'); + + if ((!isElementInFilteredSearch && !isElementInFilterDropdown) || isElementTokensContainer) { + gl.FilteredSearchVisualTokens.moveInputToTheRight(); + this.dropdownManager.resetDropdowns(); } + } - loadSearchParamsFromURL() { - const params = gl.utils.getUrlParamsArray(); - const usernameParams = this.getUsernameParams(); - let hasFilteredSearch = false; + editToken(e) { + const token = e.target.closest('.js-visual-token'); - params.forEach((p) => { - const split = p.split('='); - const keyParam = decodeURIComponent(split[0]); - const value = split[1]; + if (token) { + gl.FilteredSearchVisualTokens.editToken(token); + this.tokenChange(); + } + } - // Check if it matches edge conditions listed in this.filteredSearchTokenKeys - const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p); + toggleClearSearchButton() { + const query = gl.DropdownUtils.getSearchQuery(); + const hidden = 'hidden'; + const hasHidden = this.clearSearchButton.classList.contains(hidden); - if (condition) { - hasFilteredSearch = true; - gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value); - } else { - // Sanitize value since URL converts spaces into + - // Replace before decode so that we know what was originally + versus the encoded + - const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; - const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam); + if (query.length === 0 && !hasHidden) { + this.clearSearchButton.classList.add(hidden); + } else if (query.length && hasHidden) { + this.clearSearchButton.classList.remove(hidden); + } + } - if (match) { - const indexOf = keyParam.indexOf('_'); - const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam; - const symbol = match.symbol; - let quotationsToUse = ''; + handleInputPlaceholder() { + const query = gl.DropdownUtils.getSearchQuery(); + const placeholder = 'Search or filter results...'; + const currentPlaceholder = this.filteredSearchInput.placeholder; - if (sanitizedValue.indexOf(' ') !== -1) { - // Prefer ", but use ' if required - quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; - } + if (query.length === 0 && currentPlaceholder !== placeholder) { + this.filteredSearchInput.placeholder = placeholder; + } else if (query.length > 0 && currentPlaceholder !== '') { + this.filteredSearchInput.placeholder = ''; + } + } - hasFilteredSearch = true; - gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`); - } else if (!match && keyParam === 'assignee_id') { - const id = parseInt(value, 10); - if (usernameParams[id]) { - hasFilteredSearch = true; - gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`); - } - } else if (!match && keyParam === 'author_id') { - const id = parseInt(value, 10); - if (usernameParams[id]) { - hasFilteredSearch = true; - gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`); - } - } else if (!match && keyParam === 'search') { - hasFilteredSearch = true; - this.filteredSearchInput.value = sanitizedValue; - } - } - }); + removeSelectedToken(e) { + // 8 = Backspace Key + // 46 = Delete Key + if (e.keyCode === 8 || e.keyCode === 46) { + gl.FilteredSearchVisualTokens.removeSelectedToken(); + this.handleInputPlaceholder(); + this.toggleClearSearchButton(); + } + } - this.saveCurrentSearchQuery(); + onClearSearch(e) { + e.preventDefault(); + this.clearSearch(); + } - if (hasFilteredSearch) { - this.clearSearchButton.classList.remove('hidden'); - this.handleInputPlaceholder(); + clearSearch() { + this.filteredSearchInput.value = ''; + + const removeElements = []; + + [].forEach.call(this.tokensContainer.children, (t) => { + if (t.classList.contains('js-visual-token')) { + removeElements.push(t); } - } + }); - search() { - const paths = []; - const searchQuery = gl.DropdownUtils.getSearchQuery(); + removeElements.forEach((el) => { + el.parentElement.removeChild(el); + }); - this.saveCurrentSearchQuery(); + this.clearSearchButton.classList.add('hidden'); + this.handleInputPlaceholder(); - const { tokens, searchToken } - = this.tokenizer.processTokens(searchQuery); - const currentState = gl.utils.getParameterByName('state') || 'opened'; - paths.push(`state=${currentState}`); + this.dropdownManager.resetDropdowns(); - tokens.forEach((token) => { - const condition = this.filteredSearchTokenKeys - .searchByConditionKeyValue(token.key, token.value.toLowerCase()); - const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {}; - const keyParam = param ? `${token.key}_${param}` : token.key; - let tokenPath = ''; - - if (condition) { - tokenPath = condition.url; - } else { - let tokenValue = token.value; - - if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') || - (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) { - tokenValue = tokenValue.slice(1, tokenValue.length - 1); - } - - tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`; - } - - paths.push(tokenPath); - }); - - if (searchToken) { - const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+'); - paths.push(`search=${sanitized}`); - } - - const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`; - - if (this.updateObject) { - this.updateObject(parameterizedUrl); - } else { - gl.utils.visitUrl(parameterizedUrl); - } - } - - getUsernameParams() { - const usernamesById = {}; - try { - const attribute = this.filteredSearchInput.getAttribute('data-username-params'); - JSON.parse(attribute).forEach((user) => { - usernamesById[user.id] = user.username; - }); - } catch (e) { - // do nothing - } - return usernamesById; - } - - tokenChange() { - const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; - - if (dropdown) { - const currentDropdownRef = dropdown.reference; - - this.setDropdownWrapper(); - currentDropdownRef.dispatchInputEvent(); - } - } - - onrecentSearchesItemSelected(text) { - this.clearSearch(); - this.filteredSearchInput.value = text; - this.filteredSearchInput.dispatchEvent(new CustomEvent('input')); + if (this.isHandledAsync) { this.search(); } } - window.gl = window.gl || {}; - gl.FilteredSearchManager = FilteredSearchManager; -})(); + handleInputVisualToken() { + const input = this.filteredSearchInput; + const { tokens, searchToken } + = gl.FilteredSearchTokenizer.processTokens(input.value); + const { isLastVisualTokenValid } + = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + + if (isLastVisualTokenValid) { + tokens.forEach((t) => { + input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, ''); + gl.FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`); + }); + + const fragments = searchToken.split(':'); + if (fragments.length > 1) { + const inputValues = fragments[0].split(' '); + const tokenKey = inputValues.last(); + + if (inputValues.length > 1) { + inputValues.pop(); + const searchTerms = inputValues.join(' '); + + input.value = input.value.replace(searchTerms, ''); + gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms); + } + + gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenKey); + input.value = input.value.replace(`${tokenKey}:`, ''); + } + } else { + // Keep listening to token until we determine that the user is done typing the token value + const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g; + + if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') { + gl.FilteredSearchVisualTokens.addFilterVisualToken(searchToken); + + // Trim the last space as seen in the if statement above + input.value = input.value.replace(searchToken, '').trim(); + } + } + } + + handleFormSubmit(e) { + e.preventDefault(); + this.search(); + } + + saveCurrentSearchQuery() { + // Don't save before we have fetched the already saved searches + this.fetchingRecentSearchesPromise.then(() => { + const searchQuery = gl.DropdownUtils.getSearchQuery(); + if (searchQuery.length > 0) { + const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery); + this.recentSearchesService.save(resultantSearches); + } + }); + } + + loadSearchParamsFromURL() { + const params = gl.utils.getUrlParamsArray(); + const usernameParams = this.getUsernameParams(); + let hasFilteredSearch = false; + + params.forEach((p) => { + const split = p.split('='); + const keyParam = decodeURIComponent(split[0]); + const value = split[1]; + + // Check if it matches edge conditions listed in this.filteredSearchTokenKeys + const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p); + + if (condition) { + hasFilteredSearch = true; + gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value); + } else { + // Sanitize value since URL converts spaces into + + // Replace before decode so that we know what was originally + versus the encoded + + const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; + const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam); + + if (match) { + const indexOf = keyParam.indexOf('_'); + const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam; + const symbol = match.symbol; + let quotationsToUse = ''; + + if (sanitizedValue.indexOf(' ') !== -1) { + // Prefer ", but use ' if required + quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; + } + + hasFilteredSearch = true; + gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`); + } else if (!match && keyParam === 'assignee_id') { + const id = parseInt(value, 10); + if (usernameParams[id]) { + hasFilteredSearch = true; + gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`); + } + } else if (!match && keyParam === 'author_id') { + const id = parseInt(value, 10); + if (usernameParams[id]) { + hasFilteredSearch = true; + gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`); + } + } else if (!match && keyParam === 'search') { + hasFilteredSearch = true; + this.filteredSearchInput.value = sanitizedValue; + } + } + }); + + this.saveCurrentSearchQuery(); + + if (hasFilteredSearch) { + this.clearSearchButton.classList.remove('hidden'); + this.handleInputPlaceholder(); + } + } + + search() { + const paths = []; + const searchQuery = gl.DropdownUtils.getSearchQuery(); + + this.saveCurrentSearchQuery(); + + const { tokens, searchToken } + = this.tokenizer.processTokens(searchQuery); + const currentState = gl.utils.getParameterByName('state') || 'opened'; + paths.push(`state=${currentState}`); + + tokens.forEach((token) => { + const condition = this.filteredSearchTokenKeys + .searchByConditionKeyValue(token.key, token.value.toLowerCase()); + const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {}; + const keyParam = param ? `${token.key}_${param}` : token.key; + let tokenPath = ''; + + if (condition) { + tokenPath = condition.url; + } else { + let tokenValue = token.value; + + if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') || + (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) { + tokenValue = tokenValue.slice(1, tokenValue.length - 1); + } + + tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`; + } + + paths.push(tokenPath); + }); + + if (searchToken) { + const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+'); + paths.push(`search=${sanitized}`); + } + + const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`; + + if (this.updateObject) { + this.updateObject(parameterizedUrl); + } else { + gl.utils.visitUrl(parameterizedUrl); + } + } + + getUsernameParams() { + const usernamesById = {}; + try { + const attribute = this.filteredSearchInput.getAttribute('data-username-params'); + JSON.parse(attribute).forEach((user) => { + usernamesById[user.id] = user.username; + }); + } catch (e) { + // do nothing + } + return usernamesById; + } + + tokenChange() { + const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; + + if (dropdown) { + const currentDropdownRef = dropdown.reference; + + this.setDropdownWrapper(); + currentDropdownRef.dispatchInputEvent(); + } + } + + onrecentSearchesItemSelected(text) { + this.clearSearch(); + this.filteredSearchInput.value = text; + this.filteredSearchInput.dispatchEvent(new CustomEvent('input')); + this.search(); + } +} + +window.gl = window.gl || {}; +gl.FilteredSearchManager = FilteredSearchManager; diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js index 6d5df86f2a5..1abad9d1b73 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -1,100 +1,98 @@ -(() => { - const tokenKeys = [{ - key: 'author', - type: 'string', - param: 'username', - symbol: '@', - }, { - key: 'assignee', - type: 'string', - param: 'username', - symbol: '@', - }, { - key: 'milestone', - type: 'string', - param: 'title', - symbol: '%', - }, { - key: 'label', - type: 'array', - param: 'name[]', - symbol: '~', - }]; +const tokenKeys = [{ + key: 'author', + type: 'string', + param: 'username', + symbol: '@', +}, { + key: 'assignee', + type: 'string', + param: 'username', + symbol: '@', +}, { + key: 'milestone', + type: 'string', + param: 'title', + symbol: '%', +}, { + key: 'label', + type: 'array', + param: 'name[]', + symbol: '~', +}]; - const alternativeTokenKeys = [{ - key: 'label', - type: 'string', - param: 'name', - symbol: '~', - }]; +const alternativeTokenKeys = [{ + key: 'label', + type: 'string', + param: 'name', + symbol: '~', +}]; - const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); +const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); - const conditions = [{ - url: 'assignee_id=0', - tokenKey: 'assignee', - value: 'none', - }, { - url: 'milestone_title=No+Milestone', - tokenKey: 'milestone', - value: 'none', - }, { - url: 'milestone_title=%23upcoming', - tokenKey: 'milestone', - value: 'upcoming', - }, { - url: 'milestone_title=%23started', - tokenKey: 'milestone', - value: 'started', - }, { - url: 'label_name[]=No+Label', - tokenKey: 'label', - value: 'none', - }]; +const conditions = [{ + url: 'assignee_id=0', + tokenKey: 'assignee', + value: 'none', +}, { + url: 'milestone_title=No+Milestone', + tokenKey: 'milestone', + value: 'none', +}, { + url: 'milestone_title=%23upcoming', + tokenKey: 'milestone', + value: 'upcoming', +}, { + url: 'milestone_title=%23started', + tokenKey: 'milestone', + value: 'started', +}, { + url: 'label_name[]=No+Label', + tokenKey: 'label', + value: 'none', +}]; - class FilteredSearchTokenKeys { - static get() { - return tokenKeys; - } - - static getAlternatives() { - return alternativeTokenKeys; - } - - static getConditions() { - return conditions; - } - - static searchByKey(key) { - return tokenKeys.find(tokenKey => tokenKey.key === key) || null; - } - - static searchBySymbol(symbol) { - return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null; - } - - static searchByKeyParam(keyParam) { - return tokenKeysWithAlternative.find((tokenKey) => { - let tokenKeyParam = tokenKey.key; - - if (tokenKey.param) { - tokenKeyParam += `_${tokenKey.param}`; - } - - return keyParam === tokenKeyParam; - }) || null; - } - - static searchByConditionUrl(url) { - return conditions.find(condition => condition.url === url) || null; - } - - static searchByConditionKeyValue(key, value) { - return conditions - .find(condition => condition.tokenKey === key && condition.value === value) || null; - } +class FilteredSearchTokenKeys { + static get() { + return tokenKeys; } - window.gl = window.gl || {}; - gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys; -})(); + static getAlternatives() { + return alternativeTokenKeys; + } + + static getConditions() { + return conditions; + } + + static searchByKey(key) { + return tokenKeys.find(tokenKey => tokenKey.key === key) || null; + } + + static searchBySymbol(symbol) { + return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null; + } + + static searchByKeyParam(keyParam) { + return tokenKeysWithAlternative.find((tokenKey) => { + let tokenKeyParam = tokenKey.key; + + if (tokenKey.param) { + tokenKeyParam += `_${tokenKey.param}`; + } + + return keyParam === tokenKeyParam; + }) || null; + } + + static searchByConditionUrl(url) { + return conditions.find(condition => condition.url === url) || null; + } + + static searchByConditionKeyValue(key, value) { + return conditions + .find(condition => condition.tokenKey === key && condition.value === value) || null; + } +} + +window.gl = window.gl || {}; +gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys; diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js index a2729dc0e95..2808e4b238a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js @@ -1,58 +1,56 @@ require('./filtered_search_token_keys'); -(() => { - class FilteredSearchTokenizer { - static processTokens(input) { - const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key); - // Regex extracts `(token):(symbol)(value)` - // Values that start with a double quote must end in a double quote (same for single) - const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g'); - const tokens = []; - const tokenIndexes = []; // stores key+value for simple search - let lastToken = null; - const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { - let tokenValue = v1 || v2 || v3; - let tokenSymbol = symbol; - let tokenIndex = ''; +class FilteredSearchTokenizer { + static processTokens(input) { + const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key); + // Regex extracts `(token):(symbol)(value)` + // Values that start with a double quote must end in a double quote (same for single) + const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g'); + const tokens = []; + const tokenIndexes = []; // stores key+value for simple search + let lastToken = null; + const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { + let tokenValue = v1 || v2 || v3; + let tokenSymbol = symbol; + let tokenIndex = ''; - if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') { - tokenSymbol = tokenValue; - tokenValue = ''; - } - - tokenIndex = `${key}:${tokenValue}`; - - // Prevent adding duplicates - if (tokenIndexes.indexOf(tokenIndex) === -1) { - tokenIndexes.push(tokenIndex); - - tokens.push({ - key, - value: tokenValue || '', - symbol: tokenSymbol || '', - }); - } - - return ''; - }).replace(/\s{2,}/g, ' ').trim() || ''; - - if (tokens.length > 0) { - const last = tokens[tokens.length - 1]; - const lastString = `${last.key}:${last.symbol}${last.value}`; - lastToken = input.lastIndexOf(lastString) === - input.length - lastString.length ? last : searchToken; - } else { - lastToken = searchToken; + if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') { + tokenSymbol = tokenValue; + tokenValue = ''; } - return { - tokens, - lastToken, - searchToken, - }; - } - } + tokenIndex = `${key}:${tokenValue}`; - window.gl = window.gl || {}; - gl.FilteredSearchTokenizer = FilteredSearchTokenizer; -})(); + // Prevent adding duplicates + if (tokenIndexes.indexOf(tokenIndex) === -1) { + tokenIndexes.push(tokenIndex); + + tokens.push({ + key, + value: tokenValue || '', + symbol: tokenSymbol || '', + }); + } + + return ''; + }).replace(/\s{2,}/g, ' ').trim() || ''; + + if (tokens.length > 0) { + const last = tokens[tokens.length - 1]; + const lastString = `${last.key}:${last.symbol}${last.value}`; + lastToken = input.lastIndexOf(lastString) === + input.length - lastString.length ? last : searchToken; + } else { + lastToken = searchToken; + } + + return { + tokens, + lastToken, + searchToken, + }; + } +} + +window.gl = window.gl || {}; +gl.FilteredSearchTokenizer = FilteredSearchTokenizer; diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index 2b1fe5e3eef..3f92fe4701e 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -3,69 +3,67 @@ require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_dropdown'); require('~/filtered_search/dropdown_user'); -(() => { - describe('Dropdown User', () => { - describe('getSearchInput', () => { - let dropdownUser; +describe('Dropdown User', () => { + describe('getSearchInput', () => { + let dropdownUser; - beforeEach(() => { - spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); - spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); - spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {}); + beforeEach(() => { + spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); + spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {}); - dropdownUser = new gl.DropdownUser(); - }); - - it('should not return the double quote found in value', () => { - spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ - lastToken: '"johnny appleseed', - }); - - expect(dropdownUser.getSearchInput()).toBe('johnny appleseed'); - }); - - it('should not return the single quote found in value', () => { - spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ - lastToken: '\'larry boy', - }); - - expect(dropdownUser.getSearchInput()).toBe('larry boy'); - }); + dropdownUser = new gl.DropdownUser(); }); - describe('config AjaxFilter\'s endpoint', () => { - beforeEach(() => { - spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); - spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); + it('should not return the double quote found in value', () => { + spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ + lastToken: '"johnny appleseed', }); - it('should return endpoint', () => { - window.gon = { - relative_url_root: '', - }; - const dropdown = new gl.DropdownUser(); + expect(dropdownUser.getSearchInput()).toBe('johnny appleseed'); + }); - expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json'); + it('should not return the single quote found in value', () => { + spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ + lastToken: '\'larry boy', }); - it('should return endpoint when relative_url_root is undefined', () => { - const dropdown = new gl.DropdownUser(); - - expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json'); - }); - - it('should return endpoint with relative url when available', () => { - window.gon = { - relative_url_root: '/gitlab_directory', - }; - const dropdown = new gl.DropdownUser(); - - expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json'); - }); - - afterEach(() => { - window.gon = {}; - }); + expect(dropdownUser.getSearchInput()).toBe('larry boy'); }); }); -})(); + + describe('config AjaxFilter\'s endpoint', () => { + beforeEach(() => { + spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); + }); + + it('should return endpoint', () => { + window.gon = { + relative_url_root: '', + }; + const dropdown = new gl.DropdownUser(); + + expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json'); + }); + + it('should return endpoint when relative_url_root is undefined', () => { + const dropdown = new gl.DropdownUser(); + + expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json'); + }); + + it('should return endpoint with relative url when available', () => { + window.gon = { + relative_url_root: '/gitlab_directory', + }; + const dropdown = new gl.DropdownUser(); + + expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json'); + }); + + afterEach(() => { + window.gon = {}; + }); + }); +}); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index e6538020896..c820c955172 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -3,308 +3,306 @@ require('~/filtered_search/dropdown_utils'); require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_dropdown_manager'); -(() => { - describe('Dropdown Utils', () => { - describe('getEscapedText', () => { - it('should return same word when it has no space', () => { - const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace'); - expect(escaped).toBe('textWithoutSpace'); - }); - - it('should escape with double quotes', () => { - let escaped = gl.DropdownUtils.getEscapedText('text with space'); - expect(escaped).toBe('"text with space"'); - - escaped = gl.DropdownUtils.getEscapedText('won\'t fix'); - expect(escaped).toBe('"won\'t fix"'); - }); - - it('should escape with single quotes', () => { - const escaped = gl.DropdownUtils.getEscapedText('won"t fix'); - expect(escaped).toBe('\'won"t fix\''); - }); - - it('should escape with single quotes by default', () => { - const escaped = gl.DropdownUtils.getEscapedText('won"t\' fix'); - expect(escaped).toBe('\'won"t\' fix\''); - }); +describe('Dropdown Utils', () => { + describe('getEscapedText', () => { + it('should return same word when it has no space', () => { + const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace'); + expect(escaped).toBe('textWithoutSpace'); }); - describe('filterWithSymbol', () => { - let input; - const item = { - title: '@root', + it('should escape with double quotes', () => { + let escaped = gl.DropdownUtils.getEscapedText('text with space'); + expect(escaped).toBe('"text with space"'); + + escaped = gl.DropdownUtils.getEscapedText('won\'t fix'); + expect(escaped).toBe('"won\'t fix"'); + }); + + it('should escape with single quotes', () => { + const escaped = gl.DropdownUtils.getEscapedText('won"t fix'); + expect(escaped).toBe('\'won"t fix\''); + }); + + it('should escape with single quotes by default', () => { + const escaped = gl.DropdownUtils.getEscapedText('won"t\' fix'); + expect(escaped).toBe('\'won"t\' fix\''); + }); + }); + + describe('filterWithSymbol', () => { + let input; + const item = { + title: '@root', + }; + + beforeEach(() => { + setFixtures(` + + `); + + input = document.getElementById('test'); + }); + + it('should filter without symbol', () => { + input.value = 'roo'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with symbol', () => { + input.value = '@roo'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + describe('filters multiple word title', () => { + const multipleWordItem = { + title: 'Community Contributions', }; - beforeEach(() => { - setFixtures(` - - `); + it('should filter with double quote', () => { + input.value = '"'; - input = document.getElementById('test'); - }); - - it('should filter without symbol', () => { - input.value = 'roo'; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); expect(updatedItem.droplab_hidden).toBe(false); }); - it('should filter with symbol', () => { - input.value = '@roo'; + it('should filter with double quote and symbol', () => { + input.value = '~"'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); expect(updatedItem.droplab_hidden).toBe(false); }); - describe('filters multiple word title', () => { - const multipleWordItem = { - title: 'Community Contributions', - }; + it('should filter with double quote and multiple words', () => { + input.value = '"community con'; - it('should filter with double quote', () => { - input.value = '"'; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); - - it('should filter with double quote and symbol', () => { - input.value = '~"'; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); - - it('should filter with double quote and multiple words', () => { - input.value = '"community con'; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); - - it('should filter with double quote, symbol and multiple words', () => { - input.value = '~"community con'; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); - - it('should filter with single quote', () => { - input.value = '\''; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); - - it('should filter with single quote and symbol', () => { - input.value = '~\''; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); - - it('should filter with single quote and multiple words', () => { - input.value = '\'community con'; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); - - it('should filter with single quote, symbol and multiple words', () => { - input.value = '~\'community con'; - - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); - }); - }); - - describe('filterHint', () => { - let input; - - beforeEach(() => { - setFixtures(` -
    -
  • - -
  • -
- `); - - input = document.getElementById('test'); - }); - - it('should filter', () => { - input.value = 'l'; - let updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'label', - }); - expect(updatedItem.droplab_hidden).toBe(false); - - input.value = 'o'; - updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'label', - }); - expect(updatedItem.droplab_hidden).toBe(true); - }); - - it('should return droplab_hidden false when item has no hint', () => { - const updatedItem = gl.DropdownUtils.filterHint(input, {}, ''); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); expect(updatedItem.droplab_hidden).toBe(false); }); - it('should allow multiple if item.type is array', () => { - input.value = 'label:~first la'; - const updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'label', - type: 'array', - }); + it('should filter with double quote, symbol and multiple words', () => { + input.value = '~"community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); expect(updatedItem.droplab_hidden).toBe(false); }); - it('should prevent multiple if item.type is not array', () => { - input.value = 'milestone:~first mile'; - let updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'milestone', - }); - expect(updatedItem.droplab_hidden).toBe(true); + it('should filter with single quote', () => { + input.value = '\''; - updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'milestone', - type: 'string', - }); - expect(updatedItem.droplab_hidden).toBe(true); - }); - }); - - describe('setDataValueIfSelected', () => { - beforeEach(() => { - spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput') - .and.callFake(() => {}); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); }); - it('calls addWordToInput when dataValue exists', () => { - const selected = { - getAttribute: () => 'value', - }; + it('should filter with single quote and symbol', () => { + input.value = '~\''; - gl.DropdownUtils.setDataValueIfSelected(null, selected); - expect(gl.FilteredSearchDropdownManager.addWordToInput.calls.count()).toEqual(1); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); }); - it('returns true when dataValue exists', () => { - const selected = { - getAttribute: () => 'value', - }; + it('should filter with single quote and multiple words', () => { + input.value = '\'community con'; - const result = gl.DropdownUtils.setDataValueIfSelected(null, selected); - expect(result).toBe(true); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); }); - it('returns false when dataValue does not exist', () => { - const selected = { - getAttribute: () => null, - }; + it('should filter with single quote, symbol and multiple words', () => { + input.value = '~\'community con'; - const result = gl.DropdownUtils.setDataValueIfSelected(null, selected); - expect(result).toBe(false); - }); - }); - - describe('getInputSelectionPosition', () => { - describe('word with trailing spaces', () => { - const value = 'label:none '; - - it('should return selectionStart when cursor is at the trailing space', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 11, - value, - }); - - expect(left).toBe(11); - expect(right).toBe(11); - }); - - it('should return input when cursor is at the start of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 0, - value, - }); - - expect(left).toBe(0); - expect(right).toBe(10); - }); - - it('should return input when cursor is at the middle of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 7, - value, - }); - - expect(left).toBe(0); - expect(right).toBe(10); - }); - - it('should return input when cursor is at the end of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 10, - value, - }); - - expect(left).toBe(0); - expect(right).toBe(10); - }); - }); - - describe('multiple words', () => { - const value = 'label:~"Community Contribution"'; - - it('should return input when cursor is after the first word', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 17, - value, - }); - - expect(left).toBe(0); - expect(right).toBe(31); - }); - - it('should return input when cursor is before the second word', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 18, - value, - }); - - expect(left).toBe(0); - expect(right).toBe(31); - }); - }); - - describe('incomplete multiple words', () => { - const value = 'label:~"Community Contribution'; - - it('should return entire input when cursor is at the start of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 0, - value, - }); - - expect(left).toBe(0); - expect(right).toBe(30); - }); - - it('should return entire input when cursor is at the end of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 30, - value, - }); - - expect(left).toBe(0); - expect(right).toBe(30); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); }); }); }); -})(); + + describe('filterHint', () => { + let input; + + beforeEach(() => { + setFixtures(` +
    +
  • + +
  • +
+ `); + + input = document.getElementById('test'); + }); + + it('should filter', () => { + input.value = 'l'; + let updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'label', + }); + expect(updatedItem.droplab_hidden).toBe(false); + + input.value = 'o'; + updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'label', + }); + expect(updatedItem.droplab_hidden).toBe(true); + }); + + it('should return droplab_hidden false when item has no hint', () => { + const updatedItem = gl.DropdownUtils.filterHint(input, {}, ''); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should allow multiple if item.type is array', () => { + input.value = 'label:~first la'; + const updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'label', + type: 'array', + }); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should prevent multiple if item.type is not array', () => { + input.value = 'milestone:~first mile'; + let updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'milestone', + }); + expect(updatedItem.droplab_hidden).toBe(true); + + updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'milestone', + type: 'string', + }); + expect(updatedItem.droplab_hidden).toBe(true); + }); + }); + + describe('setDataValueIfSelected', () => { + beforeEach(() => { + spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput') + .and.callFake(() => {}); + }); + + it('calls addWordToInput when dataValue exists', () => { + const selected = { + getAttribute: () => 'value', + }; + + gl.DropdownUtils.setDataValueIfSelected(null, selected); + expect(gl.FilteredSearchDropdownManager.addWordToInput.calls.count()).toEqual(1); + }); + + it('returns true when dataValue exists', () => { + const selected = { + getAttribute: () => 'value', + }; + + const result = gl.DropdownUtils.setDataValueIfSelected(null, selected); + expect(result).toBe(true); + }); + + it('returns false when dataValue does not exist', () => { + const selected = { + getAttribute: () => null, + }; + + const result = gl.DropdownUtils.setDataValueIfSelected(null, selected); + expect(result).toBe(false); + }); + }); + + describe('getInputSelectionPosition', () => { + describe('word with trailing spaces', () => { + const value = 'label:none '; + + it('should return selectionStart when cursor is at the trailing space', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 11, + value, + }); + + expect(left).toBe(11); + expect(right).toBe(11); + }); + + it('should return input when cursor is at the start of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 0, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + + it('should return input when cursor is at the middle of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 7, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + + it('should return input when cursor is at the end of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 10, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + }); + + describe('multiple words', () => { + const value = 'label:~"Community Contribution"'; + + it('should return input when cursor is after the first word', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 17, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(31); + }); + + it('should return input when cursor is before the second word', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 18, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(31); + }); + }); + + describe('incomplete multiple words', () => { + const value = 'label:~"Community Contribution'; + + it('should return entire input when cursor is at the start of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 0, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(30); + }); + + it('should return entire input when cursor is at the end of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 30, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(30); + }); + }); + }); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js index a1da3396d7b..17bf8932489 100644 --- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js @@ -3,99 +3,97 @@ require('~/filtered_search/filtered_search_visual_tokens'); require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_dropdown_manager'); -(() => { - describe('Filtered Search Dropdown Manager', () => { - describe('addWordToInput', () => { - function getInputValue() { - return document.querySelector('.filtered-search').value; - } +describe('Filtered Search Dropdown Manager', () => { + describe('addWordToInput', () => { + function getInputValue() { + return document.querySelector('.filtered-search').value; + } - function setInputValue(value) { - document.querySelector('.filtered-search').value = value; - } + function setInputValue(value) { + document.querySelector('.filtered-search').value = value; + } - beforeEach(() => { - setFixtures(` -
    -
  • - -
  • -
- `); + beforeEach(() => { + setFixtures(` +
    +
  • + +
  • +
+ `); + }); + + describe('input has no existing value', () => { + it('should add just tokenName', () => { + gl.FilteredSearchDropdownManager.addWordToInput('milestone'); + + const token = document.querySelector('.tokens-container .js-visual-token'); + + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('milestone'); + expect(getInputValue()).toBe(''); }); - describe('input has no existing value', () => { - it('should add just tokenName', () => { - gl.FilteredSearchDropdownManager.addWordToInput('milestone'); + it('should add tokenName and tokenValue', () => { + gl.FilteredSearchDropdownManager.addWordToInput('label'); - const token = document.querySelector('.tokens-container .js-visual-token'); + let token = document.querySelector('.tokens-container .js-visual-token'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('milestone'); - expect(getInputValue()).toBe(''); - }); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('label'); + expect(getInputValue()).toBe(''); - it('should add tokenName and tokenValue', () => { - gl.FilteredSearchDropdownManager.addWordToInput('label'); + gl.FilteredSearchDropdownManager.addWordToInput('label', 'none'); + // We have to get that reference again + // Because gl.FilteredSearchDropdownManager deletes the previous token + token = document.querySelector('.tokens-container .js-visual-token'); - let token = document.querySelector('.tokens-container .js-visual-token'); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('label'); + expect(token.querySelector('.value').innerText).toBe('none'); + expect(getInputValue()).toBe(''); + }); + }); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('label'); - expect(getInputValue()).toBe(''); + describe('input has existing value', () => { + it('should be able to just add tokenName', () => { + setInputValue('a'); + gl.FilteredSearchDropdownManager.addWordToInput('author'); - gl.FilteredSearchDropdownManager.addWordToInput('label', 'none'); - // We have to get that reference again - // Because gl.FilteredSearchDropdownManager deletes the previous token - token = document.querySelector('.tokens-container .js-visual-token'); + const token = document.querySelector('.tokens-container .js-visual-token'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('label'); - expect(token.querySelector('.value').innerText).toBe('none'); - expect(getInputValue()).toBe(''); - }); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('author'); + expect(getInputValue()).toBe(''); }); - describe('input has existing value', () => { - it('should be able to just add tokenName', () => { - setInputValue('a'); - gl.FilteredSearchDropdownManager.addWordToInput('author'); + it('should replace tokenValue', () => { + gl.FilteredSearchDropdownManager.addWordToInput('author'); - const token = document.querySelector('.tokens-container .js-visual-token'); + setInputValue('roo'); + gl.FilteredSearchDropdownManager.addWordToInput(null, '@root'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('author'); - expect(getInputValue()).toBe(''); - }); + const token = document.querySelector('.tokens-container .js-visual-token'); - it('should replace tokenValue', () => { - gl.FilteredSearchDropdownManager.addWordToInput('author'); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('author'); + expect(token.querySelector('.value').innerText).toBe('@root'); + expect(getInputValue()).toBe(''); + }); - setInputValue('roo'); - gl.FilteredSearchDropdownManager.addWordToInput(null, '@root'); + it('should add tokenValues containing spaces', () => { + gl.FilteredSearchDropdownManager.addWordToInput('label'); - const token = document.querySelector('.tokens-container .js-visual-token'); + setInputValue('"test '); + gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\''); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('author'); - expect(token.querySelector('.value').innerText).toBe('@root'); - expect(getInputValue()).toBe(''); - }); + const token = document.querySelector('.tokens-container .js-visual-token'); - it('should add tokenValues containing spaces', () => { - gl.FilteredSearchDropdownManager.addWordToInput('label'); - - setInputValue('"test '); - gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\''); - - const token = document.querySelector('.tokens-container .js-visual-token'); - - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('label'); - expect(token.querySelector('.value').innerText).toBe('~\'"test me"\''); - expect(getInputValue()).toBe(''); - }); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('label'); + expect(token.querySelector('.value').innerText).toBe('~\'"test me"\''); + expect(getInputValue()).toBe(''); }); }); }); -})(); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index 97af681429b..6683489f63c 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -6,271 +6,269 @@ require('~/filtered_search/filtered_search_dropdown_manager'); require('~/filtered_search/filtered_search_manager'); const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper'); -(() => { - describe('Filtered Search Manager', () => { - let input; - let manager; - let tokensContainer; - const placeholder = 'Search or filter results...'; +describe('Filtered Search Manager', () => { + let input; + let manager; + let tokensContainer; + const placeholder = 'Search or filter results...'; - function dispatchBackspaceEvent(element, eventType) { - const backspaceKey = 8; - const event = new Event(eventType); - event.keyCode = backspaceKey; - element.dispatchEvent(event); - } + function dispatchBackspaceEvent(element, eventType) { + const backspaceKey = 8; + const event = new Event(eventType); + event.keyCode = backspaceKey; + element.dispatchEvent(event); + } - function dispatchDeleteEvent(element, eventType) { - const deleteKey = 46; - const event = new Event(eventType); - event.keyCode = deleteKey; - element.dispatchEvent(event); - } + function dispatchDeleteEvent(element, eventType) { + const deleteKey = 46; + const event = new Event(eventType); + event.keyCode = deleteKey; + element.dispatchEvent(event); + } - beforeEach(() => { - setFixtures(` - + beforeEach(() => { + setFixtures(` + + `); + + spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); + spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {}); + spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {}); + spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {}); + spyOn(gl.utils, 'getParameterByName').and.returnValue(null); + spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough(); + + input = document.querySelector('.filtered-search'); + tokensContainer = document.querySelector('.tokens-container'); + manager = new gl.FilteredSearchManager(); + }); + + afterEach(() => { + manager.cleanup(); + }); + + describe('search', () => { + const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened'; + + it('should search with a single word', (done) => { + input.value = 'searchTerm'; + + spyOn(gl.utils, 'visitUrl').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=searchTerm`); + done(); + }); + + manager.search(); + }); + + it('should search with multiple words', (done) => { + input.value = 'awesome search terms'; + + spyOn(gl.utils, 'visitUrl').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`); + done(); + }); + + manager.search(); + }); + + it('should search with special characters', (done) => { + input.value = '~!@#$%^&*()_+{}:<>,.?/'; + + spyOn(gl.utils, 'visitUrl').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`); + done(); + }); + + manager.search(); + }); + + it('removes duplicated tokens', (done) => { + tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} `); - spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); - spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {}); - spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {}); - spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {}); - spyOn(gl.utils, 'getParameterByName').and.returnValue(null); - spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough(); + spyOn(gl.utils, 'visitUrl').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&label_name[]=bug`); + done(); + }); - input = document.querySelector('.filtered-search'); - tokensContainer = document.querySelector('.tokens-container'); - manager = new gl.FilteredSearchManager(); + manager.search(); + }); + }); + + describe('handleInputPlaceholder', () => { + it('should render placeholder when there is no input', () => { + expect(input.placeholder).toEqual(placeholder); }); - afterEach(() => { - manager.cleanup(); + it('should not render placeholder when there is input', () => { + input.value = 'test words'; + + const event = new Event('input'); + input.dispatchEvent(event); + + expect(input.placeholder).toEqual(''); }); - describe('search', () => { - const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened'; + it('should not render placeholder when there are tokens and no input', () => { + tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( + FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'), + ); - it('should search with a single word', (done) => { - input.value = 'searchTerm'; + const event = new Event('input'); + input.dispatchEvent(event); - spyOn(gl.utils, 'visitUrl').and.callFake((url) => { - expect(url).toEqual(`${defaultParams}&search=searchTerm`); - done(); - }); - - manager.search(); - }); - - it('should search with multiple words', (done) => { - input.value = 'awesome search terms'; - - spyOn(gl.utils, 'visitUrl').and.callFake((url) => { - expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`); - done(); - }); - - manager.search(); - }); - - it('should search with special characters', (done) => { - input.value = '~!@#$%^&*()_+{}:<>,.?/'; - - spyOn(gl.utils, 'visitUrl').and.callFake((url) => { - expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`); - done(); - }); - - manager.search(); - }); - - it('removes duplicated tokens', (done) => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} - `); - - spyOn(gl.utils, 'visitUrl').and.callFake((url) => { - expect(url).toEqual(`${defaultParams}&label_name[]=bug`); - done(); - }); - - manager.search(); - }); + expect(input.placeholder).toEqual(''); }); + }); - describe('handleInputPlaceholder', () => { - it('should render placeholder when there is no input', () => { - expect(input.placeholder).toEqual(placeholder); - }); - - it('should not render placeholder when there is input', () => { - input.value = 'test words'; - - const event = new Event('input'); - input.dispatchEvent(event); - - expect(input.placeholder).toEqual(''); - }); - - it('should not render placeholder when there are tokens and no input', () => { + describe('checkForBackspace', () => { + describe('tokens and no input', () => { + beforeEach(() => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'), ); - - const event = new Event('input'); - input.dispatchEvent(event); - - expect(input.placeholder).toEqual(''); - }); - }); - - describe('checkForBackspace', () => { - describe('tokens and no input', () => { - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'), - ); - }); - - it('removes last token', () => { - spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); - dispatchBackspaceEvent(input, 'keyup'); - - expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled(); - }); - - it('sets the input', () => { - spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); - dispatchDeleteEvent(input, 'keyup'); - - expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled(); - expect(input.value).toEqual('~bug'); - }); }); - it('does not remove token or change input when there is existing input', () => { + it('removes last token', () => { spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); - spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); + dispatchBackspaceEvent(input, 'keyup'); - input.value = 'text'; + expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled(); + }); + + it('sets the input', () => { + spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); dispatchDeleteEvent(input, 'keyup'); - expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled(); - expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled(); - expect(input.value).toEqual('text'); + expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled(); + expect(input.value).toEqual('~bug'); }); }); - describe('removeSelectedToken', () => { - function getVisualTokens() { - return tokensContainer.querySelectorAll('.js-visual-token'); - } + it('does not remove token or change input when there is existing input', () => { + spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); + spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), - ); - }); + input.value = 'text'; + dispatchDeleteEvent(input, 'keyup'); - it('removes selected token when the backspace key is pressed', () => { - expect(getVisualTokens().length).toEqual(1); - - dispatchBackspaceEvent(document, 'keydown'); - - expect(getVisualTokens().length).toEqual(0); - }); - - it('removes selected token when the delete key is pressed', () => { - expect(getVisualTokens().length).toEqual(1); - - dispatchDeleteEvent(document, 'keydown'); - - expect(getVisualTokens().length).toEqual(0); - }); - - it('updates the input placeholder after removal', () => { - manager.handleInputPlaceholder(); - - expect(input.placeholder).toEqual(''); - expect(getVisualTokens().length).toEqual(1); - - dispatchBackspaceEvent(document, 'keydown'); - - expect(input.placeholder).not.toEqual(''); - expect(getVisualTokens().length).toEqual(0); - }); - - it('updates the clear button after removal', () => { - manager.toggleClearSearchButton(); - - const clearButton = document.querySelector('.clear-search'); - - expect(clearButton.classList.contains('hidden')).toEqual(false); - expect(getVisualTokens().length).toEqual(1); - - dispatchBackspaceEvent(document, 'keydown'); - - expect(clearButton.classList.contains('hidden')).toEqual(true); - expect(getVisualTokens().length).toEqual(0); - }); - }); - - describe('unselects token', () => { - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug', true)} - ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')} - `); - }); - - it('unselects token when input is clicked', () => { - const selectedToken = tokensContainer.querySelector('.js-visual-token .selected'); - - expect(selectedToken.classList.contains('selected')).toEqual(true); - expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled(); - - // Click directly on input attached to document - // so that the click event will propagate properly - document.querySelector('.filtered-search').click(); - - expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); - expect(selectedToken.classList.contains('selected')).toEqual(false); - }); - - it('unselects token when document.body is clicked', () => { - const selectedToken = tokensContainer.querySelector('.js-visual-token .selected'); - - expect(selectedToken.classList.contains('selected')).toEqual(true); - expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled(); - - document.body.click(); - - expect(selectedToken.classList.contains('selected')).toEqual(false); - expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); - }); - }); - - describe('toggleInputContainerFocus', () => { - it('toggles on focus', () => { - input.focus(); - expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true); - }); - - it('toggles on blur', () => { - input.blur(); - expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false); - }); + expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled(); + expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled(); + expect(input.value).toEqual('text'); }); }); -})(); + + describe('removeSelectedToken', () => { + function getVisualTokens() { + return tokensContainer.querySelectorAll('.js-visual-token'); + } + + beforeEach(() => { + tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( + FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), + ); + }); + + it('removes selected token when the backspace key is pressed', () => { + expect(getVisualTokens().length).toEqual(1); + + dispatchBackspaceEvent(document, 'keydown'); + + expect(getVisualTokens().length).toEqual(0); + }); + + it('removes selected token when the delete key is pressed', () => { + expect(getVisualTokens().length).toEqual(1); + + dispatchDeleteEvent(document, 'keydown'); + + expect(getVisualTokens().length).toEqual(0); + }); + + it('updates the input placeholder after removal', () => { + manager.handleInputPlaceholder(); + + expect(input.placeholder).toEqual(''); + expect(getVisualTokens().length).toEqual(1); + + dispatchBackspaceEvent(document, 'keydown'); + + expect(input.placeholder).not.toEqual(''); + expect(getVisualTokens().length).toEqual(0); + }); + + it('updates the clear button after removal', () => { + manager.toggleClearSearchButton(); + + const clearButton = document.querySelector('.clear-search'); + + expect(clearButton.classList.contains('hidden')).toEqual(false); + expect(getVisualTokens().length).toEqual(1); + + dispatchBackspaceEvent(document, 'keydown'); + + expect(clearButton.classList.contains('hidden')).toEqual(true); + expect(getVisualTokens().length).toEqual(0); + }); + }); + + describe('unselects token', () => { + beforeEach(() => { + tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug', true)} + ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')} + `); + }); + + it('unselects token when input is clicked', () => { + const selectedToken = tokensContainer.querySelector('.js-visual-token .selected'); + + expect(selectedToken.classList.contains('selected')).toEqual(true); + expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled(); + + // Click directly on input attached to document + // so that the click event will propagate properly + document.querySelector('.filtered-search').click(); + + expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); + expect(selectedToken.classList.contains('selected')).toEqual(false); + }); + + it('unselects token when document.body is clicked', () => { + const selectedToken = tokensContainer.querySelector('.js-visual-token .selected'); + + expect(selectedToken.classList.contains('selected')).toEqual(true); + expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled(); + + document.body.click(); + + expect(selectedToken.classList.contains('selected')).toEqual(false); + expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); + }); + }); + + describe('toggleInputContainerFocus', () => { + it('toggles on focus', () => { + input.focus(); + expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true); + }); + + it('toggles on blur', () => { + input.blur(); + expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false); + }); + }); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js index cf409a7e509..6f9fa434c35 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js @@ -1,110 +1,108 @@ require('~/extensions/array'); require('~/filtered_search/filtered_search_token_keys'); -(() => { - describe('Filtered Search Token Keys', () => { - describe('get', () => { - let tokenKeys; +describe('Filtered Search Token Keys', () => { + describe('get', () => { + let tokenKeys; - beforeEach(() => { - tokenKeys = gl.FilteredSearchTokenKeys.get(); - }); - - it('should return tokenKeys', () => { - expect(tokenKeys !== null).toBe(true); - }); - - it('should return tokenKeys as an array', () => { - expect(tokenKeys instanceof Array).toBe(true); - }); + beforeEach(() => { + tokenKeys = gl.FilteredSearchTokenKeys.get(); }); - describe('getConditions', () => { - let conditions; - - beforeEach(() => { - conditions = gl.FilteredSearchTokenKeys.getConditions(); - }); - - it('should return conditions', () => { - expect(conditions !== null).toBe(true); - }); - - it('should return conditions as an array', () => { - expect(conditions instanceof Array).toBe(true); - }); + it('should return tokenKeys', () => { + expect(tokenKeys !== null).toBe(true); }); - describe('searchByKey', () => { - it('should return null when key not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey'); - expect(tokenKey === null).toBe(true); - }); - - it('should return tokenKey when found by key', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key); - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchBySymbol', () => { - it('should return null when symbol not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol'); - expect(tokenKey === null).toBe(true); - }); - - it('should return tokenKey when found by symbol', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol); - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchByKeyParam', () => { - it('should return null when key param not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam'); - expect(tokenKey === null).toBe(true); - }); - - it('should return tokenKey when found by key param', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); - expect(result).toEqual(tokenKeys[0]); - }); - - it('should return alternative tokenKey when found by key param', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives(); - const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchByConditionUrl', () => { - it('should return null when condition url not found', () => { - const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null); - expect(condition === null).toBe(true); - }); - - it('should return condition when found by url', () => { - const conditions = gl.FilteredSearchTokenKeys.getConditions(); - const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url); - expect(result).toBe(conditions[0]); - }); - }); - - describe('searchByConditionKeyValue', () => { - it('should return null when condition tokenKey and value not found', () => { - const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null); - expect(condition === null).toBe(true); - }); - - it('should return condition when found by tokenKey and value', () => { - const conditions = gl.FilteredSearchTokenKeys.getConditions(); - const result = gl.FilteredSearchTokenKeys - .searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value); - expect(result).toEqual(conditions[0]); - }); + it('should return tokenKeys as an array', () => { + expect(tokenKeys instanceof Array).toBe(true); }); }); -})(); + + describe('getConditions', () => { + let conditions; + + beforeEach(() => { + conditions = gl.FilteredSearchTokenKeys.getConditions(); + }); + + it('should return conditions', () => { + expect(conditions !== null).toBe(true); + }); + + it('should return conditions as an array', () => { + expect(conditions instanceof Array).toBe(true); + }); + }); + + describe('searchByKey', () => { + it('should return null when key not found', () => { + const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey'); + expect(tokenKey === null).toBe(true); + }); + + it('should return tokenKey when found by key', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.get(); + const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key); + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchBySymbol', () => { + it('should return null when symbol not found', () => { + const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol'); + expect(tokenKey === null).toBe(true); + }); + + it('should return tokenKey when found by symbol', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.get(); + const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol); + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchByKeyParam', () => { + it('should return null when key param not found', () => { + const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam'); + expect(tokenKey === null).toBe(true); + }); + + it('should return tokenKey when found by key param', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.get(); + const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + expect(result).toEqual(tokenKeys[0]); + }); + + it('should return alternative tokenKey when found by key param', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives(); + const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchByConditionUrl', () => { + it('should return null when condition url not found', () => { + const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null); + expect(condition === null).toBe(true); + }); + + it('should return condition when found by url', () => { + const conditions = gl.FilteredSearchTokenKeys.getConditions(); + const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url); + expect(result).toBe(conditions[0]); + }); + }); + + describe('searchByConditionKeyValue', () => { + it('should return null when condition tokenKey and value not found', () => { + const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null); + expect(condition === null).toBe(true); + }); + + it('should return condition when found by tokenKey and value', () => { + const conditions = gl.FilteredSearchTokenKeys.getConditions(); + const result = gl.FilteredSearchTokenKeys + .searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value); + expect(result).toEqual(conditions[0]); + }); + }); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js index cabbc694ec4..3e2e577f115 100644 --- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js @@ -2,134 +2,132 @@ require('~/extensions/array'); require('~/filtered_search/filtered_search_token_keys'); require('~/filtered_search/filtered_search_tokenizer'); -(() => { - describe('Filtered Search Tokenizer', () => { - describe('processTokens', () => { - it('returns for input containing only search value', () => { - const results = gl.FilteredSearchTokenizer.processTokens('searchTerm'); - expect(results.searchToken).toBe('searchTerm'); - expect(results.tokens.length).toBe(0); - expect(results.lastToken).toBe(results.searchToken); - }); +describe('Filtered Search Tokenizer', () => { + describe('processTokens', () => { + it('returns for input containing only search value', () => { + const results = gl.FilteredSearchTokenizer.processTokens('searchTerm'); + expect(results.searchToken).toBe('searchTerm'); + expect(results.tokens.length).toBe(0); + expect(results.lastToken).toBe(results.searchToken); + }); - it('returns for input containing only tokens', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none'); - expect(results.searchToken).toBe(''); - expect(results.tokens.length).toBe(4); - expect(results.tokens[3]).toBe(results.lastToken); + it('returns for input containing only tokens', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none'); + expect(results.searchToken).toBe(''); + expect(results.tokens.length).toBe(4); + expect(results.tokens[3]).toBe(results.lastToken); - expect(results.tokens[0].key).toBe('author'); - expect(results.tokens[0].value).toBe('root'); - expect(results.tokens[0].symbol).toBe('@'); + expect(results.tokens[0].key).toBe('author'); + expect(results.tokens[0].value).toBe('root'); + expect(results.tokens[0].symbol).toBe('@'); - expect(results.tokens[1].key).toBe('label'); - expect(results.tokens[1].value).toBe('"Very Important"'); - expect(results.tokens[1].symbol).toBe('~'); + expect(results.tokens[1].key).toBe('label'); + expect(results.tokens[1].value).toBe('"Very Important"'); + expect(results.tokens[1].symbol).toBe('~'); - expect(results.tokens[2].key).toBe('milestone'); - expect(results.tokens[2].value).toBe('v1.0'); - expect(results.tokens[2].symbol).toBe('%'); + expect(results.tokens[2].key).toBe('milestone'); + expect(results.tokens[2].value).toBe('v1.0'); + expect(results.tokens[2].symbol).toBe('%'); - expect(results.tokens[3].key).toBe('assignee'); - expect(results.tokens[3].value).toBe('none'); - expect(results.tokens[3].symbol).toBe(''); - }); + expect(results.tokens[3].key).toBe('assignee'); + expect(results.tokens[3].value).toBe('none'); + expect(results.tokens[3].symbol).toBe(''); + }); - it('returns for input starting with search value and ending with tokens', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('searchTerm anotherSearchTerm milestone:none'); - expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); - expect(results.tokens.length).toBe(1); - expect(results.tokens[0]).toBe(results.lastToken); - expect(results.tokens[0].key).toBe('milestone'); - expect(results.tokens[0].value).toBe('none'); - expect(results.tokens[0].symbol).toBe(''); - }); + it('returns for input starting with search value and ending with tokens', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('searchTerm anotherSearchTerm milestone:none'); + expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); + expect(results.tokens.length).toBe(1); + expect(results.tokens[0]).toBe(results.lastToken); + expect(results.tokens[0].key).toBe('milestone'); + expect(results.tokens[0].value).toBe('none'); + expect(results.tokens[0].symbol).toBe(''); + }); - it('returns for input starting with tokens and ending with search value', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('assignee:@user searchTerm'); + it('returns for input starting with tokens and ending with search value', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('assignee:@user searchTerm'); - expect(results.searchToken).toBe('searchTerm'); - expect(results.tokens.length).toBe(1); - expect(results.tokens[0].key).toBe('assignee'); - expect(results.tokens[0].value).toBe('user'); - expect(results.tokens[0].symbol).toBe('@'); - expect(results.lastToken).toBe(results.searchToken); - }); + expect(results.searchToken).toBe('searchTerm'); + expect(results.tokens.length).toBe(1); + expect(results.tokens[0].key).toBe('assignee'); + expect(results.tokens[0].value).toBe('user'); + expect(results.tokens[0].symbol).toBe('@'); + expect(results.lastToken).toBe(results.searchToken); + }); - it('returns for input containing search value wrapped between tokens', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none'); + it('returns for input containing search value wrapped between tokens', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none'); - expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); - expect(results.tokens.length).toBe(3); - expect(results.tokens[2]).toBe(results.lastToken); + expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); + expect(results.tokens.length).toBe(3); + expect(results.tokens[2]).toBe(results.lastToken); - expect(results.tokens[0].key).toBe('author'); - expect(results.tokens[0].value).toBe('root'); - expect(results.tokens[0].symbol).toBe('@'); + expect(results.tokens[0].key).toBe('author'); + expect(results.tokens[0].value).toBe('root'); + expect(results.tokens[0].symbol).toBe('@'); - expect(results.tokens[1].key).toBe('label'); - expect(results.tokens[1].value).toBe('"Won\'t fix"'); - expect(results.tokens[1].symbol).toBe('~'); + expect(results.tokens[1].key).toBe('label'); + expect(results.tokens[1].value).toBe('"Won\'t fix"'); + expect(results.tokens[1].symbol).toBe('~'); - expect(results.tokens[2].key).toBe('milestone'); - expect(results.tokens[2].value).toBe('none'); - expect(results.tokens[2].symbol).toBe(''); - }); + expect(results.tokens[2].key).toBe('milestone'); + expect(results.tokens[2].value).toBe('none'); + expect(results.tokens[2].symbol).toBe(''); + }); - it('returns for input containing search value in between tokens', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing'); - expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); - expect(results.tokens.length).toBe(3); - expect(results.tokens[2]).toBe(results.lastToken); + it('returns for input containing search value in between tokens', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing'); + expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); + expect(results.tokens.length).toBe(3); + expect(results.tokens[2]).toBe(results.lastToken); - expect(results.tokens[0].key).toBe('author'); - expect(results.tokens[0].value).toBe('root'); - expect(results.tokens[0].symbol).toBe('@'); + expect(results.tokens[0].key).toBe('author'); + expect(results.tokens[0].value).toBe('root'); + expect(results.tokens[0].symbol).toBe('@'); - expect(results.tokens[1].key).toBe('assignee'); - expect(results.tokens[1].value).toBe('none'); - expect(results.tokens[1].symbol).toBe(''); + expect(results.tokens[1].key).toBe('assignee'); + expect(results.tokens[1].value).toBe('none'); + expect(results.tokens[1].symbol).toBe(''); - expect(results.tokens[2].key).toBe('label'); - expect(results.tokens[2].value).toBe('Doing'); - expect(results.tokens[2].symbol).toBe('~'); - }); + expect(results.tokens[2].key).toBe('label'); + expect(results.tokens[2].value).toBe('Doing'); + expect(results.tokens[2].symbol).toBe('~'); + }); - it('returns search value for invalid tokens', () => { - const results = gl.FilteredSearchTokenizer.processTokens('fake:token'); - expect(results.lastToken).toBe('fake:token'); - expect(results.searchToken).toBe('fake:token'); - expect(results.tokens.length).toEqual(0); - }); + it('returns search value for invalid tokens', () => { + const results = gl.FilteredSearchTokenizer.processTokens('fake:token'); + expect(results.lastToken).toBe('fake:token'); + expect(results.searchToken).toBe('fake:token'); + expect(results.tokens.length).toEqual(0); + }); - it('returns search value and token for mix of valid and invalid tokens', () => { - const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token'); - expect(results.tokens.length).toEqual(1); - expect(results.tokens[0].key).toBe('label'); - expect(results.tokens[0].value).toBe('real'); - expect(results.tokens[0].symbol).toBe(''); - expect(results.lastToken).toBe('fake:token'); - expect(results.searchToken).toBe('fake:token'); - }); + it('returns search value and token for mix of valid and invalid tokens', () => { + const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token'); + expect(results.tokens.length).toEqual(1); + expect(results.tokens[0].key).toBe('label'); + expect(results.tokens[0].value).toBe('real'); + expect(results.tokens[0].symbol).toBe(''); + expect(results.lastToken).toBe('fake:token'); + expect(results.searchToken).toBe('fake:token'); + }); - it('returns search value for invalid symbols', () => { - const results = gl.FilteredSearchTokenizer.processTokens('std::includes'); - expect(results.lastToken).toBe('std::includes'); - expect(results.searchToken).toBe('std::includes'); - }); + it('returns search value for invalid symbols', () => { + const results = gl.FilteredSearchTokenizer.processTokens('std::includes'); + expect(results.lastToken).toBe('std::includes'); + expect(results.searchToken).toBe('std::includes'); + }); - it('removes duplicated values', () => { - const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo'); - expect(results.tokens.length).toBe(1); - expect(results.tokens[0].key).toBe('label'); - expect(results.tokens[0].value).toBe('foo'); - expect(results.tokens[0].symbol).toBe('~'); - }); + it('removes duplicated values', () => { + const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo'); + expect(results.tokens.length).toBe(1); + expect(results.tokens[0].key).toBe('label'); + expect(results.tokens[0].value).toBe('foo'); + expect(results.tokens[0].symbol).toBe('~'); }); }); -})(); +}); From c26b126502d4025230aa80e7d26736e1398c5022 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Apr 2017 15:53:09 +0800 Subject: [PATCH 094/168] Make sure @stream.each_line would tag Encoding.default_external --- lib/gitlab/ci/trace/stream.rb | 2 ++ spec/lib/gitlab/ci/trace/stream_spec.rb | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index 33141d0d88d..8d9fb6ff0f1 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -15,6 +15,8 @@ module Gitlab def initialize @stream = yield @stream.binmode + # Ci::Ansi2html::Converter would read from @stream directly + @stream.set_encoding(Encoding.default_external) end def valid? diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 4bbca6d2ea2..6f5c9994f54 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -34,12 +34,12 @@ describe Gitlab::Ci::Trace::Stream do end context 'when the trace contains ANSI sequence and Unicode' do - let(:stream) do - described_class.new do - File.open(expand_fixture_path('trace/ansi-sequence-and-unicode')) - end + let(:io) do + File.open(expand_fixture_path('trace/ansi-sequence-and-unicode')) end + let(:stream) { described_class.new { io } } + it 'forwards to the next linefeed, case 1' do stream.limit(7) @@ -57,6 +57,16 @@ describe Gitlab::Ci::Trace::Stream do expect(result).to eq("\e[01;32m許功蓋\e[0m\n") expect(result.encoding).to eq(Encoding.default_external) end + + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/30796 + it 'reads in binary, output as Encoding.default_external' do + stream.limit(29) + + result = io.read # Ci::Ansi2html::Converter would read with each_line + + expect(result).to eq("\e[01;32m許功蓋\e[0m\n") + expect(result.encoding).to eq(Encoding.default_external) + end end end From dac23fa233f98b521065fd3f21f6183f84b562d3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Apr 2017 16:29:47 +0800 Subject: [PATCH 095/168] Don't try to read if there's no trace --- lib/gitlab/ci/trace.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index 5b835bb669a..b980e29e9c3 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -50,6 +50,8 @@ module Gitlab end def read + return unless exist? + stream = Gitlab::Ci::Trace::Stream.new do if current_path File.open(current_path, "rb") From e7d3fe44f64d1efbbc0b9611cb3382feff4a2f00 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Apr 2017 17:10:41 +0800 Subject: [PATCH 096/168] Only set the encoding before passing to Ansi2html --- lib/gitlab/ci/trace.rb | 2 -- lib/gitlab/ci/trace/stream.rb | 11 ++++++++--- spec/lib/gitlab/ci/trace/stream_spec.rb | 14 +++++++------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index b980e29e9c3..5b835bb669a 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -50,8 +50,6 @@ module Gitlab end def read - return unless exist? - stream = Gitlab::Ci::Trace::Stream.new do if current_path File.open(current_path, "rb") diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index 8d9fb6ff0f1..757a8e2dc6a 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -14,9 +14,7 @@ module Gitlab def initialize @stream = yield - @stream.binmode - # Ci::Ansi2html::Converter would read from @stream directly - @stream.set_encoding(Encoding.default_external) + @stream.binmode if @stream end def valid? @@ -58,12 +56,14 @@ module Gitlab end def html_with_state(state = nil) + set_encoding_for_ansi2html ::Ci::Ansi2html.convert(stream, state) end def html(last_lines: nil) text = raw(last_lines: last_lines) stream = StringIO.new(text) + set_encoding_for_ansi2html(stream) ::Ci::Ansi2html.convert(stream).html end @@ -117,6 +117,11 @@ module Gitlab chunks.join.lines.last(last_lines).join end + + def set_encoding_for_ansi2html(stream = @stream) + # Ci::Ansi2html::Converter would read from @stream directly + stream.set_encoding(Encoding.default_external) + end end end end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 6f5c9994f54..1602a4945ad 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -34,12 +34,12 @@ describe Gitlab::Ci::Trace::Stream do end context 'when the trace contains ANSI sequence and Unicode' do - let(:io) do - File.open(expand_fixture_path('trace/ansi-sequence-and-unicode')) + let(:stream) do + described_class.new do + File.open(expand_fixture_path('trace/ansi-sequence-and-unicode')) + end end - let(:stream) { described_class.new { io } } - it 'forwards to the next linefeed, case 1' do stream.limit(7) @@ -60,11 +60,11 @@ describe Gitlab::Ci::Trace::Stream do # See https://gitlab.com/gitlab-org/gitlab-ce/issues/30796 it 'reads in binary, output as Encoding.default_external' do - stream.limit(29) + stream.limit(52) - result = io.read # Ci::Ansi2html::Converter would read with each_line + result = stream.html - expect(result).to eq("\e[01;32m許功蓋\e[0m\n") + expect(result.lines.first).to eq("ヾ(´༎ຶД༎ຶ`)ノ
許功蓋
") expect(result.encoding).to eq(Encoding.default_external) end end From f05ab97e1ef1c1e70b6b8e1286b1d626e70f5286 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Apr 2017 17:12:43 +0800 Subject: [PATCH 097/168] Restore nil for stream --- spec/lib/gitlab/ci/trace/stream_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 1602a4945ad..28d5c2183ce 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Trace::Stream do describe 'delegates' do - subject { described_class.new { StringIO.new } } + subject { described_class.new { nil } } it { is_expected.to delegate_method(:close).to(:stream) } it { is_expected.to delegate_method(:tell).to(:stream) } From bb10b23194a3f3332096168fa0e0fd297f846307 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Apr 2017 17:25:36 +0800 Subject: [PATCH 098/168] Add changelog entry --- changelogs/unreleased/fix-trace-encoding.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-trace-encoding.yml diff --git a/changelogs/unreleased/fix-trace-encoding.yml b/changelogs/unreleased/fix-trace-encoding.yml new file mode 100644 index 00000000000..152610c43f5 --- /dev/null +++ b/changelogs/unreleased/fix-trace-encoding.yml @@ -0,0 +1,4 @@ +--- +title: Fix another case where trace does not have proper encoding set +merge_request: 10728 +author: From 9350b9064cb9c5048f9eab8041e953a5dab5b154 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Apr 2017 17:52:15 +0800 Subject: [PATCH 099/168] Set the encoding in c'tor and explain why it's fine --- lib/gitlab/ci/trace/stream.rb | 16 ++++++++-------- spec/lib/gitlab/ci/trace/stream_spec.rb | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index 757a8e2dc6a..b929bdd55bc 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -14,7 +14,14 @@ module Gitlab def initialize @stream = yield - @stream.binmode if @stream + if @stream + @stream.binmode + # Ci::Ansi2html::Converter would read from @stream directly, + # using @stream.each_line to be specific. It's safe to set + # the encoding here because IO#seek(bytes) and IO#read(bytes) + # are not characters based, so encoding doesn't matter to them. + @stream.set_encoding(Encoding.default_external) + end end def valid? @@ -56,14 +63,12 @@ module Gitlab end def html_with_state(state = nil) - set_encoding_for_ansi2html ::Ci::Ansi2html.convert(stream, state) end def html(last_lines: nil) text = raw(last_lines: last_lines) stream = StringIO.new(text) - set_encoding_for_ansi2html(stream) ::Ci::Ansi2html.convert(stream).html end @@ -117,11 +122,6 @@ module Gitlab chunks.join.lines.last(last_lines).join end - - def set_encoding_for_ansi2html(stream = @stream) - # Ci::Ansi2html::Converter would read from @stream directly - stream.set_encoding(Encoding.default_external) - end end end end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 28d5c2183ce..03f040f4465 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -64,7 +64,7 @@ describe Gitlab::Ci::Trace::Stream do result = stream.html - expect(result.lines.first).to eq("ヾ(´༎ຶД༎ຶ`)ノ
許功蓋
") + expect(result).to eq("ヾ(´༎ຶД༎ຶ`)ノ
許功蓋
") expect(result.encoding).to eq(Encoding.default_external) end end From 9ccab7effb629afb26133ecb069833c2d163705c Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Mon, 17 Apr 2017 11:16:41 +0100 Subject: [PATCH 100/168] Added break-word wrap --- app/assets/stylesheets/pages/diff.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 1aa1079903c..1b4694377b3 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -106,6 +106,10 @@ span { white-space: pre-wrap; } + + .line { + word-wrap: break-word; + } } } From 27f519a29577ca8f461a30d3eabfe9dcac85dd1c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 17 Apr 2017 14:07:41 +0300 Subject: [PATCH 101/168] Removes `Repository#version` method and tests Signed-off-by: Dmitriy Zaporozhets --- app/models/repository.rb | 8 +------- changelogs/unreleased/dz-remove-repo-version.yml | 4 ++++ features/steps/project/project.rb | 6 ------ spec/models/repository_spec.rb | 1 - 4 files changed, 5 insertions(+), 14 deletions(-) create mode 100644 changelogs/unreleased/dz-remove-repo-version.yml diff --git a/app/models/repository.rb b/app/models/repository.rb index 2b11ed6128e..1d6acbda235 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -19,7 +19,7 @@ class Repository # # For example, for entry `:readme` there's a method called `readme` which # stores its data in the `readme` cache key. - CACHED_METHODS = %i(size commit_count readme version contribution_guide + CACHED_METHODS = %i(size commit_count readme contribution_guide changelog license_blob license_key gitignore koding_yml gitlab_ci_yml branch_names tag_names branch_count tag_count avatar exists? empty? root_ref).freeze @@ -32,7 +32,6 @@ class Repository changelog: :changelog, license: %i(license_blob license_key), contributing: :contribution_guide, - version: :version, gitignore: :gitignore, koding: :koding_yml, gitlab_ci: :gitlab_ci_yml, @@ -530,11 +529,6 @@ class Repository end cache_method :readme - def version - file_on_head(:version) - end - cache_method :version - def contribution_guide file_on_head(:contributing) end diff --git a/changelogs/unreleased/dz-remove-repo-version.yml b/changelogs/unreleased/dz-remove-repo-version.yml new file mode 100644 index 00000000000..f9e51a920f9 --- /dev/null +++ b/changelogs/unreleased/dz-remove-repo-version.yml @@ -0,0 +1,4 @@ +--- +title: Remove Repository#version method and tests +merge_request: 10734 +author: diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 975c879149e..280d70925f7 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -66,12 +66,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps expect(page).not_to have_link('Remove avatar') end - step 'I should see project "Shop" version' do - page.within '.project-side' do - expect(page).to have_content '6.7.0.pre' - end - end - step 'change project default branch' do select 'fix', from: 'project_default_branch' click_button 'Save changes' diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 5e5c2b016b6..5f8337220df 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1259,7 +1259,6 @@ describe Repository, models: true do :changelog, :license, :contributing, - :version, :gitignore, :koding, :gitlab_ci, From b544d8fade02ca826a55b8300e0a4c18fccbec01 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Mon, 17 Apr 2017 13:18:40 +0100 Subject: [PATCH 102/168] Review changes, used eq instead of match --- spec/controllers/projects/builds_controller_spec.rb | 2 +- spec/controllers/projects/merge_requests_controller_spec.rb | 2 +- spec/controllers/projects/pipelines_controller_spec.rb | 2 +- spec/serializers/build_serializer_spec.rb | 2 +- spec/serializers/pipeline_serializer_spec.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/controllers/projects/builds_controller_spec.rb b/spec/controllers/projects/builds_controller_spec.rb index 07f3a3d0062..faf3770f5e9 100644 --- a/spec/controllers/projects/builds_controller_spec.rb +++ b/spec/controllers/projects/builds_controller_spec.rb @@ -60,7 +60,7 @@ describe Projects::BuildsController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to match status.favicon + expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 49e94574f57..cc393bd24f2 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -1208,7 +1208,7 @@ describe Projects::MergeRequestsController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to match status.favicon + expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index f64daff42ec..d9192177a06 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -86,7 +86,7 @@ describe Projects::PipelinesController do expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to match status.favicon + expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" end end end diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb index ebb70466a73..7f1abecfafe 100644 --- a/spec/serializers/build_serializer_spec.rb +++ b/spec/serializers/build_serializer_spec.rb @@ -38,7 +38,7 @@ describe BuildSerializer do expect(subject[:text]).to eq(status.text) expect(subject[:label]).to eq(status.label) expect(subject[:icon]).to eq(status.icon) - expect(subject[:favicon]).to match status.favicon + expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico") end end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 238dfa50013..ecde45a6d44 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -144,7 +144,7 @@ describe PipelineSerializer do expect(subject[:text]).to eq(status.text) expect(subject[:label]).to eq(status.label) expect(subject[:icon]).to eq(status.icon) - expect(subject[:favicon]).to match status.favicon + expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico") end end end From ae833a8b83bbdfa3ad95af56ca470e2ac51baadf Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 17 Apr 2017 14:23:39 +0100 Subject: [PATCH 103/168] Fix user activities HTTP clone spec --- spec/requests/git_http_spec.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 069ff4bdaa3..316742ff076 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -158,13 +158,6 @@ describe 'Git HTTP requests', lib: true do expect(response).to have_http_status(:ok) end end - - it 'updates the user last activity' do - expect(user_activity(user)).to be_nil - download(path, {}) do |response| - expect(user_activity(user)).to be_present - end - end end context 'but only project members are allowed' do @@ -263,6 +256,14 @@ describe 'Git HTTP requests', lib: true do expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end + + it 'updates the user last activity', :redis do + expect(user_activity(user)).to be_nil + + download(path, env) do |response| + expect(user_activity(user)).to be_present + end + end end context "when an oauth token is provided" do From 2545f6adbfb6977a2d677805b3b04256103573ca Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 17 Apr 2017 13:58:07 +0100 Subject: [PATCH 104/168] Fix following with multiple paths `git log --follow` is only supported for a single path. CE doesn't currently pass multiple paths, but EE does. --- app/models/repository.rb | 2 +- spec/models/repository_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 2b11ed6128e..ec7acc6b6f7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -109,7 +109,7 @@ class Repository offset: offset, after: after, before: before, - follow: path.present?, + follow: Array(path).length == 1, skip_merges: skip_merges } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 5e5c2b016b6..cbf2fc4a91b 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -171,6 +171,27 @@ describe Repository, models: true do end end + describe '#commits' do + it 'sets follow when path is a single path' do + expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice + + repository.commits('master', path: 'README.md') + repository.commits('master', path: ['README.md']) + end + + it 'does not set follow when path is multiple paths' do + expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original + + repository.commits('master', path: ['README.md', 'CHANGELOG']) + end + + it 'does not set follow when there are no paths' do + expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original + + repository.commits('master') + end + end + describe '#find_commits_by_message' do it 'returns commits with messages containing a given string' do commit_ids = repository.find_commits_by_message('submodule').map(&:id) From 878e46a1af006213f990fa9bff26d42f0f594063 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 17 Apr 2017 13:57:09 +0000 Subject: [PATCH 105/168] Fix restricted visibility project setting --- app/helpers/projects_helper.rb | 19 +++++++++++---- .../fix-project-visibility-setting.yml | 4 ++++ spec/helpers/projects_helper_spec.rb | 23 +++++++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/fix-project-visibility-setting.yml diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 6b9e4267281..43669b6f356 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -24,7 +24,7 @@ module ProjectsHelper return "(deleted)" unless author - author_html = "" + author_html = "" # Build avatar image tag author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar] @@ -45,7 +45,7 @@ module ProjectsHelper link_to(author_html, user_path(author), class: "author_link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe else title = opts[:title].sub(":name", sanitize(author.name)) - link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe + link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' }).html_safe end end @@ -430,13 +430,22 @@ module ProjectsHelper end def visibility_select_options(project, selected_level) - levels_options_array = Gitlab::VisibilityLevel.values.map do |level| - [ + level_options = Gitlab::VisibilityLevel.values.each_with_object([]) do |level, level_options| + next if restricted_levels.include?(level) + + level_options << [ visibility_level_label(level), { data: { description: visibility_level_description(level, project) } }, level ] end - options_for_select(levels_options_array, selected_level) + + options_for_select(level_options, selected_level) + end + + def restricted_levels + return [] if current_user.admin? + + current_application_settings.restricted_visibility_levels || [] end end diff --git a/changelogs/unreleased/fix-project-visibility-setting.yml b/changelogs/unreleased/fix-project-visibility-setting.yml new file mode 100644 index 00000000000..0fc219ccf52 --- /dev/null +++ b/changelogs/unreleased/fix-project-visibility-setting.yml @@ -0,0 +1,4 @@ +--- +title: Fix restricted project visibility setting available to users +merge_request: +author: diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 40efab6e4f7..a7fc5d14859 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -265,4 +265,27 @@ describe ProjectsHelper do end end end + + describe "#visibility_select_options" do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it "does not include the Public restricted level" do + expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).not_to include('Public') + end + + it "includes the Internal level" do + expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Internal') + end + + it "includes the Private level" do + expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private') + end + end end From 8d10add4a504079c00bea3284cb8a7ab71cc8aaf Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 17 Apr 2017 15:54:10 +0100 Subject: [PATCH 106/168] Don't show usage ping on settings page --- app/assets/javascripts/dispatcher.js | 3 +-- app/assets/javascripts/main.js | 1 + .../{application_settings.js.es6 => usage_ping.js} | 2 +- app/views/admin/application_settings/_form.html.haml | 11 ++++++----- app/views/admin/cohorts/_usage_ping.html.haml | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) rename app/assets/javascripts/{application_settings.js.es6 => usage_ping.js} (87%) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index a0afffb6635..9d41556653f 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -365,9 +365,8 @@ const ShortcutsBlob = require('./shortcuts_blob'); case 'admin': new Admin(); switch (path[1]) { - case 'application_settings': case 'cohorts': - new gl.ApplicationSettings(); + new gl.UsagePing(); break; case 'groups': new UsersSelect(); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index c50ec24c818..36616d02bf6 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -165,6 +165,7 @@ import './syntax_highlight'; import './task_list'; import './todos'; import './tree'; +import './usage_ping'; import './user'; import './user_tabs'; import './username_validator'; diff --git a/app/assets/javascripts/application_settings.js.es6 b/app/assets/javascripts/usage_ping.js similarity index 87% rename from app/assets/javascripts/application_settings.js.es6 rename to app/assets/javascripts/usage_ping.js index ce7d5129d8d..4c8ca345778 100644 --- a/app/assets/javascripts/application_settings.js.es6 +++ b/app/assets/javascripts/usage_ping.js @@ -1,7 +1,7 @@ (global => { global.gl = global.gl || {}; - gl.ApplicationSettings = function() { + gl.UsagePing = function() { var usage_data_url = $('.usage-data').data('endpoint'); $.ajax({ diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 13e9faa9642..0dc1103eece 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -493,11 +493,12 @@ = f.check_box :usage_ping_enabled Usage ping enabled = link_to icon('question-circle'), help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-data") - .container - .help-block - Every week GitLab will report license usage back to GitLab, Inc. - Disable this option if you do not want this to occur. This is the JSON payload that will be sent: - %pre.usage-data.js-syntax-highlight.code.highlight{ "data-endpoint" => usage_data_admin_application_settings_path(format: :html) } + .help-block + Every week GitLab will report license usage back to GitLab, Inc. + Disable this option if you do not want this to occur. To see the + JSON payload that will be sent, visit the + = succeed '.' do + = link_to "Cohorts page", admin_cohorts_path(anchor: 'usage-ping') %fieldset %legend Email diff --git a/app/views/admin/cohorts/_usage_ping.html.haml b/app/views/admin/cohorts/_usage_ping.html.haml index a95f81a7f49..73aa95d84f1 100644 --- a/app/views/admin/cohorts/_usage_ping.html.haml +++ b/app/views/admin/cohorts/_usage_ping.html.haml @@ -1,4 +1,4 @@ -%h2 Usage ping +%h2#usage-ping Usage ping .bs-callout.clearfix %p From e072806ac2e170e7638df1def47f6380b4e5b63b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 17 Apr 2017 16:40:34 +0100 Subject: [PATCH 107/168] Make ESLint happy --- app/assets/javascripts/usage_ping.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/usage_ping.js b/app/assets/javascripts/usage_ping.js index 4c8ca345778..fd3af7d7ab6 100644 --- a/app/assets/javascripts/usage_ping.js +++ b/app/assets/javascripts/usage_ping.js @@ -1,16 +1,15 @@ -(global => { - global.gl = global.gl || {}; +function UsagePing() { + const usageDataUrl = $('.usage-data').data('endpoint'); - gl.UsagePing = function() { - var usage_data_url = $('.usage-data').data('endpoint'); + $.ajax({ + type: 'GET', + url: usageDataUrl, + dataType: 'html', + success(html) { + $('.usage-data').html(html); + }, + }); +} - $.ajax({ - type: "GET", - url: usage_data_url, - dataType: "html", - success: function (html) { - $(".usage-data").html(html); - } - }); - }; -})(window); +window.gl = window.gl || {}; +window.gl.UsagePing = UsagePing; From fd681ee5b688388b2d202816330e03cf688e6f96 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Apr 2017 11:55:16 -0400 Subject: [PATCH 108/168] Update variable table styles at wider viewport. --- app/assets/stylesheets/pages/projects.scss | 8 ++++++-- app/views/projects/variables/_table.html.haml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 717ebb44a23..042d25ee064 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -937,20 +937,24 @@ pre.light-well { // override bootstrap white-space: normal!important; - @media (max-width: $screen-sm-max) { + @media (max-width: $screen-md-max) { width: 150px; max-width: 150px; } } .variable-value { - @media(max-width: $screen-xs-max) { + @media(max-width: $screen-sm-max) { width: 150px; max-width: 150px; overflow: hidden; word-wrap: break-word; } } + + .variable-menu { + text-align: right; + } } .services-installation-info .row { diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml index c7cebf45160..0ce597dcf21 100644 --- a/app/views/projects/variables/_table.html.haml +++ b/app/views/projects/variables/_table.html.haml @@ -14,7 +14,7 @@ %tr %td.variable-key= variable.key %td.variable-value{ "data-value" => variable.value }****** - %td + %td.variable-menu = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do %span.sr-only Update From 29517307ddc936ab30aadf107de56373fc9f2354 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Apr 2017 12:26:20 -0400 Subject: [PATCH 109/168] Remove breakpoints, use fixed widths. --- app/assets/stylesheets/pages/projects.scss | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 042d25ee064..4cf5a3f7b8d 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -929,27 +929,20 @@ pre.light-well { } .variable-key { - width: 300px; - max-width: 300px; + width: 120px; + max-width: 120px; overflow: hidden; word-wrap: break-word; // override bootstrap white-space: normal!important; - - @media (max-width: $screen-md-max) { - width: 150px; - max-width: 150px; - } } .variable-value { - @media(max-width: $screen-sm-max) { - width: 150px; - max-width: 150px; - overflow: hidden; - word-wrap: break-word; - } + width: 150px; + max-width: 150px; + overflow: hidden; + word-wrap: break-word; } .variable-menu { From 41f018516aba0140cb3a1acd2a0ad461737ad9d7 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Apr 2017 12:54:31 -0400 Subject: [PATCH 110/168] Use ellipsis and nowrap. --- app/assets/stylesheets/pages/projects.scss | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 4cf5a3f7b8d..742ca10944c 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -929,20 +929,24 @@ pre.light-well { } .variable-key { - width: 120px; - max-width: 120px; + /* 45px is not the actual maximum width. The + browser uses it to calculate cell width relative + to variable-value and variable-menu */ + max-width: 45px; + // This is the actual min-width, makes a difference only on small screens + min-width: 100px; overflow: hidden; word-wrap: break-word; - - // override bootstrap - white-space: normal!important; + white-space: nowrap; + text-overflow: ellipsis; } .variable-value { - width: 150px; max-width: 150px; overflow: hidden; word-wrap: break-word; + white-space: nowrap; + text-overflow: ellipsis; } .variable-menu { From 397b3ac590d342677a4352c3733a3c1b6ee3ee2d Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Apr 2017 16:58:31 +0000 Subject: [PATCH 111/168] Fix comment length and syntax. --- app/assets/stylesheets/pages/projects.scss | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 742ca10944c..f09981be448 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -929,11 +929,10 @@ pre.light-well { } .variable-key { - /* 45px is not the actual maximum width. The - browser uses it to calculate cell width relative - to variable-value and variable-menu */ + // Not the actual maximum width. Used to calculate cell width relative + // to variable-value and variable-menu max-width: 45px; - // This is the actual min-width, makes a difference only on small screens + // Is the actual min-width, makes a difference only on small screens min-width: 100px; overflow: hidden; word-wrap: break-word; From 131c62ef839128d0098128e31a0fc709959ecf53 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Apr 2017 13:06:21 -0400 Subject: [PATCH 112/168] Remove unneeded min-width. --- app/assets/stylesheets/pages/projects.scss | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 742ca10944c..a9e2a034790 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -929,12 +929,7 @@ pre.light-well { } .variable-key { - /* 45px is not the actual maximum width. The - browser uses it to calculate cell width relative - to variable-value and variable-menu */ - max-width: 45px; - // This is the actual min-width, makes a difference only on small screens - min-width: 100px; + max-width: 120px; overflow: hidden; word-wrap: break-word; white-space: nowrap; From 7ac55b37338b5c9cd38de86c33455756b32dbd34 Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Mon, 17 Apr 2017 18:19:52 +0000 Subject: [PATCH 113/168] Clarify wording for "release" and dates. --- PROCESS.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index cfa841dc13d..483aeaec266 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -57,16 +57,16 @@ star, smile, etc.). Some good tips about code reviews can be found in our [Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html -## Feature Freeze +## Feature freeze on the 7th for the release on the 22nd -After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it. +After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it. Merge requests may still be merged into master during this period, but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch. By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things. ### Between the 1st and the 7th -These types of merge requests need special consideration: +These types of merge requests for the upcoming release need special consideration: * **Large features**: a large feature is one that is highlighted in the kick-off and the release blogpost; typically this will have its own channel in Slack @@ -114,14 +114,15 @@ subsequent EE merge, as we often merge a lot to CE on the release date. For more information, see [limit conflicts with EE when developing on CE][limit_ee_conflicts]. -### Between the 7th and the 22nd +### After the 7th Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release) and security issues will be cherry-picked into the stable branch. Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch. -These fixes will be released in the next RC (before the 22nd) or patch release (after the 22nd). +These fixes will be shipped in the next RC if it is before the upcoming release. +If the fixes are are completed by after the 22nd, they will be shipped in a patch for that release. -If you think a merge request should go into the upcoming release even though it does not meet these requirements, +If you think a merge request should go into an RC or patch even though it does not meet these requirements, you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer: 1. a Release Manager From 607fa9e7056553303e3c363a8ade6b6663765636 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 17 Apr 2017 18:21:06 +0000 Subject: [PATCH 114/168] Add link to GitHub rake task --- doc/workflow/importing/import_projects_from_github.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index aece4ab34ba..8ed1d98d05b 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -10,6 +10,11 @@ in your GitLab instance sitewide. This configuration is optional, users will still be able to import their GitHub repositories with a [personal access token][gh-token]. +>**Note:** +Administrators of a GitLab instance (Community or Enterprise Edition) can also +use the [GitHub rake task][gh-rake] to import projects from GitHub without the +constrains of a Sidekiq worker. + - At its current state, GitHub importer can import: - the repository description (GitLab 7.7+) - the Git repository data (GitLab 7.7+) @@ -112,5 +117,6 @@ You can also choose a different name for the project and a different namespace, if you have the privileges to do so. [gh-import]: ../../integration/github.md "GitHub integration" +[gh-rake]: ../../administration/raketasks/github_import.md "GitHub rake task" [gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration [gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token From 41bc820bde661729a3033c7b3680481e82e131e4 Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Mon, 17 Apr 2017 18:23:24 +0000 Subject: [PATCH 115/168] Update wording. --- PROCESS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 483aeaec266..fac3c22e09f 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -119,8 +119,8 @@ information, see Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release) and security issues will be cherry-picked into the stable branch. Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch. -These fixes will be shipped in the next RC if it is before the upcoming release. -If the fixes are are completed by after the 22nd, they will be shipped in a patch for that release. +These fixes will be shipped in the next RC for that release if it is before the 22nd. +If the fixes are are completed on or after the 22nd, they will be shipped in a patch for that release. If you think a merge request should go into an RC or patch even though it does not meet these requirements, you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer: From d40970bf5eb3ee677965b0450bfceb66804c4528 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 13 Apr 2017 11:49:24 -0500 Subject: [PATCH 116/168] Normalize sizes in Gitlab::Git::Blob --- lib/gitlab/git/blob.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index e56eb0d3beb..98fd4e78126 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -8,7 +8,7 @@ module Gitlab # the user. We load as much as we can for encoding detection # (Linguist) and LFS pointer parsing. All other cases where we need full # blob data should use load_all_data!. - MAX_DATA_DISPLAY_SIZE = 10485760 + MAX_DATA_DISPLAY_SIZE = 10.megabytes attr_accessor :name, :path, :size, :data, :mode, :id, :commit_id, :loaded_size, :binary @@ -153,7 +153,7 @@ module Gitlab def lfs_size if has_lfs_version_key? size = data.match(/(?<=size )([0-9]+)/) - return size[1] if size + return size[1].to_i if size end nil From 6bd5ec6a1dc8a84236c3e144aca59c7af8b0ca6d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 13 Apr 2017 11:43:28 -0500 Subject: [PATCH 117/168] Add snippet IID to snippet page titles --- app/views/projects/snippets/edit.html.haml | 2 +- app/views/projects/snippets/show.html.haml | 2 +- app/views/snippets/edit.html.haml | 2 +- app/views/snippets/show.html.haml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml index fb39028529d..24b92094b7d 100644 --- a/app/views/projects/snippets/edit.html.haml +++ b/app/views/projects/snippets/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", @snippet.title, "Snippets" +- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" %h3.page-title Edit Snippet diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index e35385f4cab..7c6be003d4c 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -1,4 +1,4 @@ -- page_title @snippet.title, "Snippets" +- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" = render 'shared/snippets/header' diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml index 915bf98eb3e..18ebeb78f87 100644 --- a/app/views/snippets/edit.html.haml +++ b/app/views/snippets/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", @snippet.title, "Snippets" +- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" %h3.page-title Edit Snippet %hr diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index da9fb755a36..e5711ca79c7 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -1,4 +1,4 @@ -- page_title @snippet.title, "Snippets" +- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" = render 'shared/snippets/header' From 938a07f1f773a0c2702db25d855cbf6e0386411d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 13 Apr 2017 11:43:46 -0500 Subject: [PATCH 118/168] Show case insensitive matches in snippet search results --- app/helpers/snippets_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 8c02b4061ca..979264c9421 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -42,7 +42,7 @@ module SnippetsHelper 0, lined_content.size, surrounding_lines - ) if line.include?(query) + ) if line.downcase.include?(query.downcase) end used_lines.uniq.sort From f4da1a805ff3f4dc5f1889b2cce19d26885120f7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 13 Apr 2017 12:19:50 -0500 Subject: [PATCH 119/168] =?UTF-8?q?Rephrase=20error=20message=20for=20a=20?= =?UTF-8?q?diff=20with=20a=20blob=20that=E2=80=99s=20too=20large?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/projects/diffs/_content.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index 5c38b5ad9c0..438a98c3e95 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -4,7 +4,7 @@ - if diff_file.too_large? .nothing-here-block This diff could not be displayed because it is too large. - elsif blob.only_display_raw? - .nothing-here-block This file is too large to display. + .nothing-here-block The file could not be displayed because it is too large. - elsif blob_text_viewable?(blob) - if !project.repository.diffable?(blob) .nothing-here-block This diff was suppressed by a .gitattributes entry. From e2a58aecc9050332295dd82a7b77c94d517b2448 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 17 Apr 2017 14:03:17 -0500 Subject: [PATCH 120/168] Rename can_edit_blob? to can_modify_blob? --- app/helpers/blob_helper.rb | 6 +++--- app/views/projects/blob/show.html.haml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 6c3f3a61e0a..4b3ab03a69c 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -34,7 +34,7 @@ module BlobHelper if !on_top_of_branch?(project, ref) button_tag 'Edit', class: "#{common_classes} disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } # This condition applies to anonymous or users who can edit directly - elsif !current_user || (current_user && can_edit_blob?(blob, project, ref)) + elsif !current_user || (current_user && can_modify_blob?(blob, project, ref)) link_to 'Edit', edit_path(project, ref, path, options), class: "#{common_classes} btn-sm" elsif current_user && can?(current_user, :fork_project, project) button_tag 'Edit', class: "#{common_classes} js-edit-blob-link-fork-toggler" @@ -52,7 +52,7 @@ module BlobHelper button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } elsif blob.lfs_pointer? button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' } - elsif can_edit_blob?(blob, project, ref) + elsif can_modify_blob?(blob, project, ref) button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' elsif can?(current_user, :fork_project, project) continue_params = { @@ -90,7 +90,7 @@ module BlobHelper ) end - def can_edit_blob?(blob, project = @project, ref = @ref) + def can_modify_blob?(blob, project = @project, ref = @ref) !blob.lfs_pointer? && can_edit_tree?(project, ref) end diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index b6738c3380f..b9b3f3ec7a3 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -8,7 +8,7 @@ #tree-holder.tree-holder = render 'blob', blob: @blob - - if can_edit_blob?(@blob) + - if can_modify_blob?(@blob) = render 'projects/blob/remove' - title = "Replace #{@blob.name}" From d6f49b85f1cdba077f0046534314ed562a9ed418 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 17 Apr 2017 18:28:18 -0500 Subject: [PATCH 121/168] Fix specs --- spec/lib/gitlab/git/blob_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 3f494257545..e6a07a58d73 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -234,7 +234,7 @@ describe Gitlab::Git::Blob, seed_helper: true do it { expect(blob.lfs_pointer?).to eq(true) } it { expect(blob.lfs_oid).to eq("4206f951d2691c78aac4c0ce9f2b23580b2c92cdcc4336e1028742c0274938e0") } - it { expect(blob.lfs_size).to eq("19548") } + it { expect(blob.lfs_size).to eq(19548) } it { expect(blob.id).to eq("f4d76af13003d1106be7ac8c5a2a3d37ddf32c2a") } it { expect(blob.name).to eq("image.jpg") } it { expect(blob.path).to eq("files/lfs/image.jpg") } @@ -273,7 +273,7 @@ describe Gitlab::Git::Blob, seed_helper: true do it { expect(blob.lfs_pointer?).to eq(false) } it { expect(blob.lfs_oid).to eq(nil) } - it { expect(blob.lfs_size).to eq("1575078") } + it { expect(blob.lfs_size).to eq(1575078) } it { expect(blob.id).to eq("5ae35296e1f95c1ef9feda1241477ed29a448572") } it { expect(blob.name).to eq("picture-invalid.png") } it { expect(blob.path).to eq("files/lfs/picture-invalid.png") } From 00c4e1410388b6d85e3f163204a49c31f76b5349 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 13 Apr 2017 11:44:52 -0500 Subject: [PATCH 122/168] Remove format from end of URL for URLs that take a ref or path --- config/routes/project.rb | 32 ------ config/routes/repository.rb | 155 ++++++++++++++------------- spec/routing/project_routing_spec.rb | 2 +- 3 files changed, 79 insertions(+), 110 deletions(-) diff --git a/config/routes/project.rb b/config/routes/project.rb index f5009186344..fa92202c1ea 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -42,29 +42,6 @@ constraints(ProjectUrlConstrainer.new) do resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: /[^\/]+/ } end - resources :compare, only: [:index, :create] do - collection do - get :diff_for_path - end - end - - get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } - - # Don't use format parameter as file extension (old 3.0.x behavior) - # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments - scope format: false do - resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } - - resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do - member do - get :charts - get :commits - get :ci - get :languages - end - end - end - resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do member do get 'raw' @@ -128,15 +105,6 @@ constraints(ProjectUrlConstrainer.new) do end end - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - delete :merged_branches, controller: 'branches', action: :destroy_all_merged - resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do - resource :release, only: [:edit, :update] - end - - resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :protected_tags, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :variables, only: [:index, :show, :update, :create, :destroy] resources :triggers, only: [:index, :create, :edit, :update, :destroy] do member do diff --git a/config/routes/repository.rb b/config/routes/repository.rb index f8966c5ae75..5cf37a06e97 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -1,4 +1,4 @@ -# All routing related to repositoty browsing +# All routing related to repository browsing resource :repository, only: [:create] do member do @@ -6,83 +6,84 @@ resource :repository, only: [:create] do end end -resources :refs, only: [] do - collection do - get 'switch' +# Don't use format parameter as file extension (old 3.0.x behavior) +# See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments +scope format: false do + get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } + + resources :compare, only: [:index, :create] do + collection do + get :diff_for_path + end end - member do - # tree viewer logs - get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } - # Directories with leading dots erroneously get rejected if git - # ref regex used in constraints. Regex verification now done in controller. - get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: /.*/, - path: /.*/ - } + resources :refs, only: [] do + collection do + get 'switch' + end + + member do + # tree viewer logs + get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + # Directories with leading dots erroneously get rejected if git + # ref regex used in constraints. Regex verification now done in controller. + get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: { + id: /.*/, + path: /.*/ + } + end + end + + scope constraints: { id: Gitlab::Regex.git_reference_regex } do + resources :network, only: [:show] + + resources :graphs, only: [:show] do + member do + get :charts + get :commits + get :ci + get :languages + end + end + + resources :branches, only: [:index, :new, :create, :destroy] + delete :merged_branches, controller: 'branches', action: :destroy_all_merged + resources :tags, only: [:index, :show, :new, :create, :destroy] do + resource :release, only: [:edit, :update] + end + + resources :protected_branches, only: [:index, :show, :create, :update, :destroy] + resources :protected_tags, only: [:index, :show, :create, :update, :destroy] + end + + scope constraints: { id: /.+/ } do + scope controller: :blob do + get '/new/*id', action: :new, as: :new_blob + post '/create/*id', action: :create, as: :create_blob + get '/edit/*id', action: :edit, as: :edit_blob + put '/update/*id', action: :update, as: :update_blob + post '/preview/*id', action: :preview, as: :preview_blob + + scope path: '/blob/*id', as: :blob do + get :diff + get '/', action: :show + delete '/', action: :destroy + post '/', action: :create + put '/', action: :update + end + end + + get '/tree/*id', to: 'tree#show', as: :tree + get '/raw/*id', to: 'raw#show', as: :raw + get '/blame/*id', to: 'blame#show', as: :blame + get '/commits/*id', to: 'commits#show', as: :commits + + post '/create_dir/*id', to: 'tree#create_dir', as: :create_dir + + scope controller: :find_file do + get '/find_file/*id', action: :show, as: :find_file + + get '/files/*id', action: :list, as: :files + end end end - -get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob' -post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob' -get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob' -put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob' -post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob' - -scope('/blob/*id', as: :blob, controller: :blob, constraints: { id: /.+/, format: false }) do - get :diff - get '/', action: :show - delete '/', action: :destroy - post '/', action: :create - put '/', action: :update -end - -get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw -) - -get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree -) - -get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file -) - -get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files -) - -post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' -) - -get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame -) - -# File/dir history -get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /.+/, format: false }, - as: :commits -) diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 4baccacd448..a3de022d242 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -484,7 +484,7 @@ describe 'project routing' do end it 'to #list' do - expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.json') end end From fd605619361ce60500c58e5dffbbe8446cc25396 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 16 Apr 2017 14:54:41 -0700 Subject: [PATCH 123/168] Use DeleteUserWorker for removing users via spam logs Before deleting a user via a SpamLog would just call `user.destroy`, which may omit other things that need to be cleaned up. --- app/controllers/admin/spam_logs_controller.rb | 2 +- app/models/spam_log.rb | 4 ++-- spec/models/spam_log_spec.rb | 11 +++++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb index 2abfa22712d..1d66955bb71 100644 --- a/app/controllers/admin/spam_logs_controller.rb +++ b/app/controllers/admin/spam_logs_controller.rb @@ -7,7 +7,7 @@ class Admin::SpamLogsController < Admin::ApplicationController spam_log = SpamLog.find(params[:id]) if params[:remove_user] - spam_log.remove_user + spam_log.remove_user(deleted_by: current_user) redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed." else spam_log.destroy diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb index 3b8b9833565..dd21ee15c6c 100644 --- a/app/models/spam_log.rb +++ b/app/models/spam_log.rb @@ -3,9 +3,9 @@ class SpamLog < ActiveRecord::Base validates :user, presence: true - def remove_user + def remove_user(deleted_by:) user.block - user.destroy + DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true, hard_delete: true) end def text diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb index c4ec7625cb0..838fba6c92d 100644 --- a/spec/models/spam_log_spec.rb +++ b/spec/models/spam_log_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe SpamLog, models: true do + let(:admin) { create(:admin) } + describe 'associations' do it { is_expected.to belong_to(:user) } end @@ -13,13 +15,18 @@ describe SpamLog, models: true do it 'blocks the user' do spam_log = build(:spam_log) - expect { spam_log.remove_user }.to change { spam_log.user.blocked? }.to(true) + expect { spam_log.remove_user(deleted_by: admin) }.to change { spam_log.user.blocked? }.to(true) end it 'removes the user' do spam_log = build(:spam_log) + user = spam_log.user - expect { spam_log.remove_user }.to change { User.count }.by(-1) + Sidekiq::Testing.inline! do + spam_log.remove_user(deleted_by: admin) + end + + expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) end end end From bf0717802f9bea5222f0149ebc7a843664a27ef7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Apr 2017 00:22:33 +0000 Subject: [PATCH 124/168] Does not remove the GitHub remote when importing from GitHub --- app/services/projects/import_service.rb | 1 - lib/tasks/import.rake | 1 - spec/services/projects/import_service_spec.rb | 9 +++++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 4c72d5e117d..eea17e24903 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -59,7 +59,6 @@ module Projects project.repository.add_remote(project.import_type, project.import_url) project.repository.set_remote_as_mirror(project.import_type) project.repository.fetch_remote(project.import_type, forced: true) - project.repository.remove_remote(project.import_type) end def import_data diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index 15131fbf755..a9dad6a1bf0 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -52,7 +52,6 @@ class NewImporter < ::Gitlab::GithubImport::Importer project.repository.add_remote(project.import_type, project.import_url) project.repository.set_remote_as_mirror(project.import_type) project.repository.fetch_remote(project.import_type, forced: true) - project.repository.remove_remote(project.import_type) rescue => e # Expire cache to prevent scenarios such as: # 1. First import failed, but the repo was imported successfully, so +exists?+ returns true diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index 09cfa36b3b9..852a4ac852f 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -54,6 +54,15 @@ describe Projects::ImportService, services: true do expect(result[:status]).to eq :error expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository" end + + it 'does not remove the GitHub remote' do + expect_any_instance_of(Repository).to receive(:fetch_remote).and_return(true) + expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true) + + subject.execute + + expect(project.repository.raw_repository.remote_names).to include('github') + end end context 'with a non Github repository' do From 4a129ececfd0e23fff117379782e3fd3761b74ef Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 18 Apr 2017 08:59:16 +0100 Subject: [PATCH 125/168] Moved per page value to const --- app/assets/javascripts/boards/models/list.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index d83006ebe1a..f2b79a88a4a 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -3,6 +3,8 @@ /* global ListLabel */ import queryData from '../utils/query_data'; +const PER_PAGE = 20; + class List { constructor (obj) { this.id = obj.id; @@ -58,7 +60,7 @@ class List { nextPage () { if (this.issuesSize > this.issues.length) { - if (this.issues.length / 20 >= 1) { + if (this.issues.length / PER_PAGE >= 1) { this.page += 1; } From b07da07c82e17f0be5bb5398b9b0cfec52cf2d2b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 18 Apr 2017 17:03:02 +0800 Subject: [PATCH 126/168] Just enforce the output encoding for Ansi2html Fixes https://sentry.gitlap.com/gitlab/gitlabcom/issues/27545/ --- lib/ci/ansi2html.rb | 2 +- lib/gitlab/ci/trace/stream.rb | 13 +++---------- spec/lib/gitlab/ci/trace/stream_spec.rb | 21 ++++++++++++++++++++- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb index 1020452480a..b439b0ee29b 100644 --- a/lib/ci/ansi2html.rb +++ b/lib/ci/ansi2html.rb @@ -172,7 +172,7 @@ module Ci close_open_tags() OpenStruct.new( - html: @out, + html: @out.force_encoding(Encoding.default_external), state: state, append: append, truncated: truncated, diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index b929bdd55bc..e93019c4098 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -14,14 +14,7 @@ module Gitlab def initialize @stream = yield - if @stream - @stream.binmode - # Ci::Ansi2html::Converter would read from @stream directly, - # using @stream.each_line to be specific. It's safe to set - # the encoding here because IO#seek(bytes) and IO#read(bytes) - # are not characters based, so encoding doesn't matter to them. - @stream.set_encoding(Encoding.default_external) - end + @stream.binmode if @stream end def valid? @@ -68,8 +61,8 @@ module Gitlab def html(last_lines: nil) text = raw(last_lines: last_lines) - stream = StringIO.new(text) - ::Ci::Ansi2html.convert(stream).html + buffer = StringIO.new(text) + ::Ci::Ansi2html.convert(buffer).html end def extract_coverage(regex) diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 03f040f4465..40ac5a3ed37 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -71,12 +71,20 @@ describe Gitlab::Ci::Trace::Stream do end describe '#append' do + let(:tempfile) { Tempfile.new } + let(:stream) do described_class.new do - StringIO.new("12345678") + tempfile.write("12345678") + tempfile.rewind + tempfile end end + after do + tempfile.unlink + end + it "truncates and append content" do stream.append("89", 4) stream.seek(0) @@ -84,6 +92,17 @@ describe Gitlab::Ci::Trace::Stream do expect(stream.size).to eq(6) expect(stream.raw).to eq("123489") end + + it 'appends in binary mode' do + '😺'.force_encoding('ASCII-8BIT').each_char.with_index do |byte, offset| + stream.append(byte, offset) + end + + stream.seek(0) + + expect(stream.size).to eq(4) + expect(stream.raw).to eq('😺') + end end describe '#set' do From ed8afea9116533258da617f4f444f4a74b8b83f6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 18 Apr 2017 17:22:44 +0800 Subject: [PATCH 127/168] Add a changelog entry --- changelogs/unreleased/enforce-Ansi2html-output-encoding.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/enforce-Ansi2html-output-encoding.yml diff --git a/changelogs/unreleased/enforce-Ansi2html-output-encoding.yml b/changelogs/unreleased/enforce-Ansi2html-output-encoding.yml new file mode 100644 index 00000000000..b1200548518 --- /dev/null +++ b/changelogs/unreleased/enforce-Ansi2html-output-encoding.yml @@ -0,0 +1,4 @@ +--- +title: Fix trace cannot be written due to encoding +merge_request: 10758 +author: From fb60c6187df379c5ccb5882c2372f417141ddfff Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 18 Apr 2017 17:33:17 +0800 Subject: [PATCH 128/168] Use &. because rubocop. Seriously I don't think this makes sense --- lib/gitlab/ci/trace/stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index e93019c4098..68b14c7c04c 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -14,7 +14,7 @@ module Gitlab def initialize @stream = yield - @stream.binmode if @stream + @stream&.binmode end def valid? From ee65f5eecb5bc08a135481cd8924e62125dc7dbe Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Wed, 29 Mar 2017 13:12:51 +1100 Subject: [PATCH 129/168] update textarea height and refocus when attaching files also fix extra newline when pasting image into textarea --- app/assets/javascripts/dropzone_input.js | 11 +++++++---- changelogs/unreleased/30008-textarea-focus.yml | 4 ++++ 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/30008-textarea-focus.yml diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index df0e3f46827..c5fbbdaf465 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -130,13 +130,15 @@ window.DropzoneInput = (function() { var afterSelection, beforeSelection, caretEnd, caretStart, textEnd; var formattedText = text; if (shouldPad) formattedText += "\n\n"; - caretStart = $(child)[0].selectionStart; - caretEnd = $(child)[0].selectionEnd; + const textarea = child.get(0); + caretStart = textarea.selectionStart; + caretEnd = textarea.selectionEnd; textEnd = $(child).val().length; beforeSelection = $(child).val().substring(0, caretStart); afterSelection = $(child).val().substring(caretEnd, textEnd); $(child).val(beforeSelection + formattedText + afterSelection); - child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); + textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); + textarea.style.height = `${textarea.scrollHeight}px`; return form_textarea.trigger("input"); }; getFilename = function(e) { @@ -180,7 +182,7 @@ window.DropzoneInput = (function() { }; insertToTextArea = function(filename, url) { return $(child).val(function(index, val) { - return val.replace("{{" + filename + "}}", url + "\n"); + return val.replace("{{" + filename + "}}", url); }); }; appendToTextArea = function(url) { @@ -215,6 +217,7 @@ window.DropzoneInput = (function() { form.find(".markdown-selector").click(function(e) { e.preventDefault(); $(this).closest('.gfm-form').find('.div-dropzone').click(); + form_textarea.focus(); }); } diff --git a/changelogs/unreleased/30008-textarea-focus.yml b/changelogs/unreleased/30008-textarea-focus.yml new file mode 100644 index 00000000000..91837bbf96e --- /dev/null +++ b/changelogs/unreleased/30008-textarea-focus.yml @@ -0,0 +1,4 @@ +--- +title: refocus textarea after attaching a file +merge_request: +author: From b67bb566b33ab58fe3ebe81930506835cee8df3d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 18 Apr 2017 12:27:10 +0000 Subject: [PATCH 130/168] Improves support for long build traces: --- app/assets/javascripts/build.js | 24 +++- app/assets/javascripts/lib/utils/constants.js | 2 + .../javascripts/lib/utils/number_utils.js | 12 +- app/assets/stylesheets/pages/builds.scss | 13 +- app/views/projects/builds/show.html.haml | 10 +- lib/gitlab/ci/trace/stream.rb | 2 +- spec/javascripts/build_spec.js | 131 ++++++++++++++---- .../lib/utils/number_utility_spec.js | 9 +- 8 files changed, 163 insertions(+), 40 deletions(-) create mode 100644 app/assets/javascripts/lib/utils/constants.js diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 0aad95c2fe3..97f279e4be4 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -2,6 +2,8 @@ consistent-return, prefer-rest-params */ /* global Breakpoints */ +import { bytesToKiB } from './lib/utils/number_utils'; + const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; }; const AUTO_SCROLL_OFFSET = 75; const DOWN_BUILD_TRACE = '#down-build-trace'; @@ -20,6 +22,7 @@ window.Build = (function () { this.state = this.options.logState; this.buildStage = this.options.buildStage; this.$document = $(document); + this.logBytes = 0; this.updateDropdown = bind(this.updateDropdown, this); @@ -98,15 +101,22 @@ window.Build = (function () { if (log.append) { $buildContainer.append(log.html); + this.logBytes += log.size; } else { $buildContainer.html(log.html); - if (log.truncated) { - $('.js-truncated-info-size').html(` ${log.size} `); - this.$truncatedInfo.removeClass('hidden'); - this.initAffixTruncatedInfo(); - } else { - this.$truncatedInfo.addClass('hidden'); - } + this.logBytes = log.size; + } + + // if the incremental sum of logBytes we received is less than the total + // we need to show a message warning the user about that. + if (this.logBytes < log.total) { + // size is in bytes, we need to calculate KiB + const size = bytesToKiB(this.logBytes); + $('.js-truncated-info-size').html(`${size}`); + this.$truncatedInfo.removeClass('hidden'); + this.initAffixTruncatedInfo(); + } else { + this.$truncatedInfo.addClass('hidden'); } this.checkAutoscroll(); diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js new file mode 100644 index 00000000000..1e96c7ab5cd --- /dev/null +++ b/app/assets/javascripts/lib/utils/constants.js @@ -0,0 +1,2 @@ +/* eslint-disable import/prefer-default-export */ +export const BYTES_IN_KIB = 1024; diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index e2bf69ee52e..f1b07408671 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -1,4 +1,4 @@ -/* eslint-disable import/prefer-default-export */ +import { BYTES_IN_KIB } from './constants'; /** * Function that allows a number with an X amount of decimals @@ -32,3 +32,13 @@ export function formatRelevantDigits(number) { } return formattedNumber; } + +/** + * Utility function that calculates KiB of the given bytes. + * + * @param {Number} number bytes + * @return {Number} KiB + */ +export function bytesToKiB(number) { + return number / BYTES_IN_KIB; +} diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 144adbcdaef..411f1c4442b 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -61,8 +61,9 @@ .truncated-info { text-align: center; border-bottom: 1px solid; - background-color: $black-transparent; + background-color: $black; height: 45px; + padding: 15px; &.affix { top: 0; @@ -87,6 +88,16 @@ right: 5px; left: 5px; } + + .truncated-info-size { + margin: 0 5px; + } + + .raw-link { + color: inherit; + margin-left: 5px; + text-decoration: underline; + } } } diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 0faad57a312..7cb2ec83cc7 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -71,11 +71,11 @@ = custom_icon('scroll_down_hover_active') #up-build-trace %pre.build-trace#build-trace - .js-truncated-info.truncated-info.hidden - %span< - Showing last - %span.js-truncated-info-size>< - KiB of log + .js-truncated-info.truncated-info.hidden< + Showing last + %span.js-truncated-info-size.truncated-info-size>< + KiB of log - + %a.js-raw-link.raw-link{ :href => raw_namespace_project_build_path(@project.namespace, @project, @build) }>< Complete Raw %code.bash.js-build-output .build-loader-animation.js-build-refresh diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index 68b14c7c04c..fa462cbe095 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -4,7 +4,7 @@ module Gitlab # This was inspired from: http://stackoverflow.com/a/10219411/1520132 class Stream BUFFER_SIZE = 4096 - LIMIT_SIZE = 50.kilobytes + LIMIT_SIZE = 500.kilobytes attr_reader :stream diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js index 7174bf1e041..8ec96bdb583 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/build_spec.js @@ -1,11 +1,11 @@ /* eslint-disable no-new */ /* global Build */ - -require('~/lib/utils/datetime_utility'); -require('~/lib/utils/url_utility'); -require('~/build'); -require('~/breakpoints'); -require('vendor/jquery.nicescroll'); +import { bytesToKiB } from '~/lib/utils/number_utils'; +import '~/lib/utils/datetime_utility'; +import '~/lib/utils/url_utility'; +import '~/build'; +import '~/breakpoints'; +import 'vendor/jquery.nicescroll'; describe('Build', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`; @@ -144,24 +144,6 @@ describe('Build', () => { expect($('#build-trace .js-build-output').text()).toMatch(/Different/); }); - it('shows information about truncated log', () => { - jasmine.clock().tick(4001); - const [{ success }] = $.ajax.calls.argsFor(0); - - success.call($, { - html: 'Update', - status: 'success', - append: false, - truncated: true, - size: '50', - }); - - expect( - $('#build-trace .js-truncated-info').text().trim(), - ).toContain('Showing last 50 KiB of log'); - expect($('#build-trace .js-truncated-info-size').text()).toMatch('50'); - }); - it('reloads the page when the build is done', () => { spyOn(gl.utils, 'visitUrl'); @@ -176,6 +158,107 @@ describe('Build', () => { expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL); }); + + describe('truncated information', () => { + describe('when size is less than total', () => { + it('shows information about truncated log', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: 'Update', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden'); + }); + + it('shows the size in KiB', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + const size = 50; + + success.call($, { + html: 'Update', + status: 'success', + append: false, + size, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(size)}`); + }); + + it('shows incremented size', () => { + jasmine.clock().tick(4001); + let args = $.ajax.calls.argsFor(0)[0]; + args.success.call($, { + html: 'Update', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(50)}`); + + jasmine.clock().tick(4001); + args = $.ajax.calls.argsFor(2)[0]; + args.success.call($, { + html: 'Update', + status: 'success', + append: true, + size: 10, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(60)}`); + }); + + it('renders the raw link', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: 'Update', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect( + document.querySelector('.js-raw-link').textContent.trim(), + ).toContain('Complete Raw'); + }); + }); + + describe('when size is equal than total', () => { + it('does not show the trunctated information', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: 'Update', + status: 'success', + append: false, + size: 100, + total: 100, + }); + + expect(document.querySelector('.js-truncated-info').classList).toContain('hidden'); + }); + }); + }); }); }); }); diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js index 5fde8be9123..90b12c9f115 100644 --- a/spec/javascripts/lib/utils/number_utility_spec.js +++ b/spec/javascripts/lib/utils/number_utility_spec.js @@ -1,4 +1,4 @@ -import { formatRelevantDigits } from '~/lib/utils/number_utils'; +import { formatRelevantDigits, bytesToKiB } from '~/lib/utils/number_utils'; describe('Number Utils', () => { describe('formatRelevantDigits', () => { @@ -38,4 +38,11 @@ describe('Number Utils', () => { expect(leftFromDecimal.length).toBe(3); }); }); + + describe('bytesToKiB', () => { + it('calculates KiB for the given bytes', () => { + expect(bytesToKiB(1024)).toEqual(1); + expect(bytesToKiB(1000)).toEqual(0.9765625); + }); + }); }); From 476fc19743291bb34a050d380c06a5aef5926cb0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 18 Apr 2017 13:48:52 +0100 Subject: [PATCH 131/168] Fixed branches sort toggle being empty Closes #30950 --- app/helpers/sorting_helper.rb | 8 ++++++++ app/views/projects/branches/index.html.haml | 16 +++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 3a5d1b97c36..2fda98cae90 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -62,6 +62,14 @@ module SortingHelper } end + def branches_sort_options_hash + { + sort_value_name => sort_title_name, + sort_value_recently_updated => sort_title_recently_updated, + sort_value_oldest_updated => sort_title_oldest_updated + } + end + def sort_title_priority 'Priority' end diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index bd1f2d96f56..91b86280e4c 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -15,16 +15,14 @@ .dropdown.inline> %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span.light - = projects_sort_options_hash[@sort] + = branches_sort_options_hash[@sort] = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right - %li - = link_to filter_branches_path(sort: sort_value_name) do - = sort_title_name - = link_to filter_branches_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to filter_branches_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated + %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable + %li.dropdown-header + Sort by + - branches_sort_options_hash.each do |value, title| + %li + = link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value) - if can? current_user, :push_code, @project = link_to namespace_project_merged_branches_path(@project.namespace, @project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do From 1194d904dd6f92230e178c2252fbe44c8d8b0832 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 18 Apr 2017 14:06:27 +0100 Subject: [PATCH 132/168] Disable initialization table pipeline for new merge request form Closes #31052 --- app/views/projects/merge_requests/_new_submit.html.haml | 2 +- app/views/projects/merge_requests/show/_pipelines.html.haml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 03069804c86..da79ca2ee75 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -46,7 +46,7 @@ -# This tab is always loaded via AJAX - if @pipelines.any? #pipelines.pipelines.tab-pane - = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)) + = render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true .mr-loading-status = spinner diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml index de4aa255bbd..2f1dbe87619 100644 --- a/app/views/projects/merge_requests/show/_pipelines.html.haml +++ b/app/views/projects/merge_requests/show/_pipelines.html.haml @@ -1,3 +1,4 @@ - endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json) +- disable_initialization = local_assigns.fetch(:disable_initialization, false) -= render 'projects/commit/pipelines_list', endpoint: endpoint_path += render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization From 7696191e330337ab5fac207bf24aa13d211a0ded Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Tue, 18 Apr 2017 10:50:04 +0100 Subject: [PATCH 133/168] disables test settings on chat notification services when repository is empty --- .../chat_notification_service.rb | 2 +- ...s-on-services-when-repository-is-empty.yml | 4 ++++ .../chat_notification_service_spec.rb | 20 ++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index fa782c6fbb7..f2dfb87dbda 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -22,7 +22,7 @@ class ChatNotificationService < Service end def can_test? - valid? + super && valid? end def self.supported_events diff --git a/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml b/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml new file mode 100644 index 00000000000..6e43a032f20 --- /dev/null +++ b/changelogs/unreleased/31009-disable-test-settings-on-services-when-repository-is-empty.yml @@ -0,0 +1,4 @@ +--- +title: Disable test settings on chat notification services when repository is empty +merge_request: 10759 +author: diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb index c98e7ee14fd..592c90cda36 100644 --- a/spec/models/project_services/chat_notification_service_spec.rb +++ b/spec/models/project_services/chat_notification_service_spec.rb @@ -1,11 +1,29 @@ require 'spec_helper' describe ChatNotificationService, models: true do - describe "Associations" do + describe 'Associations' do before do allow(subject).to receive(:activated?).and_return(true) end it { is_expected.to validate_presence_of :webhook } end + + describe '#can_test?' do + context 'with empty repository' do + it 'returns false' do + subject.project = create(:empty_project, :empty_repo) + + expect(subject.can_test?).to be false + end + end + + context 'with repository' do + it 'returns true' do + subject.project = create(:project) + + expect(subject.can_test?).to be true + end + end + end end From ab529cddac543c2052f7347d22e87f9a6ed04182 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 13 Apr 2017 16:20:04 +0100 Subject: [PATCH 134/168] Refactor group search out of global search --- app/services/search/global_service.rb | 11 +++--- app/services/search/group_service.rb | 18 ++++++++++ app/services/search_service.rb | 2 ++ spec/services/search/global_service_spec.rb | 21 ----------- spec/services/search/group_service_spec.rb | 40 +++++++++++++++++++++ 5 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 app/services/search/group_service.rb create mode 100644 spec/services/search/group_service_spec.rb diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index 8409b592b72..ff188102b62 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -7,16 +7,13 @@ module Search end def execute - group = Group.find_by(id: params[:group_id]) if params[:group_id].present? - projects = ProjectsFinder.new(current_user: current_user).execute - - if group - projects = projects.inside_path(group.full_path) - end - Gitlab::SearchResults.new(current_user, projects, params[:search]) end + def projects + @projects ||= ProjectsFinder.new(current_user: current_user).execute + end + def scope @scope ||= begin allowed_scopes = %w[issues merge_requests milestones] diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb new file mode 100644 index 00000000000..29478e3251f --- /dev/null +++ b/app/services/search/group_service.rb @@ -0,0 +1,18 @@ +module Search + class GroupService < Search::GlobalService + attr_accessor :group + + def initialize(user, group, params) + super(user, params) + + @group = group + end + + def projects + return Project.none unless group + return @projects if defined? @projects + + @projects = super.inside_path(group.full_path) + end + end +end diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 8d46a8dab3e..22736c71725 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -54,6 +54,8 @@ class SearchService Search::ProjectService.new(project, current_user, params) elsif show_snippets? Search::SnippetService.new(current_user, params) + elsif group + Search::GroupService.new(current_user, group, params) else Search::GlobalService.new(current_user, params) end diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb index 2531607acad..cbf4f56213d 100644 --- a/spec/services/search/global_service_spec.rb +++ b/spec/services/search/global_service_spec.rb @@ -40,27 +40,6 @@ describe Search::GlobalService, services: true do expect(results.objects('projects')).to match_array [found_project] end - - context 'nested group' do - let!(:nested_group) { create(:group, :nested) } - let!(:project) { create(:empty_project, namespace: nested_group) } - - before do - project.add_master(user) - end - - it 'returns result from nested group' do - results = Search::GlobalService.new(user, search: project.path).execute - - expect(results.objects('projects')).to match_array [project] - end - - it 'returns result from descendants when search inside group' do - results = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent).execute - - expect(results.objects('projects')).to match_array [project] - end - end end end end diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb new file mode 100644 index 00000000000..38f264f6e7b --- /dev/null +++ b/spec/services/search/group_service_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Search::GroupService, services: true do + shared_examples_for 'group search' do + context 'finding projects by name' do + let(:user) { create(:user) } + let(:term) { "Project Name" } + let(:nested_group) { create(:group, :nested) } + + # These projects shouldn't be found + let!(:outside_project) { create(:empty_project, :public, name: "Outside #{term}") } + let!(:private_project) { create(:empty_project, :private, namespace: nested_group, name: "Private #{term}" )} + let!(:other_project) { create(:empty_project, :public, namespace: nested_group, name: term.reverse) } + + # These projects should be found + let!(:project1) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 1") } + let!(:project2) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 2") } + let!(:project3) { create(:empty_project, :internal, namespace: nested_group.parent, name: "Outer #{term}") } + + let(:results) { Search::GroupService.new(user, search_group, search: term).execute } + subject { results.objects('projects') } + + context 'in parent group' do + let(:search_group) { nested_group.parent } + + it { is_expected.to match_array([project1, project2, project3]) } + end + + context 'in subgroup' do + let(:search_group) { nested_group } + + it { is_expected.to match_array([project1, project2]) } + end + end + end + + describe 'basic search' do + include_examples 'group search' + end +end From baaaf4e95979c128779c735a59cc4e0ecade0bf9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 18 Apr 2017 17:49:21 +0100 Subject: [PATCH 135/168] Destroy tooltip in async buttons and tooltips --- .../javascripts/environments/components/environment_actions.js | 2 ++ .../javascripts/environments/components/environment_rollback.js | 2 ++ .../javascripts/environments/components/environment_stop.js | 2 ++ .../javascripts/vue_pipelines_index/components/async_button.vue | 2 ++ .../vue_pipelines_index/components/pipelines_actions.js | 2 ++ 5 files changed, 10 insertions(+) diff --git a/app/assets/javascripts/environments/components/environment_actions.js b/app/assets/javascripts/environments/components/environment_actions.js index 1418e8d86ee..c199c7abcac 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js +++ b/app/assets/javascripts/environments/components/environment_actions.js @@ -35,6 +35,8 @@ export default { onClickAction(endpoint) { this.isLoading = true; + $('.has-tooltip').tooltip('destroy'); + this.service.postAction(endpoint) .then(() => { this.isLoading = false; diff --git a/app/assets/javascripts/environments/components/environment_rollback.js b/app/assets/javascripts/environments/components/environment_rollback.js index baa15d9e5b5..7f3c4b78a51 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.js +++ b/app/assets/javascripts/environments/components/environment_rollback.js @@ -36,6 +36,8 @@ export default { onClick() { this.isLoading = true; + $('.has-tooltip').tooltip('destroy'); + this.service.postAction(this.retryUrl) .then(() => { this.isLoading = false; diff --git a/app/assets/javascripts/environments/components/environment_stop.js b/app/assets/javascripts/environments/components/environment_stop.js index 47102692024..949de9a9604 100644 --- a/app/assets/javascripts/environments/components/environment_stop.js +++ b/app/assets/javascripts/environments/components/environment_stop.js @@ -36,6 +36,8 @@ export default { if (confirm('Are you sure you want to stop this environment?')) { this.isLoading = true; + $('.has-tooltip').tooltip('destroy'); + this.service.postAction(this.retryUrl) .then(() => { this.isLoading = false; diff --git a/app/assets/javascripts/vue_pipelines_index/components/async_button.vue b/app/assets/javascripts/vue_pipelines_index/components/async_button.vue index 11da6e908b7..fb1f8a9e2a3 100644 --- a/app/assets/javascripts/vue_pipelines_index/components/async_button.vue +++ b/app/assets/javascripts/vue_pipelines_index/components/async_button.vue @@ -65,6 +65,8 @@ export default { makeRequest() { this.isLoading = true; + $('.has-tooltip').tooltip('destroy'); + this.service.postAction(this.endpoint) .then(() => { this.isLoading = false; diff --git a/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js b/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js index 12d80768646..74396d8249b 100644 --- a/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js +++ b/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js @@ -28,6 +28,8 @@ export default { onClickAction(endpoint) { this.isLoading = true; + $('.has-tooltip').tooltip('destroy'); + this.service.postAction(endpoint) .then(() => { this.isLoading = false; From 4622ae1ae480b85cade8191af30be9b2d0d63b70 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 18 Apr 2017 18:30:05 +0000 Subject: [PATCH 136/168] Resolve "Mini pipeline graph + status badge, when updating in real time don't change color and svg icon" --- app/assets/javascripts/ci_status_icons.js | 34 ++++++++++ .../vue_pipelines_index/components/stage.js | 28 ++------ spec/javascripts/ci_status_icon_spec.js | 44 +++++++++++++ .../vue_pipelines_index/stage_spec.js | 66 +++++++++++++++++++ 4 files changed, 149 insertions(+), 23 deletions(-) create mode 100644 app/assets/javascripts/ci_status_icons.js create mode 100644 spec/javascripts/ci_status_icon_spec.js create mode 100644 spec/javascripts/vue_pipelines_index/stage_spec.js diff --git a/app/assets/javascripts/ci_status_icons.js b/app/assets/javascripts/ci_status_icons.js new file mode 100644 index 00000000000..f16616873b2 --- /dev/null +++ b/app/assets/javascripts/ci_status_icons.js @@ -0,0 +1,34 @@ +import CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg'; +import CREATED_SVG from 'icons/_icon_status_created_borderless.svg'; +import FAILED_SVG from 'icons/_icon_status_failed_borderless.svg'; +import MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg'; +import PENDING_SVG from 'icons/_icon_status_pending_borderless.svg'; +import RUNNING_SVG from 'icons/_icon_status_running_borderless.svg'; +import SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg'; +import SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg'; +import WARNING_SVG from 'icons/_icon_status_warning_borderless.svg'; + +const StatusIconEntityMap = { + icon_status_canceled: CANCELED_SVG, + icon_status_created: CREATED_SVG, + icon_status_failed: FAILED_SVG, + icon_status_manual: MANUAL_SVG, + icon_status_pending: PENDING_SVG, + icon_status_running: RUNNING_SVG, + icon_status_skipped: SKIPPED_SVG, + icon_status_success: SUCCESS_SVG, + icon_status_warning: WARNING_SVG, +}; + +export { + CANCELED_SVG, + CREATED_SVG, + FAILED_SVG, + MANUAL_SVG, + PENDING_SVG, + RUNNING_SVG, + SKIPPED_SVG, + SUCCESS_SVG, + WARNING_SVG, + StatusIconEntityMap as default, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/components/stage.js b/app/assets/javascripts/vue_pipelines_index/components/stage.js index a2c29002707..b8cc3630611 100644 --- a/app/assets/javascripts/vue_pipelines_index/components/stage.js +++ b/app/assets/javascripts/vue_pipelines_index/components/stage.js @@ -1,32 +1,11 @@ /* global Flash */ -import canceledSvg from 'icons/_icon_status_canceled_borderless.svg'; -import createdSvg from 'icons/_icon_status_created_borderless.svg'; -import failedSvg from 'icons/_icon_status_failed_borderless.svg'; -import manualSvg from 'icons/_icon_status_manual_borderless.svg'; -import pendingSvg from 'icons/_icon_status_pending_borderless.svg'; -import runningSvg from 'icons/_icon_status_running_borderless.svg'; -import skippedSvg from 'icons/_icon_status_skipped_borderless.svg'; -import successSvg from 'icons/_icon_status_success_borderless.svg'; -import warningSvg from 'icons/_icon_status_warning_borderless.svg'; +import StatusIconEntityMap from '../../ci_status_icons'; export default { data() { - const svgsDictionary = { - icon_status_canceled: canceledSvg, - icon_status_created: createdSvg, - icon_status_failed: failedSvg, - icon_status_manual: manualSvg, - icon_status_pending: pendingSvg, - icon_status_running: runningSvg, - icon_status_skipped: skippedSvg, - icon_status_success: successSvg, - icon_status_warning: warningSvg, - }; - return { builds: '', spinner: '', - svg: svgsDictionary[this.stage.status.icon], }; }, @@ -89,6 +68,9 @@ export default { triggerButtonClass() { return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; }, + svgHTML() { + return StatusIconEntityMap[this.stage.status.icon]; + }, }, template: `
@@ -100,7 +82,7 @@ export default { data-toggle="dropdown" type="button" :aria-label="stage.title"> - +