From 1d3815f89b9b9f5ecfd6dd15158a2988603b9ed8 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 26 Jul 2017 14:21:53 +0200 Subject: Allow projects to be started from a template Started implementation for the first iteration of gitlab-org/gitlab-ce#32420. This will allow users to select a template to start with, instead of an empty repository in the project just created. Internally this is basically a small extension of the ImportExport GitLab projects we already support. We just import a certain import tar archive. This commits includes the first one: Ruby on Rails. In the future more will be added. --- app/assets/images/project_templates/rails.png | Bin 0 -> 4221 bytes .../import/gitlab_projects_controller.rb | 10 +--- app/controllers/projects_controller.rb | 12 ++++- app/models/project.rb | 1 + .../projects/create_from_template_service.rb | 14 +++++ .../projects/gitlab_projects_importer_service.rb | 33 ++++++++++++ app/views/projects/_project_templates.html.haml | 8 +++ app/views/projects/new.html.haml | 59 ++++++++++++--------- lib/gitlab/import_export.rb | 4 +- lib/gitlab/project_template.rb | 39 ++++++++++++++ spec/features/projects_spec.rb | 21 ++++++++ spec/lib/gitlab/project_template_spec.rb | 48 +++++++++++++++++ .../projects/create_from_template_service_spec.rb | 26 +++++++++ vendor/project_templates/rails.tar.gz | Bin 0 -> 899958 bytes 14 files changed, 238 insertions(+), 37 deletions(-) create mode 100644 app/assets/images/project_templates/rails.png create mode 100644 app/services/projects/create_from_template_service.rb create mode 100644 app/services/projects/gitlab_projects_importer_service.rb create mode 100644 app/views/projects/_project_templates.html.haml create mode 100644 lib/gitlab/project_template.rb create mode 100644 spec/lib/gitlab/project_template_spec.rb create mode 100644 spec/services/projects/create_from_template_service_spec.rb create mode 100644 vendor/project_templates/rails.tar.gz diff --git a/app/assets/images/project_templates/rails.png b/app/assets/images/project_templates/rails.png new file mode 100644 index 00000000000..dbee6bf6227 Binary files /dev/null and b/app/assets/images/project_templates/rails.png differ diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 36d246d185b..6463d2dfd5b 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -12,15 +12,7 @@ class Import::GitlabProjectsController < Import::BaseController return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) end - import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename) - - FileUtils.mkdir_p(File.dirname(import_upload_path)) - FileUtils.copy_entry(project_params[:file].path, import_upload_path) - - @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id], - current_user, - import_upload_path, - project_params[:path]).execute + @project = ::Projects::GitlabProjectsImporterService.new(current_user, project_params).execute if @project.saved? redirect_to( diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c769693255c..275474d02f6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -27,7 +27,12 @@ class ProjectsController < Projects::ApplicationController end def create - @project = ::Projects::CreateService.new(current_user, project_params).execute + @project = + if project_from_template? + ::Projects::CreateFromTemplateService.new(current_user, project_params).execute + else + ::Projects::CreateService.new(current_user, project_params).execute + end if @project.saved? cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) } @@ -324,6 +329,7 @@ class ProjectsController < Projects::ApplicationController :runners_token, :tag_list, :visibility_level, + :template_title, project_feature_attributes: %i[ builds_access_level @@ -345,6 +351,10 @@ class ProjectsController < Projects::ApplicationController false end + def project_from_template? + project_params[:template_title]&.present? + end + def project_view_files? if current_user current_user.project_view == 'files' diff --git a/app/models/project.rb b/app/models/project.rb index d827bfaa806..e7c404bf817 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -71,6 +71,7 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch attr_accessor :old_path_with_namespace + attr_accessor :template_title attr_writer :pipeline_status alias_attribute :title, :name diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb new file mode 100644 index 00000000000..3fc5c4ad157 --- /dev/null +++ b/app/services/projects/create_from_template_service.rb @@ -0,0 +1,14 @@ +module Projects + class CreateFromTemplateService < BaseService + def initialize(user, params) + @current_user, @params = user, params.dup + end + + def execute + params[:file] = Gitlab::ProjectTemplate.find(params[:template_title]).file + + @params[:from_template] = true + GitlabProjectsImporterService.new(@current_user, @params).execute + end + end +end diff --git a/app/services/projects/gitlab_projects_importer_service.rb b/app/services/projects/gitlab_projects_importer_service.rb new file mode 100644 index 00000000000..4cb98c54de5 --- /dev/null +++ b/app/services/projects/gitlab_projects_importer_service.rb @@ -0,0 +1,33 @@ +# This service is an adapter used to for the GitLab Import feature, and +# creating a project from a template. +# The latter will under the hood just import an archive supplied by GitLab. +module Projects + class GitlabProjectsImporterService + attr_reader :current_user, :params + + def initialize(user, params) + @current_user, @params = user, params.dup + end + + def execute + FileUtils.mkdir_p(File.dirname(import_upload_path)) + FileUtils.copy_entry(file.path, import_upload_path) + + Gitlab::ImportExport::ProjectCreator.new(params[:namespace_id], + current_user, + import_upload_path, + params[:path]).execute + end + + private + + def import_upload_path + @import_upload_path ||= Gitlab::ImportExport + .import_upload_path(filename: "#{params[:namespace_id]}-#{params[:path]}") + end + + def file + params[:file] + end + end +end diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml new file mode 100644 index 00000000000..bef64ca7433 --- /dev/null +++ b/app/views/projects/_project_templates.html.haml @@ -0,0 +1,8 @@ +.col-sm-12.template-buttons + - Gitlab::ProjectTemplate.all.each do |template| + -# The title should be the value posted to the controller, a pretty name to print would be + -# template.name + = template.title + = image_tag(template.logo_path) + + = f.text_field :template_title, placeholder: "rails", class: "form-control", tabindex: 2, autofocus: true, required: true diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index a2d7a21d5f6..c01645e2ec9 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -17,36 +17,17 @@ Create or Import your project from popular Git services .col-lg-9 = form_for @project, html: { class: 'new_project' } do |f| - .row - .form-group.col-xs-12.col-sm-6 - = f.label :namespace_id, class: 'label-light' do - %span - Project path - .form-group - .input-group - - if current_user.can_select_namespace? - .input-group-addon - = root_url - = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1} - - - else - .input-group-addon.static-namespace - #{root_url}#{current_user.username}/ - = f.hidden_field :namespace_id, value: current_user.namespace_id - .form-group.col-xs-12.col-sm-6.project-path - = f.label :path, class: 'label-light' do - %span - Project name - = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true - - if current_user.can_create_group? - .help-block - Want to house several dependent projects under the same namespace? - = link_to "Create a group", new_group_path + .project-template.js-toggle-container + .form_group.clearfix + = f.label :template_project, class: 'label-light' do + Start from template + .col-sm-12.import-buttons + = render 'project_templates', f: f - if import_sources_enabled? .project-import.js-toggle-container .form-group.clearfix - = f.label :visibility_level, class: 'label-light' do + = f.label :visibility_level, class: 'label-light' do #the label here seems wrong Import project from .col-sm-12.import-buttons %div @@ -90,6 +71,32 @@ .js-toggle-content.hide = render "shared/import_form", f: f + .row + .form-group.col-xs-12.col-sm-6 + = f.label :namespace_id, class: 'label-light' do + %span + Project path + .form-group + .input-group + - if current_user.can_select_namespace? + .input-group-addon + = root_url + = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1} + + - else + .input-group-addon.static-namespace + #{root_url}#{current_user.username}/ + = f.hidden_field :namespace_id, value: current_user.namespace_id + .form-group.col-xs-12.col-sm-6.project-path + = f.label :path, class: 'label-light' do + %span + Project name + = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true + - if current_user.can_create_group? + .help-block + Want to house several dependent projects under the same namespace? + = link_to "Create a group", new_group_path + .form-group = f.label :description, class: 'label-light' do Project description diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 3470a09eaf0..9f23d29218b 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -15,7 +15,9 @@ module Gitlab end def import_upload_path(filename:) - File.join(storage_path, 'uploads', filename) + milliseconds = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) + + File.join(storage_path, 'uploads', "#{millisecond}-#{filename}") end def project_filename diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb new file mode 100644 index 00000000000..72fcda154c5 --- /dev/null +++ b/lib/gitlab/project_template.rb @@ -0,0 +1,39 @@ +module Gitlab + class ProjectTemplate + attr_reader :title, :name + + def initialize(name, title) + @name, @title = name, title + end + + def logo_path + "project_templates/#{name}.png" + end + + def file + template_archive.open + end + + def template_archive + Rails.root.join("vendor/project_templates/#{name}.tar.gz") + end + + def ==(other) + name == other.name && title == other.title + end + + TemplatesTable = [ + ProjectTemplate.new('rails', 'Ruby on Rails') + ].freeze + + class << self + def all + TemplatesTable + end + + def find(name) + all.find { |template| template.name == name.to_s } + end + end + end +end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 10c7e5934e4..ab5042490ae 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,6 +1,27 @@ require 'spec_helper' feature 'Project', feature: true do + describe 'creating from template' do + let(:user) { create(:user) } + let(:template) { Gitlab::ProjectTemplate.find(:rails) } + + before do + sign_in user + visit new_project_path + end + + it "allows creation from the #{template.name} template" do + fill_in("project_template_title", with: template.title) + fill_in("project_path", with: template.name) + + page.within '#content-body' do + click_button "Create project" + end + + expect(page).to have_content 'Import' + end + end + describe 'description' do let(:project) { create(:project, :repository) } let(:path) { project_path(project) } diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb new file mode 100644 index 00000000000..5dc6059b49c --- /dev/null +++ b/spec/lib/gitlab/project_template_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::ProjectTemplate do + describe '.all' do + it 'returns a all templates' do + expected = [ + described_class.new('rails', 'Ruby on Rails') + ] + + expect(described_class.all).to be_an(Array) + expect(described_class.all).to eq(expected) + end + end + + describe '.find' do + subject { described_class.find(query) } + + context 'when there is a match' do + let(:query) { :rails } + + it { is_expected.to be_a(described_class) } + end + + context 'when there is no match' do + let(:query) { 'no-match' } + + it { is_expected.to be(nil) } + end + end + + describe 'instance methods' do + subject { described_class.new('phoenix', 'Phoenix Framework') } + + it { is_expected.to respond_to(:logo_path, :file, :template_archive) } + end + + describe 'validate all templates' do + described_class.all.each do |template| + it "#{template.name} has a valid archive" do + archive = template.template_archive + logo = Rails.root.join("app/assets/images/#{template.logo_path}") + + expect(File.exist?(archive)).to be(true) + expect(File.exist?(logo)).to be(true) + end + end + end +end diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb new file mode 100644 index 00000000000..81e88c0862d --- /dev/null +++ b/spec/services/projects/create_from_template_service_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Projects::CreateFromTemplateService do + let(:user) { create(:user) } + let(:project_params) do + { + path: user.to_param, + template_title: 'rails' + } + end + + subject { described_class.new(user, project_params) } + + it 'calls the importer service' do + expect_any_instance_of(Projects::GitlabProjectsImporterService).to receive(:execute) + + subject.execute + end + + it 'returns the project thats created' do + project = subject.execute + + expect(project).to be_saved + expect(project.import_status).to eq('scheduled') + end +end diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz new file mode 100644 index 00000000000..b54cae3143a Binary files /dev/null and b/vendor/project_templates/rails.tar.gz differ -- cgit v1.2.1 From e6ac171421cf19424a1312957b908cf67281da5e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 30 Jul 2017 11:56:40 +0100 Subject: Styles html according to mockup --- app/assets/stylesheets/pages/projects.scss | 21 +++++ app/views/projects/_project_templates.html.haml | 14 ++- app/views/projects/new.html.haml | 111 +++++++++++++----------- 3 files changed, 90 insertions(+), 56 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index a3e07a36c33..512f6b838ae 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -480,6 +480,27 @@ a.deploy-project-label { } } +.project-template { + .project-templates-buttons { + i { + display: block; + font-size: 24px; + margin-bottom: 4px; + } + + img { + display: block; + height: 24px; + margin: 0 auto 4px; + } + } + + &:after { + content: "OR"; + float: right; + } +} + .project-stats { font-size: 0; text-align: center; diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml index bef64ca7433..31438040ff3 100644 --- a/app/views/projects/_project_templates.html.haml +++ b/app/views/projects/_project_templates.html.haml @@ -1,8 +1,14 @@ -.col-sm-12.template-buttons + +.btn-group.project-templates-buttons{ data: { toggle: "buttons" }} + %label.btn.active + %input{ type: "radio", autocomplete: "off", name: "project_templates" } + = icon('file-o') + Blank - Gitlab::ProjectTemplate.all.each do |template| -# The title should be the value posted to the controller, a pretty name to print would be -# template.name - = template.title - = image_tag(template.logo_path) + %label.btn + %input{ type: "radio", autocomplete: "off", name: "project_templates" } + = image_tag(template.logo_path) + = template.title - = f.text_field :template_title, placeholder: "rails", class: "form-control", tabindex: 2, autofocus: true, required: true diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index c01645e2ec9..7bcdea3b59e 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -15,61 +15,68 @@ - if import_sources_enabled? %p Create or Import your project from popular Git services - .col-lg-9 + .col-lg-9.js-toggle-container = form_for @project, html: { class: 'new_project' } do |f| - .project-template.js-toggle-container - .form_group.clearfix - = f.label :template_project, class: 'label-light' do - Start from template - .col-sm-12.import-buttons - = render 'project_templates', f: f - - - if import_sources_enabled? - .project-import.js-toggle-container - .form-group.clearfix - = f.label :visibility_level, class: 'label-light' do #the label here seems wrong - Import project from - .col-sm-12.import-buttons - %div - - if github_import_enabled? - = link_to new_import_github_path, class: 'btn import_github' do - = icon('github', text: 'GitHub') - %div - - if bitbucket_import_enabled? - = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do - = icon('bitbucket', text: 'Bitbucket') - - unless bitbucket_import_configured? - = render 'bitbucket_import_modal' - %div - - if gitlab_import_enabled? - = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do - = icon('gitlab', text: 'GitLab.com') - - unless gitlab_import_configured? - = render 'gitlab_import_modal' - %div - - if google_code_import_enabled? - = link_to new_import_google_code_path, class: 'btn import_google_code' do - = icon('google', text: 'Google Code') - %div - - if fogbugz_import_enabled? - = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do - = icon('bug', text: 'Fogbugz') - %div - - if gitea_import_enabled? - = link_to new_import_gitea_url, class: 'btn import_gitea' do - = custom_icon('go_logo') - Gitea - %div - - if git_import_enabled? - %button.btn.js-toggle-button.import_git{ type: "button" } - = icon('git', text: 'Repo by URL') - .import_gitlab_project.has-tooltip{ data: { container: 'body' } } - - if gitlab_project_import_enabled? - = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do - = icon('gitlab', text: 'GitLab export') + .row + .col-lg-6 + .project-template + .form_group.clearfix + = f.label :template_project, class: 'label-light' do + Create from template + = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What's included in a template?" }, title: "What's included in a template?", class: 'has-tooltip', data: { placement: 'top'} + .col-sm-12.import-buttons + = render 'project_templates', f: f + .col-lg-6 + - if import_sources_enabled? + .project-import + .form-group.clearfix + = f.label :visibility_level, class: 'label-light' do #the label here seems wrong + Import project from + .col-sm-12.import-buttons + %div + - if github_import_enabled? + = link_to new_import_github_path, class: 'btn import_github' do + = icon('github', text: 'GitHub') + %div + - if bitbucket_import_enabled? + = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do + = icon('bitbucket', text: 'Bitbucket') + - unless bitbucket_import_configured? + = render 'bitbucket_import_modal' + %div + - if gitlab_import_enabled? + = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do + = icon('gitlab', text: 'GitLab.com') + - unless gitlab_import_configured? + = render 'gitlab_import_modal' + %div + - if google_code_import_enabled? + = link_to new_import_google_code_path, class: 'btn import_google_code' do + = icon('google', text: 'Google Code') + %div + - if fogbugz_import_enabled? + = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do + = icon('bug', text: 'Fogbugz') + %div + - if gitea_import_enabled? + = link_to new_import_gitea_url, class: 'btn import_gitea' do + = custom_icon('go_logo') + Gitea + %div + - if git_import_enabled? + %button.btn.js-toggle-button.import_git{ type: "button" } + = icon('git', text: 'Repo by URL') + .import_gitlab_project.has-tooltip{ data: { container: 'body' } } + - if gitlab_project_import_enabled? + = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do + = icon('gitlab', text: 'GitLab export') + .row + .col-lg-12 .js-toggle-content.hide - = render "shared/import_form", f: f + %hr + = render "shared/import_form", f: f + %hr.js-toggle-content.hide .row .form-group.col-xs-12.col-sm-6 -- cgit v1.2.1 From 51b418b503d30fdf51808cac1ab67ec7ae168388 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 30 Jul 2017 12:01:13 +0100 Subject: [ci skip] Adds hr between form fields --- app/views/projects/new.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 7bcdea3b59e..52041f5e38f 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -18,7 +18,7 @@ .col-lg-9.js-toggle-container = form_for @project, html: { class: 'new_project' } do |f| .row - .col-lg-6 + .col-lg-6.col-sm-12 .project-template .form_group.clearfix = f.label :template_project, class: 'label-light' do @@ -26,7 +26,7 @@ = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What's included in a template?" }, title: "What's included in a template?", class: 'has-tooltip', data: { placement: 'top'} .col-sm-12.import-buttons = render 'project_templates', f: f - .col-lg-6 + .col-lg-6.col-sm-12 - if import_sources_enabled? .project-import .form-group.clearfix @@ -76,7 +76,7 @@ .js-toggle-content.hide %hr = render "shared/import_form", f: f - %hr.js-toggle-content.hide + %hr .row .form-group.col-xs-12.col-sm-6 -- cgit v1.2.1 From 5f86347ee614445d23ec9b21cf77fcbcf790756c Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 24 Jul 2017 12:44:33 +0200 Subject: Add two more metrics for CI/CD As its hard right now to determine what is a good metric and whats not, these two are not listed in the docs, nor will they get a CHANGELOG entry. --- app/services/ci/register_job_service.rb | 24 ++++++++++++++++++++++++ app/services/projects/update_pages_service.rb | 18 ++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index b951e8d0c9f..fc87bd6a659 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -30,6 +30,7 @@ module Ci # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. build.runner_id = runner.id build.run! + register_success(build) return Result.new(build, true) rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError @@ -46,6 +47,7 @@ module Ci end end + register_failure Result.new(nil, valid) end @@ -81,5 +83,27 @@ module Ci def shared_runner_build_limits_feature_enabled? ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true' end + + def register_failure + failed_attempt_counter.increase + attempt_counter.increase + end + + def register_success(job) + job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at) + attempt_counter.increase + end + + def failed_attempt_counter + @failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job") + end + + def attempt_counter + @attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_total, "Counts the times a runner tries to register a job") + end + + def job_queue_duration_seconds + @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time') + end end end diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 749a1cc56d8..5038155ca31 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -33,8 +33,10 @@ module Projects success end rescue => e + register_failure error(e.message) ensure + register_attempt build.erase_artifacts! unless build.has_expiring_artifacts? end @@ -168,5 +170,21 @@ module Projects def sha build.sha end + + def register_attempt + pages_deployments_total_counter.increase + end + + def register_failure + pages_deployments_failed_total_counter.increase + end + + def pages_deployments_total_counter + @pages_deployments_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_total, "Counter of GitLab Pages deployments triggered") + end + + def pages_deployments_failed_total_counter + @pages_deployments_failed_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_failed_total, "Counter of GitLab Pages deployments which failed") + end end end -- cgit v1.2.1 From cb127785e08c37fd1f5e0dd11ca2327930fa443a Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 31 Jul 2017 17:33:10 +0100 Subject: Adds CSS for desktop and mobile --- app/assets/javascripts/commons/bootstrap.js | 1 + app/assets/stylesheets/pages/projects.scss | 108 ++++++++++++++++++++++-- app/views/projects/_project_templates.html.haml | 20 +++-- app/views/projects/new.html.haml | 6 +- 4 files changed, 118 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js index 36bfe457be9..607d3d88df0 100644 --- a/app/assets/javascripts/commons/bootstrap.js +++ b/app/assets/javascripts/commons/bootstrap.js @@ -3,6 +3,7 @@ import $ from 'jquery'; // bootstrap jQuery plugins import 'bootstrap-sass/assets/javascripts/bootstrap/affix'; import 'bootstrap-sass/assets/javascripts/bootstrap/alert'; +import 'bootstrap-sass/assets/javascripts/bootstrap/button'; import 'bootstrap-sass/assets/javascripts/bootstrap/dropdown'; import 'bootstrap-sass/assets/javascripts/bootstrap/modal'; import 'bootstrap-sass/assets/javascripts/bootstrap/tab'; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 512f6b838ae..d870d8dda38 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -456,6 +456,7 @@ a.deploy-project-label { } } +.project-template, .project-import { .form-group { margin-bottom: 5px; @@ -470,7 +471,7 @@ a.deploy-project-label { .btn { padding: 8px; - margin-left: 10px; + margin-right: 10px; } > div { @@ -482,23 +483,112 @@ a.deploy-project-label { .project-template { .project-templates-buttons { - i { + i, + img { display: block; + height: 24px; font-size: 24px; - margin-bottom: 4px; + margin: 4px auto; } - img { - display: block; - height: 24px; - margin: 0 auto 4px; + @media (max-width: $screen-md-max) { + i, + img { + display: inline-block; + height: 20px; + font-size: 14px; + margin: 0; + } } } + &:after { + content: "OR"; + position: absolute; + color: $gray-darkest; + right: 13px; + z-index: 2; + top: 78px; + } + + @media (max-width: $screen-md-max) { &:after { - content: "OR"; - float: right; + top: 100%; + left: 49%; + margin-top: 10px; + } + } + + @media (max-width: $screen-xs-min) { + &:after { + top: 100%; + left: 46%; + margin-top: 10px; + } + } + + @media (min-width: $screen-xs-max) and (max-width: $screen-md-max) { + &:after { + top: 100%; + left: 49%; + margin-top: 10px; } + } + +} + +.new-project-first-column { + &:after { + background: $white-light; + content: " "; + position: absolute; + top: 66%; + height: 40px; + width: 20px; + right: 7px; + z-index: 1; + } + + @media (min-width: $screen-xs-max) and (max-width: $screen-md-max) { + margin-bottom: 40px; + &:after { + top: 100%; + left: 47%; + width: 50px; + } + } + + @media (max-width: $screen-xs-max) { + margin-bottom: 40px; + + &:after { + top: 100%; + left: 42%; + width: 50px; + } + } +} + +.new-project-second-column { + &:before { + background: $gray-darkest; + width: 1px; + height: 100%; + position: absolute; + left: -23px; + display: inline-block; + content: " "; + } + + @media (max-width: $screen-md-max) { + &:before { + height: 1px; + left: 15px; + top: -20px; + right: 15px; + width: auto; + } + } } .project-stats { diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml index 31438040ff3..8a3b5290f83 100644 --- a/app/views/projects/_project_templates.html.haml +++ b/app/views/projects/_project_templates.html.haml @@ -1,14 +1,24 @@ -.btn-group.project-templates-buttons{ data: { toggle: "buttons" }} - %label.btn.active - %input{ type: "radio", autocomplete: "off", name: "project_templates" } +.project-templates-buttons.import-buttons{ data: { toggle: "buttons" }} + %div.btn.btn-default.active + %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank", checked: "true" } = icon('file-o') Blank - Gitlab::ProjectTemplate.all.each do |template| -# The title should be the value posted to the controller, a pretty name to print would be -# template.name - %label.btn - %input{ type: "radio", autocomplete: "off", name: "project_templates" } + %div.btn + %input{ type: "radio", autocomplete: "off", name: "project_templates", id: template.name } = image_tag(template.logo_path) = template.title + %div.btn.btn-default + %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank" } + = icon('file-o') + Node Express + + + %div.btn.btn-default + %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank"} + = icon('file-o') + Java Spring diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 52041f5e38f..50013ee53b5 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -18,15 +18,15 @@ .col-lg-9.js-toggle-container = form_for @project, html: { class: 'new_project' } do |f| .row - .col-lg-6.col-sm-12 + .col-lg-6.col-sm-12.new-project-first-column .project-template .form_group.clearfix = f.label :template_project, class: 'label-light' do Create from template = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What's included in a template?" }, title: "What's included in a template?", class: 'has-tooltip', data: { placement: 'top'} - .col-sm-12.import-buttons + %div = render 'project_templates', f: f - .col-lg-6.col-sm-12 + .col-lg-6.col-sm-12.new-project-second-column - if import_sources_enabled? .project-import .form-group.clearfix -- cgit v1.2.1 From 74b58131d49ac1379cc8071b1df3867bdbf54eae Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 31 Jul 2017 17:34:00 +0100 Subject: [ci skip] Removes dummy placeholders --- app/views/projects/_project_templates.html.haml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml index 8a3b5290f83..a802705051e 100644 --- a/app/views/projects/_project_templates.html.haml +++ b/app/views/projects/_project_templates.html.haml @@ -11,14 +11,3 @@ %input{ type: "radio", autocomplete: "off", name: "project_templates", id: template.name } = image_tag(template.logo_path) = template.title - - %div.btn.btn-default - %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank" } - = icon('file-o') - Node Express - - - %div.btn.btn-default - %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank"} - = icon('file-o') - Java Spring -- cgit v1.2.1 From 09974de3e3d6409e24786c116fc084c6d834fed8 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 1 Aug 2017 12:38:10 +0200 Subject: Create rake task to create project templates First iteration, and some stuff is missing. But basically this rake task does a clone of a project we've pointed it to. Than creates a project on the GDK, which should be running in the background. This project is exported, after which we move that archive to the location we need it. We clean up by removing the generated project. The first idea was to export the project on .com too, however than we might run into ImportExport versions mismatch. This could've been circumvented by checkout out an older commit locally. This however is not needed yet, so we opted to not go this route yet, instead we will iterate on what we got. --- lib/gitlab/project_template.rb | 12 ++++++-- lib/tasks/gitlab/update_templates.rake | 47 ++++++++++++++++++++++++++++++++ spec/lib/gitlab/project_template_spec.rb | 4 +-- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb index 72fcda154c5..01f9492c860 100644 --- a/lib/gitlab/project_template.rb +++ b/lib/gitlab/project_template.rb @@ -11,13 +11,17 @@ module Gitlab end def file - template_archive.open + archive_path.open end - def template_archive + def archive_path Rails.root.join("vendor/project_templates/#{name}.tar.gz") end + def clone_url + "https://gitlab.com/gitlab-org/project-templates/#{name}.git" + end + def ==(other) name == other.name && title == other.title end @@ -34,6 +38,10 @@ module Gitlab def find(name) all.find { |template| template.name == name.to_s } end + + def archive_directory + Rails.root.join("vendor_directory/project_templates") + end end end end diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake index 59c32bbe7a4..26f6276e84b 100644 --- a/lib/tasks/gitlab/update_templates.rake +++ b/lib/tasks/gitlab/update_templates.rake @@ -4,6 +4,53 @@ namespace :gitlab do TEMPLATE_DATA.each { |template| update(template) } end + desc "GitLab | Update project templates" + task :update_project_templates do + if Rails.env.production? + puts "This rake task is not meant fo production instances".red + exit(1) + end + admin = User.find_by(admin: true) + + unless admin + puts "No admin user could be found".red + exit(1) + end + + Gitlab::ProjectTemplate.all.each do |template| + params = { + import_url: template.clone_url, + namespace_id: admin.namespace.id, + path: template.title, + skip_wiki: true + } + puts "Creating project for #{template.name}" + project = Projects::CreateService.new(admin, project).execute + + loop do + if project.import_status == "finished" + puts "Import finished for #{template.name}" + break + end + + if project.import_status == "failed" + puts "Failed to import from #{project_params[:import_url]}".red + exit(1) + end + + puts "Waiting for the import to finish" + sleep(5) + project = project.reload + end + + Projects::ImportExport::ExportService.new(project, admin).execute + FileUtils.cp(project.export_project_path, template.archive_path) + Projects::DestroyService.new(admin, project).execute + puts "Exported #{template.name}".green + end + puts "Done".green + end + def update(template) sub_dir = template.repo_url.match(/([A-Za-z-]+)\.git\z/)[1] dir = File.join(vendor_directory, sub_dir) diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index 5dc6059b49c..d95dab748fe 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -31,13 +31,13 @@ describe Gitlab::ProjectTemplate do describe 'instance methods' do subject { described_class.new('phoenix', 'Phoenix Framework') } - it { is_expected.to respond_to(:logo_path, :file, :template_archive) } + it { is_expected.to respond_to(:logo_path, :file, :archive_path) } end describe 'validate all templates' do described_class.all.each do |template| it "#{template.name} has a valid archive" do - archive = template.template_archive + archive = template.archive_path logo = Rails.root.join("app/assets/images/#{template.logo_path}") expect(File.exist?(archive)).to be(true) -- cgit v1.2.1 From 5e20e448cec833cc10d2cc4c305a056e0f29ed83 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 1 Aug 2017 13:49:57 +0200 Subject: Add Gitlab::Git::Blob.batch method --- lib/gitlab/git/blob.rb | 137 ++++++++++++++++++++++----------------- spec/lib/gitlab/git/blob_spec.rb | 71 ++++++++++++++++++++ 2 files changed, 148 insertions(+), 60 deletions(-) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index db6cfc9671f..0b98be3a14f 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -20,66 +20,7 @@ module Gitlab if is_enabled find_by_gitaly(repository, sha, path) else - find_by_rugged(repository, sha, path) - end - end - end - - def find_by_gitaly(repository, sha, path) - path = path.sub(/\A\/*/, '') - path = '/' if path.empty? - name = File.basename(path) - entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE) - return unless entry - - case entry.type - when :COMMIT - new( - id: entry.oid, - name: name, - size: 0, - data: '', - path: path, - commit_id: sha - ) - when :BLOB - new( - id: entry.oid, - name: name, - size: entry.size, - data: entry.data.dup, - mode: entry.mode.to_s(8), - path: path, - commit_id: sha, - binary: binary?(entry.data) - ) - end - end - - def find_by_rugged(repository, sha, path) - commit = repository.lookup(sha) - root_tree = commit.tree - - blob_entry = find_entry_by_path(repository, root_tree.oid, path) - - return nil unless blob_entry - - if blob_entry[:type] == :commit - submodule_blob(blob_entry, path, sha) - else - blob = repository.lookup(blob_entry[:oid]) - - if blob - new( - id: blob.oid, - name: blob_entry[:name], - size: blob.size, - data: blob.content(MAX_DATA_DISPLAY_SIZE), - mode: blob_entry[:filemode].to_s(8), - path: path, - commit_id: sha, - binary: blob.binary? - ) + find_by_rugged(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE) end end end @@ -109,6 +50,22 @@ module Gitlab detect && detect[:type] == :binary end + # Returns an array of Blob instances, specified in blob_references as + # [[commit_sha, path], [commit_sha, path], ...]. If limit < 0 then the + # full blob contents are returned. If limit >= 0 then each blob will + # contain no more than limit bytes in its data attribute. + # + # Keep in mind that this method may allocate a lot of memory. It is up + # to the caller to limit the number of blobs and/or the content limit + # for the individual blobs. + # + def batch(repository, blob_references, limit: nil) + limit ||= MAX_DATA_DISPLAY_SIZE + blob_references.map do |sha, path| + find_by_rugged(repository, sha, path, limit: limit) + end + end + private # Recursive search of blob id by path @@ -153,6 +110,66 @@ module Gitlab commit_id: sha ) end + + def find_by_gitaly(repository, sha, path) + path = path.sub(/\A\/*/, '') + path = '/' if path.empty? + name = File.basename(path) + entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE) + return unless entry + + case entry.type + when :COMMIT + new( + id: entry.oid, + name: name, + size: 0, + data: '', + path: path, + commit_id: sha + ) + when :BLOB + new( + id: entry.oid, + name: name, + size: entry.size, + data: entry.data.dup, + mode: entry.mode.to_s(8), + path: path, + commit_id: sha, + binary: binary?(entry.data) + ) + end + end + + def find_by_rugged(repository, sha, path, limit:) + commit = repository.lookup(sha) + root_tree = commit.tree + + blob_entry = find_entry_by_path(repository, root_tree.oid, path) + + return nil unless blob_entry + + if blob_entry[:type] == :commit + submodule_blob(blob_entry, path, sha) + else + blob = repository.lookup(blob_entry[:oid]) + + if blob + new( + id: blob.oid, + name: blob_entry[:name], + size: blob.size, + # Rugged::Blob#content is expensive; don't call it if we don't have to. + data: limit.zero? ? '' : blob.content(limit), + mode: blob_entry[:filemode].to_s(8), + path: path, + commit_id: sha, + binary: blob.binary? + ) + end + end + end end def initialize(options) diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 3c784eda4f8..ed2a781b172 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -146,6 +146,77 @@ describe Gitlab::Git::Blob, seed_helper: true do end end + describe '.batch' do + let(:blob_references) do + [ + [SeedRepo::Commit::ID, "files/ruby/popen.rb"], + [SeedRepo::Commit::ID, 'six'] + ] + end + + subject { described_class.batch(repository, blob_references) } + + it { expect(subject.size).to eq(blob_references.size) } + + context 'first blob' do + let(:blob) { subject[0] } + + it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) } + it { expect(blob.name).to eq(SeedRepo::RubyBlob::NAME) } + it { expect(blob.path).to eq("files/ruby/popen.rb") } + it { expect(blob.commit_id).to eq(SeedRepo::Commit::ID) } + it { expect(blob.data[0..10]).to eq(SeedRepo::RubyBlob::CONTENT[0..10]) } + it { expect(blob.size).to eq(669) } + it { expect(blob.mode).to eq("100644") } + end + + context 'second blob' do + let(:blob) { subject[1] } + + it { expect(blob.id).to eq('409f37c4f05865e4fb208c771485f211a22c4c2d') } + it { expect(blob.data).to eq('') } + it 'does not mark the blob as binary' do + expect(blob).not_to be_binary + end + end + + context 'limiting' do + subject { described_class.batch(repository, blob_references, limit: limit) } + + context 'default' do + let(:limit) { nil } + + it 'limits to MAX_DATA_DISPLAY_SIZE' do + stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100) + + expect(subject.first.data.size).to eq(100) + end + end + + context 'positive' do + let(:limit) { 10 } + + it { expect(subject.first.data.size).to eq(10) } + end + + context 'zero' do + let(:limit) { 0 } + + it { expect(subject.first.data).to eq('') } + end + + context 'negative' do + let(:limit) { -1 } + + it 'ignores MAX_DATA_DISPLAY_SIZE' do + stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100) + + expect(subject.first.data.size).to eq(669) + end + end + end + end + describe 'encoding' do context 'file with russian text' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/russian.rb") } -- cgit v1.2.1 From a853d3e944abb7a6d60ea24381028020166760d1 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 1 Aug 2017 13:23:04 +0100 Subject: [ci skip] Adds svgs and css for all icons --- app/assets/images/project_templates/rails.png | Bin 4221 -> 0 bytes app/assets/stylesheets/pages/projects.scss | 42 ++++++++++++++++-------- app/views/projects/_project_templates.html.haml | 19 ++++++++--- app/views/projects/new.html.haml | 2 +- app/views/shared/icons/_java_spring.svg | 6 ++++ app/views/shared/icons/_node_express.svg | 6 ++++ app/views/shared/icons/_rails.svg | 6 ++++ lib/gitlab/project_template.rb | 4 +-- 8 files changed, 63 insertions(+), 22 deletions(-) delete mode 100644 app/assets/images/project_templates/rails.png create mode 100644 app/views/shared/icons/_java_spring.svg create mode 100644 app/views/shared/icons/_node_express.svg create mode 100644 app/views/shared/icons/_rails.svg diff --git a/app/assets/images/project_templates/rails.png b/app/assets/images/project_templates/rails.png deleted file mode 100644 index dbee6bf6227..00000000000 Binary files a/app/assets/images/project_templates/rails.png and /dev/null differ diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index d870d8dda38..12da6fc075b 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -474,33 +474,49 @@ a.deploy-project-label { margin-right: 10px; } - > div { - margin-bottom: 10px; - padding-left: 0; + .blank-option { + min-width: 70px; } - } -} -.project-template { - .project-templates-buttons { - i, - img { - display: block; + .btn-template-icon { height: 24px; + width: inherit; + display: block; + margin: 2px auto 5px; font-size: 24px; - margin: 4px auto; } @media (max-width: $screen-md-max) { - i, - img { + .btn-template-icon { display: inline-block; height: 20px; font-size: 14px; margin: 0; } } + + .icon-rails path { + fill: #C00; + } + + .icon-node-express path { + fill: #353535; + } + + .icon-java-spring path { + fill: #70AD51; + } + + + + > div { + margin-bottom: 10px; + padding-left: 0; + } } +} + +.project-template { &:after { content: "OR"; diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml index a802705051e..53d0d634ec7 100644 --- a/app/views/projects/_project_templates.html.haml +++ b/app/views/projects/_project_templates.html.haml @@ -1,13 +1,22 @@ .project-templates-buttons.import-buttons{ data: { toggle: "buttons" }} - %div.btn.btn-default.active + %div.btn.blank-option.active %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank", checked: "true" } - = icon('file-o') + = icon('file-o', class: 'btn-template-icon') Blank - Gitlab::ProjectTemplate.all.each do |template| - -# The title should be the value posted to the controller, a pretty name to print would be - -# template.name %div.btn %input{ type: "radio", autocomplete: "off", name: "project_templates", id: template.name } - = image_tag(template.logo_path) + = custom_icon(template.logo) = template.title + + -# TODO: Remove this once the templates are generated on the backend. + %div.btn + %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "node" } + = custom_icon("node_express") + Node Express + + %div.btn + %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "java" } + = custom_icon("java_spring") + Java Spring diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 50013ee53b5..8cb4b44a2d2 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -20,7 +20,7 @@ .row .col-lg-6.col-sm-12.new-project-first-column .project-template - .form_group.clearfix + .form-group = f.label :template_project, class: 'label-light' do Create from template = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What's included in a template?" }, title: "What's included in a template?", class: 'has-tooltip', data: { placement: 'top'} diff --git a/app/views/shared/icons/_java_spring.svg b/app/views/shared/icons/_java_spring.svg new file mode 100644 index 00000000000..508349aa456 --- /dev/null +++ b/app/views/shared/icons/_java_spring.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/views/shared/icons/_node_express.svg b/app/views/shared/icons/_node_express.svg new file mode 100644 index 00000000000..f2c94319f19 --- /dev/null +++ b/app/views/shared/icons/_node_express.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/views/shared/icons/_rails.svg b/app/views/shared/icons/_rails.svg new file mode 100644 index 00000000000..0bb09a705df --- /dev/null +++ b/app/views/shared/icons/_rails.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb index 01f9492c860..d90ba240fde 100644 --- a/lib/gitlab/project_template.rb +++ b/lib/gitlab/project_template.rb @@ -6,9 +6,7 @@ module Gitlab @name, @title = name, title end - def logo_path - "project_templates/#{name}.png" - end + alias_method :logo, :name def file archive_path.open -- cgit v1.2.1 From 3baf3dc955dfaad2961bba548dab940b55dfa68e Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 1 Aug 2017 14:34:11 +0200 Subject: Rename GitLabProjectImporterService and misc fixes First round of review, main changes: - templates.title is human readable, #name will be passed around - GitLabProjectImporterService has been renamed --- app/controllers/projects_controller.rb | 4 +-- app/models/project.rb | 2 +- .../projects/create_from_template_service.rb | 7 +++-- .../projects/gitlab_projects_import_service.rb | 33 ++++++++++++++++++++++ .../projects/gitlab_projects_importer_service.rb | 33 ---------------------- lib/gitlab/import_export.rb | 2 +- lib/gitlab/project_template.rb | 4 +-- spec/features/projects_spec.rb | 6 ++-- spec/lib/gitlab/project_template_spec.rb | 19 +++++++++++-- .../projects/create_from_template_service_spec.rb | 2 +- 10 files changed, 64 insertions(+), 48 deletions(-) create mode 100644 app/services/projects/gitlab_projects_import_service.rb delete mode 100644 app/services/projects/gitlab_projects_importer_service.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 275474d02f6..db4ea9c7d27 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -329,7 +329,7 @@ class ProjectsController < Projects::ApplicationController :runners_token, :tag_list, :visibility_level, - :template_title, + :template_name, project_feature_attributes: %i[ builds_access_level @@ -352,7 +352,7 @@ class ProjectsController < Projects::ApplicationController end def project_from_template? - project_params[:template_title]&.present? + project_params[:template_name]&.present? end def project_view_files? diff --git a/app/models/project.rb b/app/models/project.rb index e7c404bf817..737ad5c8884 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -71,7 +71,7 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch attr_accessor :old_path_with_namespace - attr_accessor :template_title + attr_accessor :template_name attr_writer :pipeline_status alias_attribute :title, :name diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb index 3fc5c4ad157..87d9ed7a0e6 100644 --- a/app/services/projects/create_from_template_service.rb +++ b/app/services/projects/create_from_template_service.rb @@ -5,10 +5,11 @@ module Projects end def execute - params[:file] = Gitlab::ProjectTemplate.find(params[:template_title]).file + params[:file] = Gitlab::ProjectTemplate.find(params[:template_name]).file - @params[:from_template] = true - GitlabProjectsImporterService.new(@current_user, @params).execute + GitlabProjectsImportService.new(@current_user, @params).execute + ensure + params[:file]&.close end end end diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb new file mode 100644 index 00000000000..5ba1b6436f4 --- /dev/null +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -0,0 +1,33 @@ +# This service is an adapter used to for the GitLab Import feature, and +# creating a project from a template. +# The latter will under the hood just import an archive supplied by GitLab. +module Projects + class GitlabProjectsImportService + attr_reader :current_user, :params + + def initialize(user, params) + @current_user, @params = user, params.dup + end + + def execute + FileUtils.mkdir_p(File.dirname(import_upload_path)) + FileUtils.copy_entry(file.path, import_upload_path) + + Gitlab::ImportExport::ProjectCreator.new(params[:namespace_id], + current_user, + import_upload_path, + params[:path]).execute + end + + private + + def import_upload_path + @import_upload_path ||= Gitlab::ImportExport + .import_upload_path(filename: "#{params[:namespace_id]}-#{params[:path]}") + end + + def file + params[:file] + end + end +end diff --git a/app/services/projects/gitlab_projects_importer_service.rb b/app/services/projects/gitlab_projects_importer_service.rb deleted file mode 100644 index 4cb98c54de5..00000000000 --- a/app/services/projects/gitlab_projects_importer_service.rb +++ /dev/null @@ -1,33 +0,0 @@ -# This service is an adapter used to for the GitLab Import feature, and -# creating a project from a template. -# The latter will under the hood just import an archive supplied by GitLab. -module Projects - class GitlabProjectsImporterService - attr_reader :current_user, :params - - def initialize(user, params) - @current_user, @params = user, params.dup - end - - def execute - FileUtils.mkdir_p(File.dirname(import_upload_path)) - FileUtils.copy_entry(file.path, import_upload_path) - - Gitlab::ImportExport::ProjectCreator.new(params[:namespace_id], - current_user, - import_upload_path, - params[:path]).execute - end - - private - - def import_upload_path - @import_upload_path ||= Gitlab::ImportExport - .import_upload_path(filename: "#{params[:namespace_id]}-#{params[:path]}") - end - - def file - params[:file] - end - end -end diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 9f23d29218b..30b536383f9 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -17,7 +17,7 @@ module Gitlab def import_upload_path(filename:) milliseconds = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) - File.join(storage_path, 'uploads', "#{millisecond}-#{filename}") + File.join(storage_path, 'uploads', "#{milliseconds}-#{filename}") end def project_filename diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb index d90ba240fde..cf461adf697 100644 --- a/lib/gitlab/project_template.rb +++ b/lib/gitlab/project_template.rb @@ -24,13 +24,13 @@ module Gitlab name == other.name && title == other.title end - TemplatesTable = [ + TEMPLATES_TABLE = [ ProjectTemplate.new('rails', 'Ruby on Rails') ].freeze class << self def all - TemplatesTable + TEMPLATES_TABLE end def find(name) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index ab5042490ae..9385778e0cb 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -10,15 +10,15 @@ feature 'Project', feature: true do visit new_project_path end - it "allows creation from the #{template.name} template" do - fill_in("project_template_title", with: template.title) + it "allows creation from templates" do + fill_in("project_template_name", with: template.title) fill_in("project_path", with: template.name) page.within '#content-body' do click_button "Create project" end - expect(page).to have_content 'Import' + expect(page).to have_content 'Import in progress' end end diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index d95dab748fe..0f68e87a41a 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -35,13 +35,28 @@ describe Gitlab::ProjectTemplate do end describe 'validate all templates' do + set(:admin) { create(:admin) } + described_class.all.each do |template| it "#{template.name} has a valid archive" do archive = template.archive_path - logo = Rails.root.join("app/assets/images/#{template.logo_path}") expect(File.exist?(archive)).to be(true) - expect(File.exist?(logo)).to be(true) + end + + context 'with valid parameters' do + it 'can be imported' do + params = { + template_name: template.name, + namespace_id: admin.namespace.id, + path: template.name + } + + project = Projects::CreateFromTemplateService.new(admin, params).execute + + expect(project).to be_valid + expect(project).to be_persisted + end end end end diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb index 81e88c0862d..9125b5bf161 100644 --- a/spec/services/projects/create_from_template_service_spec.rb +++ b/spec/services/projects/create_from_template_service_spec.rb @@ -12,7 +12,7 @@ describe Projects::CreateFromTemplateService do subject { described_class.new(user, project_params) } it 'calls the importer service' do - expect_any_instance_of(Projects::GitlabProjectsImporterService).to receive(:execute) + expect_any_instance_of(Projects::GitlabProjectsImportService).to receive(:execute) subject.execute end -- cgit v1.2.1 From 54e3361fa822b795e3b8ff47764f38b321f0493d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 1 Aug 2017 16:20:10 +0100 Subject: Style OR separator properly --- app/assets/stylesheets/pages/projects.scss | 117 +++++++++-------------------- app/views/projects/new.html.haml | 2 +- 2 files changed, 37 insertions(+), 82 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 12da6fc075b..dacafb0ed37 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -489,7 +489,7 @@ a.deploy-project-label { @media (max-width: $screen-md-max) { .btn-template-icon { display: inline-block; - height: 20px; + height: 14px; font-size: 14px; margin: 0; } @@ -507,8 +507,6 @@ a.deploy-project-label { fill: #70AD51; } - - > div { margin-bottom: 10px; padding-left: 0; @@ -516,93 +514,50 @@ a.deploy-project-label { } } -.project-template { - - &:after { - content: "OR"; - position: absolute; - color: $gray-darkest; - right: 13px; - z-index: 2; - top: 78px; - } - - @media (max-width: $screen-md-max) { - &:after { - top: 100%; - left: 49%; - margin-top: 10px; - } - } - - @media (max-width: $screen-xs-min) { - &:after { - top: 100%; - left: 46%; - margin-top: 10px; - } - } +.new-project-second-column { + padding-top: 30px; - @media (min-width: $screen-xs-max) and (max-width: $screen-md-max) { - &:after { - top: 100%; - left: 49%; - margin-top: 10px; - } + @media (min-width: $screen-lg-min) { + padding-top: 0; } -} - -.new-project-first-column { - &:after { - background: $white-light; - content: " "; + &::before { + content: "OR"; position: absolute; - top: 66%; - height: 40px; - width: 20px; - right: 7px; - z-index: 1; - } - - @media (min-width: $screen-xs-max) and (max-width: $screen-md-max) { - margin-bottom: 40px; - &:after { - top: 100%; - left: 47%; - width: 50px; - } - } - - @media (max-width: $screen-xs-max) { - margin-bottom: 40px; + left: 50%; + top: 0; + z-index: 10; + padding: 0 10px; + text-align: center; + background-color: $white-light; + color: $gray-darkest; + transform: translateX(-50%); - &:after { - top: 100%; - left: 42%; - width: 50px; + @media (min-width: $screen-lg-min) { + left: -35px; + top: 50%; + padding: 10px 0; + width: 20px; + line-height: 20px; + transform: translateY(-50%); } } -} -.new-project-second-column { - &:before { - background: $gray-darkest; - width: 1px; - height: 100%; + &::after { + content: ""; position: absolute; - left: -23px; - display: inline-block; - content: " "; - } - - @media (max-width: $screen-md-max) { - &:before { - height: 1px; - left: 15px; - top: -20px; - right: 15px; - width: auto; + top: 10px; + left: 10px; + right: 10px; + height: 1px; + background-color: $gray-darkest; + + @media (min-width: $screen-lg-min) { + bottom: 0; + left: -25px; + right: auto; + height: 100%; + width: 1px; } } } diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 8cb4b44a2d2..081cd3dc4dd 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -18,7 +18,7 @@ .col-lg-9.js-toggle-container = form_for @project, html: { class: 'new_project' } do |f| .row - .col-lg-6.col-sm-12.new-project-first-column + .col-lg-6.col-sm-12 .project-template .form-group = f.label :template_project, class: 'label-light' do -- cgit v1.2.1 From ba7c65a648d568b1788a4f97b893615c58febca9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 1 Aug 2017 17:00:55 +0100 Subject: Adds export form and enables export button by default --- app/assets/stylesheets/pages/projects.scss | 4 +- app/views/import/gitlab_projects/new.html.haml | 58 +++++++++++++++++++------- app/views/projects/new.html.haml | 22 +--------- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index dacafb0ed37..96720c0f83b 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -534,7 +534,7 @@ a.deploy-project-label { transform: translateX(-50%); @media (min-width: $screen-lg-min) { - left: -35px; + left: -30px; top: 50%; padding: 10px 0; width: 20px; @@ -554,7 +554,7 @@ a.deploy-project-label { @media (min-width: $screen-lg-min) { bottom: 0; - left: -25px; + left: -20px; right: auto; height: 100%; width: 1px; diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index 767dffb5589..e20e31e0b32 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -5,21 +5,47 @@ Import an exported GitLab project %hr -= form_tag import_gitlab_project_path, class: 'form-horizontal', multipart: true do - %p - Project will be imported as - %strong - #{@namespace.name}/#{@path} += form_for import_gitlab_project_path, class: 'form-horizontal', multipart: true do |f| + .row + .form-group.col-xs-12.col-sm-6 + = f.label :namespace_id, class: 'label-light' do + %span + Project path + .form-group + .input-group + - if current_user.can_select_namespace? + .input-group-addon + = root_url + = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1} - %p - To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here. - .form-group - = hidden_field_tag :namespace_id, @namespace.id - = hidden_field_tag :path, @path - = label_tag :file, class: 'control-label' do - %span GitLab project export - .col-sm-10 - = file_field_tag :file, class: '' + - else + .input-group-addon.static-namespace + #{root_url}#{current_user.username}/ + = f.hidden_field :namespace_id, value: current_user.namespace_id + .form-group.col-xs-12.col-sm-6.project-path + = f.label :path, class: 'label-light' do + %span + Project name + = f.text_field :path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true - .form-actions - = submit_tag 'Import project', class: 'btn btn-create' + + .row + .form-group.col-md-12 + To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here. + .row + .form-group.col-sm-12 + = hidden_field_tag :namespace_id, @namespace.id + = hidden_field_tag :path, @path + = f.label :file, class: 'label-light' do + %span + GitLab project export + .form-group + = file_field_tag :file, class: '' + .row + .form-actions + = f.submit 'Import project', class: 'btn btn-create' + = link_to 'Cancel', new_project_path, class: 'btn btn-cancel' + +:javascript + // get the path url and append it in the inputS + $('.js-path-name').val(gl.utils.getParameterValues('path')); diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 081cd3dc4dd..93b59dbccaf 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -67,9 +67,8 @@ %button.btn.js-toggle-button.import_git{ type: "button" } = icon('git', text: 'Repo by URL') .import_gitlab_project.has-tooltip{ data: { container: 'body' } } - - if gitlab_project_import_enabled? - = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do - = icon('gitlab', text: 'GitLab export') + = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do + = icon('gitlab', text: 'GitLab export') .row .col-lg-12 @@ -127,9 +126,6 @@ %p Please wait a moment, this page will automatically refresh when ready. :javascript - var importBtnTooltip = "Please enter a valid project name."; - var $importBtnWrapper = $('.import_gitlab_project'); - $('.how_to_import_link').bind('click', function (e) { e.preventDefault(); var import_modal = $(this).next(".modal").show(); @@ -144,25 +140,11 @@ $(".btn_import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val()); }); - $('.btn_import_gitlab_project').attr('disabled', $('#project_path').val().trim().length === 0); - $importBtnWrapper.attr('title', importBtnTooltip); - $('#new_project').submit(function(){ var $path = $('#project_path'); $path.val($path.val().trim()); }); - $('#project_path').keyup(function(){ - if($(this).val().trim().length !== 0) { - $('.btn_import_gitlab_project').attr('disabled', false); - $importBtnWrapper.attr('title',''); - $importBtnWrapper.removeClass('has-tooltip'); - } else { - $('.btn_import_gitlab_project').attr('disabled',true); - $importBtnWrapper.addClass('has-tooltip'); - } - }); - $('#project_import_url').disable(); $('.import_git').click(function( event ) { $projectImportUrl = $('#project_import_url'); -- cgit v1.2.1 From 5ef3b22e86a9662476ca7a21485e68a959921b9d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 3 Aug 2017 11:37:08 +0100 Subject: Style or separator according to review --- app/assets/stylesheets/pages/projects.scss | 110 +++++++++++++++++-------- app/views/import/gitlab_projects/new.html.haml | 2 +- app/views/projects/new.html.haml | 6 +- 3 files changed, 79 insertions(+), 39 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 96720c0f83b..2461d32a826 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -7,7 +7,8 @@ } .new_project, -.edit-project { +.edit-project, +.import-project { .sharing-and-permissions { .header { @@ -486,7 +487,7 @@ a.deploy-project-label { font-size: 24px; } - @media (max-width: $screen-md-max) { + @media (max-width: $screen-xs-max) { .btn-template-icon { display: inline-block; height: 14px; @@ -514,54 +515,93 @@ a.deploy-project-label { } } -.new-project-second-column { - padding-top: 30px; +.create-project-options { + display: flex; - @media (min-width: $screen-lg-min) { - padding-top: 0; + @media (max-width: $screen-xs-max) { + display: block; } - &::before { - content: "OR"; - position: absolute; - left: 50%; - top: 0; - z-index: 10; - padding: 0 10px; - text-align: center; - background-color: $white-light; - color: $gray-darkest; - transform: translateX(-50%); - - @media (min-width: $screen-lg-min) { - left: -30px; - top: 50%; - padding: 10px 0; - width: 20px; - line-height: 20px; - transform: translateY(-50%); + .first-column { + @media(min-width: $screen-xs-min) { + max-width: 50%; + width: 50%; + } + + @media(max-width: $screen-xs-max) { + max-width: 100%; + width: 100%; } } - &::after { - content: ""; - position: absolute; - top: 10px; - left: 10px; - right: 10px; - height: 1px; - background-color: $gray-darkest; + .second-column { + @media(min-width: $screen-xs-min) { + width: 50%; + flex: 1; + padding-left: 30px; + position: relative; + } - @media (min-width: $screen-lg-min) { + @media(max-width: $screen-xs-max) { + max-width: 100%; + width: 100%; + padding-left: 0; + position: relative; + } + + // Mobile + @media (max-width: $screen-xs-max) { + padding-top: 30px; + } + + &::before { + content: "OR"; + position: absolute; + left: 0px; + top: 40%; + z-index: 10; + padding: 8px 0; + text-align: center; + background-color: $white-light; + color: $gl-text-color-tertiary; + transform: translateX(-50%); + font-size: 12px; + font-weight: bold;; + line-height: 20px; + + // Mobile + @media (max-width: $screen-xs-max) { + left: 50%; + top: 10px; + transform: translateY(-50%); + padding: 0px 8px; + } + } + + &::after { + content: ""; + position: absolute; + background-color: $border-color; bottom: 0; - left: -20px; + left: 0; right: auto; height: 100%; width: 1px; + top: 0; + + // Mobile + @media (max-width: $screen-xs-max) { + top: 10px; + left: 10px; + right: 10px; + height: 1px; + width: auto; + } } } } + .project-stats { font-size: 0; text-align: center; diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index e20e31e0b32..2049b5dd249 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -5,7 +5,7 @@ Import an exported GitLab project %hr -= form_for import_gitlab_project_path, class: 'form-horizontal', multipart: true do |f| += form_for import_gitlab_project_path, class: 'form-horizontal import-project', multipart: true do |f| .row .form-group.col-xs-12.col-sm-6 = f.label :namespace_id, class: 'label-light' do diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 93b59dbccaf..15e8e485812 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -17,8 +17,8 @@ Create or Import your project from popular Git services .col-lg-9.js-toggle-container = form_for @project, html: { class: 'new_project' } do |f| - .row - .col-lg-6.col-sm-12 + .create-project-options + .first-column .project-template .form-group = f.label :template_project, class: 'label-light' do @@ -26,7 +26,7 @@ = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What's included in a template?" }, title: "What's included in a template?", class: 'has-tooltip', data: { placement: 'top'} %div = render 'project_templates', f: f - .col-lg-6.col-sm-12.new-project-second-column + .second-column - if import_sources_enabled? .project-import .form-group.clearfix -- cgit v1.2.1 From a5b0584dab118f1022f05d85f6b505e5138507bd Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 3 Aug 2017 11:47:27 +0100 Subject: Fix repo by url styles --- app/assets/stylesheets/pages/projects.scss | 3 ++- app/views/import/gitlab_projects/new.html.haml | 2 +- app/views/shared/_import_form.html.haml | 26 +++++++++++++------------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 2461d32a826..adeb5d55a6a 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -483,8 +483,9 @@ a.deploy-project-label { height: 24px; width: inherit; display: block; - margin: 2px auto 5px; + margin: 0 auto 4px; font-size: 24px; + top: 0; } @media (max-width: $screen-xs-max) { diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index 2049b5dd249..294de4572bd 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -5,7 +5,7 @@ Import an exported GitLab project %hr -= form_for import_gitlab_project_path, class: 'form-horizontal import-project', multipart: true do |f| += form_for import_gitlab_project_path, html: { class: 'new_project' }, multipart: true do |f| .row .form-group.col-xs-12.col-sm-6 = f.label :namespace_id, class: 'label-light' do diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 1c7c73be933..873179339dc 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -1,16 +1,16 @@ .form-group.import-url-data - = f.label :import_url, class: 'control-label' do + = f.label :import_url, class: 'label-light' do %span Git repository URL - .col-sm-10 - = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' - .well.prepend-top-20 - %ul - %li - The repository must be accessible over http://, https:// or git://. - %li - If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git. - %li - The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination. - %li - To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}. + = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' + + .well.prepend-top-20 + %ul + %li + The repository must be accessible over http://, https:// or git://. + %li + If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git. + %li + The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination. + %li + To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}. -- cgit v1.2.1 From 25c9b5b531c5dfe08f9dc9d075589a30465fa318 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 4 Aug 2017 12:13:36 +0100 Subject: Minor adjustments after UX review Fixes scss lint errors --- app/assets/stylesheets/framework/variables.scss | 8 ++++++++ app/assets/stylesheets/pages/projects.scss | 23 +++++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index cf0a1ad57d0..f25b96b87a6 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -623,3 +623,11 @@ $perf-bar-bucket-bg: #111; $perf-bar-bucket-color: #ccc; $perf-bar-bucket-box-shadow-from: rgba($white-light, .2); $perf-bar-bucket-box-shadow-to: rgba($black, .25); + + +/* +Project Templates Icons +*/ +$rails: #c00; +$node: #353535; +$java: #70ad51; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index adeb5d55a6a..2f4a8426b8b 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -485,7 +485,10 @@ a.deploy-project-label { display: block; margin: 0 auto 4px; font-size: 24px; - top: 0; + + @media (min-width: $screen-xs-max) { + top: 0; + } } @media (max-width: $screen-xs-max) { @@ -498,15 +501,15 @@ a.deploy-project-label { } .icon-rails path { - fill: #C00; + fill: $rails; } .icon-node-express path { - fill: #353535; + fill: $node; } .icon-java-spring path { - fill: #70AD51; + fill: $java; } > div { @@ -516,6 +519,10 @@ a.deploy-project-label { } } +.project-templates-buttons .btn:last-child { + margin-right: 0; +} + .create-project-options { display: flex; @@ -526,7 +533,7 @@ a.deploy-project-label { .first-column { @media(min-width: $screen-xs-min) { max-width: 50%; - width: 50%; + padding-right: 30px; } @media(max-width: $screen-xs-max) { @@ -558,7 +565,7 @@ a.deploy-project-label { &::before { content: "OR"; position: absolute; - left: 0px; + left: 0; top: 40%; z-index: 10; padding: 8px 0; @@ -567,7 +574,7 @@ a.deploy-project-label { color: $gl-text-color-tertiary; transform: translateX(-50%); font-size: 12px; - font-weight: bold;; + font-weight: bold; line-height: 20px; // Mobile @@ -575,7 +582,7 @@ a.deploy-project-label { left: 50%; top: 10px; transform: translateY(-50%); - padding: 0px 8px; + padding: 0 8px; } } -- cgit v1.2.1 From f075e6d23d9b05acc6055ab8393d318a9d523088 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 1 Aug 2017 15:26:00 -0500 Subject: remove niceScroll from job trigger variables list --- app/assets/javascripts/build_variables.js | 2 +- app/assets/stylesheets/pages/builds.scss | 8 ++++++++ app/views/projects/jobs/_sidebar.html.haml | 9 ++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js index 99082b412e2..c955a9ac2ea 100644 --- a/app/assets/javascripts/build_variables.js +++ b/app/assets/javascripts/build_variables.js @@ -2,7 +2,7 @@ $(function() { $('.reveal-variables').off('click').on('click', function() { - $('.js-build').toggle().niceScroll(); + $('.js-build-variables').toggle(); $(this).hide(); }); }); diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 28c99d8e57c..214372928d8 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -259,7 +259,15 @@ padding: 16px 0; } + .trigger-build-variables { + margin: 0; + overflow-x: auto; + -ms-overflow-style: scrollbar; + -webkit-overflow-scrolling: touch; + } + .trigger-build-variable { + font-weight: normal; color: $code-color; } diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index f2db71e8838..c876c419664 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -49,11 +49,10 @@ %p %button.btn.group.btn-group-justified.reveal-variables Reveal Variables - - - @build.trigger_request.variables.each do |key, value| - .hide.js-build - .js-build-variable.trigger-build-variable= key - .js-build-value.trigger-build-value= value + %dl.js-build-variables.trigger-build-variables.hide + - @build.trigger_request.variables.each do |key, value| + %dt.js-build-variable.trigger-build-variable= key + %dd.js-build-value.trigger-build-value= value %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") } %p -- cgit v1.2.1 From 825224ba13c636ed92262d1c7a07e77bf5f98543 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 2 Aug 2017 18:13:16 -0500 Subject: remove nicescroll from wiki sidebar --- app/assets/javascripts/wikis.js | 2 -- app/assets/stylesheets/pages/wiki.scss | 12 ++++++++++- app/views/projects/wikis/_sidebar.html.haml | 31 +++++++++++++++-------------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js index 00676bcb0b3..51ed2b4fd15 100644 --- a/app/assets/javascripts/wikis.js +++ b/app/assets/javascripts/wikis.js @@ -1,6 +1,5 @@ /* global Breakpoints */ -import 'vendor/jquery.nicescroll'; import './breakpoints'; export default class Wikis { @@ -8,7 +7,6 @@ export default class Wikis { this.bp = Breakpoints.get(); this.sidebarEl = document.querySelector('.js-wiki-sidebar'); this.sidebarExpanded = false; - $(this.sidebarEl).niceScroll(); const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle'); for (let i = 0; i < sidebarToggles.length; i += 1) { diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index 45c21c5d274..fa6bdd297eb 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -95,12 +95,22 @@ } .right-sidebar.wiki-sidebar { - padding: $gl-padding 0; + padding: 0; &.right-sidebar-collapsed { display: none; } + .sidebar-container { + padding: $gl-padding 0; + width: calc(100% + 100px); + padding-right: 100px; + height: 100%; + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + } + .blocks-container { padding: 0 $gl-padding; } diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml index e71ce1f357f..f7283ae4739 100644 --- a/app/views/projects/wikis/_sidebar.html.haml +++ b/app/views/projects/wikis/_sidebar.html.haml @@ -1,21 +1,22 @@ %aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } } - .block.wiki-sidebar-header.append-bottom-default - %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } - = icon('angle-double-right') + .sidebar-container + .block.wiki-sidebar-header.append-bottom-default + %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } + = icon('angle-double-right') - - git_access_url = project_wikis_git_access_path(@project) - = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do - = succeed ' ' do - = icon('cloud-download') - Clone repository + - git_access_url = project_wikis_git_access_path(@project) + = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do + = succeed ' ' do + = icon('cloud-download') + Clone repository - .blocks-container - .block.block-first - %ul.wiki-pages - = render @sidebar_wiki_entries, context: 'sidebar' + .blocks-container + .block.block-first + %ul.wiki-pages + = render @sidebar_wiki_entries, context: 'sidebar' - .block - = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do - More Pages + .block + = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do + More Pages = render 'projects/wikis/new' -- cgit v1.2.1 From 12c6abf9db0456c3a68b0a69a9b9022882755be0 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 2 Aug 2017 18:13:47 -0500 Subject: remove nicescroll from jobs page and remove the library from common.bundle.js --- app/assets/javascripts/build.js | 1 - app/assets/javascripts/commons/jquery.js | 1 - app/assets/stylesheets/framework/layout.scss | 12 +- app/assets/stylesheets/framework/sidebar.scss | 5 +- app/assets/stylesheets/new_sidebar.scss | 2 +- app/assets/stylesheets/pages/builds.scss | 9 + app/views/projects/jobs/_sidebar.html.haml | 169 +- spec/javascripts/build_spec.js | 1 - spec/javascripts/labels_issue_sidebar_spec.js | 1 - vendor/assets/javascripts/jquery.nicescroll.js | 3634 ------------------------ 10 files changed, 98 insertions(+), 3737 deletions(-) delete mode 100644 vendor/assets/javascripts/jquery.nicescroll.js diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index b3d3bbcf84f..940326dcd33 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -164,7 +164,6 @@ window.Build = (function () { Build.prototype.initSidebar = function () { this.$sidebar = $('.js-build-sidebar'); - this.$sidebar.niceScroll(); }; Build.prototype.getBuildTrace = function () { diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js index b53f6284afc..b93e94a3c97 100644 --- a/app/assets/javascripts/commons/jquery.js +++ b/app/assets/javascripts/commons/jquery.js @@ -6,6 +6,5 @@ import 'vendor/jquery.endless-scroll'; import 'vendor/jquery.caret'; import 'vendor/jquery.atwho'; import 'vendor/jquery.scrollTo'; -import 'vendor/jquery.nicescroll'; import 'vendor/jquery.waitforimages'; import 'select2/select2'; diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 67c3287ed74..0d8827bec11 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -109,16 +109,8 @@ body { } } - -/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch, -which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side -effects are commonly related to inconsisent z-index behavior (e.g. tooltips). By applying the following to direct children -of the body element here, we negate cascading side effects but allow momentum scrolling to be applied to the body */ - -.navbar, -.page-gutter, -.page-with-sidebar { - -webkit-overflow-scrolling: auto; +.page-with-sidebar > .content-wrapper { + min-height: calc(100vh - #{$header-height}); } .with-performance-bar .page-with-sidebar { diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 09b60ad1676..40e8a928e6e 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -78,15 +78,12 @@ .right-sidebar { border-left: 1px solid $border-color; + height: calc(100% - #{$header-height}); &.affix { position: fixed; top: $header-height; } - - &:not(.affix-top) { - min-height: 100%; - } } .with-performance-bar .right-sidebar.affix { diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 3d202183c82..bfe4d249b7d 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -21,7 +21,7 @@ $new-sidebar-width: 220px; // Override position: absolute .right-sidebar { position: fixed; - height: 100%; + height: calc(100% - #{$header-height}); } .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 214372928d8..fc77e119edb 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -235,6 +235,15 @@ display: none; } + .sidebar-container { + width: calc(100% + 100px); + padding-right: 100px; + height: 100%; + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + } + .blocks-container { padding: 0 $gl-padding; } diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index c876c419664..99f4b30d085 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -1,100 +1,101 @@ - builds = @build.pipeline.builds.to_a %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } } - .blocks-container - .block - %strong - = @build.name - %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' } - = icon('angle-double-right') - - #js-details-block-vue - - - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) + .sidebar-container + .blocks-container .block - .title - Job artifacts - - if @build.artifacts_expired? - %p.build-detail-row - The artifacts were removed - #{time_ago_with_tooltip(@build.artifacts_expire_at)} - - elsif @build.has_expiring_artifacts? - %p.build-detail-row - The artifacts will be removed in - %span.js-artifacts-remove= @build.artifacts_expire_at + %strong + = @build.name + %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-build-toggle{ href: "#", 'aria-label': 'Toggle Sidebar', role: 'button' } + = icon('angle-double-right') - - if @build.artifacts? - .btn-group.btn-group-justified{ role: :group } - - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build) - = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do - Keep + #js-details-block-vue - = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do - Download + - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) + .block + .title + Job artifacts + - if @build.artifacts_expired? + %p.build-detail-row + The artifacts were removed + #{time_ago_with_tooltip(@build.artifacts_expire_at)} + - elsif @build.has_expiring_artifacts? + %p.build-detail-row + The artifacts will be removed in + %span.js-artifacts-remove= @build.artifacts_expire_at - - if @build.artifacts_metadata? - = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do - Browse + - if @build.artifacts? + .btn-group.btn-group-justified{ role: :group } + - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build) + = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do + Keep - - if @build.trigger_request - .build-widget.block - %h4.title - Trigger + = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do + Download - %p - %span.build-light-text Token: - #{@build.trigger_request.trigger.short_token} + - if @build.artifacts_metadata? + = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do + Browse + + - if @build.trigger_request + .build-widget.block + %h4.title + Trigger - - if @build.trigger_request.variables %p - %button.btn.group.btn-group-justified.reveal-variables Reveal Variables + %span.build-light-text Token: + #{@build.trigger_request.trigger.short_token} + + - if @build.trigger_request.variables + %p + %button.btn.group.btn-group-justified.reveal-variables Reveal Variables - %dl.js-build-variables.trigger-build-variables.hide - - @build.trigger_request.variables.each do |key, value| - %dt.js-build-variable.trigger-build-variable= key - %dd.js-build-value.trigger-build-value= value + %dl.js-build-variables.trigger-build-variables.hide + - @build.trigger_request.variables.each do |key, value| + %dt.js-build-variable.trigger-build-variable= key + %dd.js-build-value.trigger-build-value= value - %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") } - %p - Commit - = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit' - = clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard") - - if @build.merge_request - in - = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request), class: 'link-commit' + %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") } + %p + Commit + = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit' + = clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard") + - if @build.merge_request + in + = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request), class: 'link-commit' - %p.build-light-text.append-bottom-0 - #{@build.pipeline.git_commit_title} + %p.build-light-text.append-bottom-0 + #{@build.pipeline.git_commit_title} - - if @build.pipeline.stages_count > 1 - .dropdown.build-dropdown - %div - %span{ class: "ci-status-icon-#{@build.pipeline.status}" } - = ci_icon_for_status(@build.pipeline.status) - Pipeline - = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit' - from - = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit' - %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.stage-selection More - = icon('chevron-down') - %ul.dropdown-menu - - @build.pipeline.legacy_stages.each do |stage| - %li - %a.stage-item= stage.name + - if @build.pipeline.stages_count > 1 + .block-last.dropdown.build-dropdown + %div + %span{ class: "ci-status-icon-#{@build.pipeline.status}" } + = ci_icon_for_status(@build.pipeline.status) + Pipeline + = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit' + from + = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit' + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.stage-selection More + = icon('chevron-down') + %ul.dropdown-menu + - @build.pipeline.legacy_stages.each do |stage| + %li + %a.stage-item= stage.name - .builds-container - - HasStatus::ORDERED_STATUSES.each do |build_status| - - builds.select{|build| build.status == build_status}.each do |build| - .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } - = link_to project_job_path(@project, build) do - = icon('arrow-right') - %span{ class: "ci-status-icon-#{build.status}" } - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id - - if build.retried? - %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } + .builds-container + - HasStatus::ORDERED_STATUSES.each do |build_status| + - builds.select{|build| build.status == build_status}.each do |build| + .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } + = link_to project_job_path(@project, build) do + = icon('arrow-right') + %span{ class: "ci-status-icon-#{build.status}" } + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id + - if build.retried? + %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js index be90dbdd88a..35149611095 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/build_spec.js @@ -5,7 +5,6 @@ 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/-/jobs/1`; diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index c99f379b871..e47adc49224 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -4,7 +4,6 @@ import '~/gl_dropdown'; import 'select2'; -import 'vendor/jquery.nicescroll'; import '~/api'; import '~/create_label'; import '~/issuable_context'; diff --git a/vendor/assets/javascripts/jquery.nicescroll.js b/vendor/assets/javascripts/jquery.nicescroll.js deleted file mode 100644 index 7653f25df4b..00000000000 --- a/vendor/assets/javascripts/jquery.nicescroll.js +++ /dev/null @@ -1,3634 +0,0 @@ -/* jquery.nicescroll --- version 3.6.0 --- copyright 2014-11-21 InuYaksa*2014 --- licensed under the MIT --- --- http://nicescroll.areaaperta.com/ --- https://github.com/inuyaksa/jquery.nicescroll --- -*/ - -(function(factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as anonymous module. - define(['jquery'], factory); - } else { - // Browser globals. - factory(jQuery); - } -}(function(jQuery) { - "use strict"; - - // globals - var domfocus = false; - var mousefocus = false; - var tabindexcounter = 0; - var ascrailcounter = 2000; - var globalmaxzindex = 0; - - var $ = jQuery; // sandbox - - // http://stackoverflow.com/questions/2161159/get-script-path - function getScriptPath() { - var scripts = document.getElementsByTagName('script'); - var path = scripts[scripts.length - 1].src.split('?')[0]; - return (path.split('/').length > 0) ? path.split('/').slice(0, -1).join('/') + '/' : ''; - } - - var vendors = ['webkit','ms','moz','o']; - - var setAnimationFrame = window.requestAnimationFrame || false; - var clearAnimationFrame = window.cancelAnimationFrame || false; - - if (!setAnimationFrame) { // legacy detection - for (var vx in vendors) { - var v = vendors[vx]; - if (!setAnimationFrame) setAnimationFrame = window[v + 'RequestAnimationFrame']; - if (!clearAnimationFrame) clearAnimationFrame = window[v + 'CancelAnimationFrame'] || window[v + 'CancelRequestAnimationFrame']; - } - } - - var ClsMutationObserver = window.MutationObserver || window.WebKitMutationObserver || false; - - var _globaloptions = { - zindex: "auto", - cursoropacitymin: 0, - cursoropacitymax: 1, - cursorcolor: "#424242", - cursorwidth: "5px", - cursorborder: "1px solid #fff", - cursorborderradius: "5px", - scrollspeed: 60, - mousescrollstep: 8 * 3, - touchbehavior: false, - hwacceleration: true, - usetransition: true, - boxzoom: false, - dblclickzoom: true, - gesturezoom: true, - grabcursorenabled: true, - autohidemode: true, - background: "", - iframeautoresize: true, - cursorminheight: 32, - preservenativescrolling: true, - railoffset: false, - railhoffset: false, - bouncescroll: true, - spacebarenabled: true, - railpadding: { - top: 0, - right: 0, - left: 0, - bottom: 0 - }, - disableoutline: true, - horizrailenabled: true, - railalign: "right", - railvalign: "bottom", - enabletranslate3d: true, - enablemousewheel: true, - enablekeyboard: true, - smoothscroll: true, - sensitiverail: true, - enablemouselockapi: true, - // cursormaxheight:false, - cursorfixedheight: false, - directionlockdeadzone: 6, - hidecursordelay: 400, - nativeparentscrolling: true, - enablescrollonselection: true, - overflowx: true, - overflowy: true, - cursordragspeed: 0.3, - rtlmode: "auto", - cursordragontouch: false, - oneaxismousemode: "auto", - scriptpath: getScriptPath(), - preventmultitouchscrolling: true - }; - - var browserdetected = false; - - var getBrowserDetection = function() { - - if (browserdetected) return browserdetected; - - var _el = document.createElement('DIV'), - _style = _el.style, - _agent = navigator.userAgent, - _platform = navigator.platform, - d = {}; - - d.haspointerlock = "pointerLockElement" in document || "webkitPointerLockElement" in document || "mozPointerLockElement" in document; - - d.isopera = ("opera" in window); // 12- - d.isopera12 = (d.isopera && ("getUserMedia" in navigator)); - d.isoperamini = (Object.prototype.toString.call(window.operamini) === "[object OperaMini]"); - - d.isie = (("all" in document) && ("attachEvent" in _el) && !d.isopera); //IE10- - d.isieold = (d.isie && !("msInterpolationMode" in _style)); // IE6 and older - d.isie7 = d.isie && !d.isieold && (!("documentMode" in document) || (document.documentMode == 7)); - d.isie8 = d.isie && ("documentMode" in document) && (document.documentMode == 8); - d.isie9 = d.isie && ("performance" in window) && (document.documentMode >= 9); - d.isie10 = d.isie && ("performance" in window) && (document.documentMode == 10); - d.isie11 = ("msRequestFullscreen" in _el) && (document.documentMode >= 11); // IE11+ - - d.isie9mobile = /iemobile.9/i.test(_agent); //wp 7.1 mango - if (d.isie9mobile) d.isie9 = false; - d.isie7mobile = (!d.isie9mobile && d.isie7) && /iemobile/i.test(_agent); //wp 7.0 - - d.ismozilla = ("MozAppearance" in _style); - - d.iswebkit = ("WebkitAppearance" in _style); - - d.ischrome = ("chrome" in window); - d.ischrome22 = (d.ischrome && d.haspointerlock); - d.ischrome26 = (d.ischrome && ("transition" in _style)); // issue with transform detection (maintain prefix) - - d.cantouch = ("ontouchstart" in document.documentElement) || ("ontouchstart" in window); // detection for Chrome Touch Emulation - d.hasmstouch = (window.MSPointerEvent || false); // IE10 pointer events - d.hasw3ctouch = (window.PointerEvent || false); //IE11 pointer events, following W3C Pointer Events spec - - d.ismac = /^mac$/i.test(_platform); - - d.isios = (d.cantouch && /iphone|ipad|ipod/i.test(_platform)); - d.isios4 = ((d.isios) && !("seal" in Object)); - d.isios7 = ((d.isios)&&("webkitHidden" in document)); //iOS 7+ - - d.isandroid = (/android/i.test(_agent)); - - d.haseventlistener = ("addEventListener" in _el); - - d.trstyle = false; - d.hastransform = false; - d.hastranslate3d = false; - d.transitionstyle = false; - d.hastransition = false; - d.transitionend = false; - - var a; - var check = ['transform', 'msTransform', 'webkitTransform', 'MozTransform', 'OTransform']; - for (a = 0; a < check.length; a++) { - if (typeof _style[check[a]] != "undefined") { - d.trstyle = check[a]; - break; - } - } - d.hastransform = (!!d.trstyle); - if (d.hastransform) { - _style[d.trstyle] = "translate3d(1px,2px,3px)"; - d.hastranslate3d = /translate3d/.test(_style[d.trstyle]); - } - - d.transitionstyle = false; - d.prefixstyle = ''; - d.transitionend = false; - check = ['transition', 'webkitTransition', 'msTransition', 'MozTransition', 'OTransition', 'OTransition', 'KhtmlTransition']; - var prefix = ['', '-webkit-', '-ms-', '-moz-', '-o-', '-o', '-khtml-']; - var evs = ['transitionend', 'webkitTransitionEnd', 'msTransitionEnd', 'transitionend', 'otransitionend', 'oTransitionEnd', 'KhtmlTransitionEnd']; - for (a = 0; a < check.length; a++) { - if (check[a] in _style) { - d.transitionstyle = check[a]; - d.prefixstyle = prefix[a]; - d.transitionend = evs[a]; - break; - } - } - if (d.ischrome26) { // always use prefix - d.prefixstyle = prefix[1]; - } - - d.hastransition = (d.transitionstyle); - - function detectCursorGrab() { - var lst = ['-webkit-grab', '-moz-grab', 'grab']; - if ((d.ischrome && !d.ischrome22) || d.isie) lst = []; // force setting for IE returns false positive and chrome cursor bug - for (var a = 0; a < lst.length; a++) { - var p = lst[a]; - _style.cursor = p; - if (_style.cursor == p) return p; - } - return 'url(//mail.google.com/mail/images/2/openhand.cur),n-resize'; // thank you google for custom cursor! - } - d.cursorgrabvalue = detectCursorGrab(); - - d.hasmousecapture = ("setCapture" in _el); - - d.hasMutationObserver = (ClsMutationObserver !== false); - - _el = null; //memory released - - browserdetected = d; - - return d; - }; - - var NiceScrollClass = function(myopt, me) { - - var self = this; - - this.version = '3.6.0'; - this.name = 'nicescroll'; - - this.me = me; - - this.opt = { - doc: $("body"), - win: false - }; - - $.extend(this.opt, _globaloptions); // clone opts - - // Options for internal use - this.opt.snapbackspeed = 80; - - if (myopt || false) { - for (var a in self.opt) { - if (typeof myopt[a] != "undefined") self.opt[a] = myopt[a]; - } - } - - this.doc = self.opt.doc; - this.iddoc = (this.doc && this.doc[0]) ? this.doc[0].id || '' : ''; - this.ispage = /^BODY|HTML/.test((self.opt.win) ? self.opt.win[0].nodeName : this.doc[0].nodeName); - this.haswrapper = (self.opt.win !== false); - this.win = self.opt.win || (this.ispage ? $(window) : this.doc); - this.docscroll = (this.ispage && !this.haswrapper) ? $(window) : this.win; - this.body = $("body"); - this.viewport = false; - - this.isfixed = false; - - this.iframe = false; - this.isiframe = ((this.doc[0].nodeName == 'IFRAME') && (this.win[0].nodeName == 'IFRAME')); - - this.istextarea = (this.win[0].nodeName == 'TEXTAREA'); - - this.forcescreen = false; //force to use screen position on events - - this.canshowonmouseevent = (self.opt.autohidemode != "scroll"); - - // Events jump table - this.onmousedown = false; - this.onmouseup = false; - this.onmousemove = false; - this.onmousewheel = false; - this.onkeypress = false; - this.ongesturezoom = false; - this.onclick = false; - - // Nicescroll custom events - this.onscrollstart = false; - this.onscrollend = false; - this.onscrollcancel = false; - - this.onzoomin = false; - this.onzoomout = false; - - // Let's start! - this.view = false; - this.page = false; - - this.scroll = { - x: 0, - y: 0 - }; - this.scrollratio = { - x: 0, - y: 0 - }; - this.cursorheight = 20; - this.scrollvaluemax = 0; - - this.isrtlmode = (this.opt.rtlmode == "auto") ? ((this.win[0] == window ? this.body : this.win).css("direction") == "rtl") : (this.opt.rtlmode === true); - // this.checkrtlmode = false; - - this.scrollrunning = false; - - this.scrollmom = false; - - this.observer = false; // observer div changes - this.observerremover = false; // observer on parent for remove detection - this.observerbody = false; // observer on body for position change - - do { - this.id = "ascrail" + (ascrailcounter++); - } while (document.getElementById(this.id)); - - this.rail = false; - this.cursor = false; - this.cursorfreezed = false; - this.selectiondrag = false; - - this.zoom = false; - this.zoomactive = false; - - this.hasfocus = false; - this.hasmousefocus = false; - - this.visibility = true; - this.railslocked = false; // locked by resize - this.locked = false; // prevent lost of locked status sets by user - this.hidden = false; // rails always hidden - this.cursoractive = true; // user can interact with cursors - - this.wheelprevented = false; //prevent mousewheel event - - this.overflowx = self.opt.overflowx; - this.overflowy = self.opt.overflowy; - - this.nativescrollingarea = false; - this.checkarea = 0; - - this.events = []; // event list for unbind - - this.saved = {}; // style saved - - this.delaylist = {}; - this.synclist = {}; - - this.lastdeltax = 0; - this.lastdeltay = 0; - - this.detected = getBrowserDetection(); - - var cap = $.extend({}, this.detected); - - this.canhwscroll = (cap.hastransform && self.opt.hwacceleration); - this.ishwscroll = (this.canhwscroll && self.haswrapper); - - this.hasreversehr = (this.isrtlmode&&!cap.iswebkit); //RTL mode with reverse horizontal axis - - this.istouchcapable = false; // desktop devices with touch screen support - - //## Check WebKit-based desktop with touch support - //## + Firefox 18 nightly build (desktop) false positive (or desktop with touch support) - if (cap.cantouch && !cap.isios && !cap.isandroid && (cap.iswebkit || cap.ismozilla)) { - this.istouchcapable = true; - cap.cantouch = false; // parse normal desktop events - } - - //## disable MouseLock API on user request - if (!self.opt.enablemouselockapi) { - cap.hasmousecapture = false; - cap.haspointerlock = false; - } - -/* deprecated - this.delayed = function(name, fn, tm, lazy) { - }; -*/ - - this.debounced = function(name, fn, tm) { - var dd = self.delaylist[name]; - self.delaylist[name] = fn; - if (!dd) { - setTimeout(function() { - var fn = self.delaylist[name]; - self.delaylist[name] = false; - fn.call(self); - }, tm); - } - }; - - var _onsync = false; - - this.synched = function(name, fn) { - - function requestSync() { - if (_onsync) return; - setAnimationFrame(function() { - _onsync = false; - for (var nn in self.synclist) { - var fn = self.synclist[nn]; - if (fn) fn.call(self); - self.synclist[nn] = false; - } - }); - _onsync = true; - } - - self.synclist[name] = fn; - requestSync(); - return name; - }; - - this.unsynched = function(name) { - if (self.synclist[name]) self.synclist[name] = false; - }; - - this.css = function(el, pars) { // save & set - for (var n in pars) { - self.saved.css.push([el, n, el.css(n)]); - el.css(n, pars[n]); - } - }; - - this.scrollTop = function(val) { - return (typeof val == "undefined") ? self.getScrollTop() : self.setScrollTop(val); - }; - - this.scrollLeft = function(val) { - return (typeof val == "undefined") ? self.getScrollLeft() : self.setScrollLeft(val); - }; - - // derived by by Dan Pupius www.pupius.net - var BezierClass = function(st, ed, spd, p1, p2, p3, p4) { - - this.st = st; - this.ed = ed; - this.spd = spd; - - this.p1 = p1 || 0; - this.p2 = p2 || 1; - this.p3 = p3 || 0; - this.p4 = p4 || 1; - - this.ts = (new Date()).getTime(); - this.df = this.ed - this.st; - }; - BezierClass.prototype = { - B2: function(t) { - return 3 * t * t * (1 - t); - }, - B3: function(t) { - return 3 * t * (1 - t) * (1 - t); - }, - B4: function(t) { - return (1 - t) * (1 - t) * (1 - t); - }, - getNow: function() { - var nw = (new Date()).getTime(); - var pc = 1 - ((nw - this.ts) / this.spd); - var bz = this.B2(pc) + this.B3(pc) + this.B4(pc); - return (pc < 0) ? this.ed : this.st + Math.round(this.df * bz); - }, - update: function(ed, spd) { - this.st = this.getNow(); - this.ed = ed; - this.spd = spd; - this.ts = (new Date()).getTime(); - this.df = this.ed - this.st; - return this; - } - }; - - //derived from http://stackoverflow.com/questions/11236090/ - function getMatrixValues() { - var tr = self.doc.css(cap.trstyle); - if (tr && (tr.substr(0, 6) == "matrix")) { - return tr.replace(/^.*\((.*)\)$/g, "$1").replace(/px/g, '').split(/, +/); - } - return false; - } - - if (this.ishwscroll) { - // hw accelerated scroll - this.doc.translate = { - x: 0, - y: 0, - tx: "0px", - ty: "0px" - }; - - //this one can help to enable hw accel on ios6 http://indiegamr.com/ios6-html-hardware-acceleration-changes-and-how-to-fix-them/ - if (cap.hastranslate3d && cap.isios) this.doc.css("-webkit-backface-visibility", "hidden"); // prevent flickering http://stackoverflow.com/questions/3461441/ - - this.getScrollTop = function(last) { - if (!last) { - var mtx = getMatrixValues(); - if (mtx) return (mtx.length == 16) ? -mtx[13] : -mtx[5]; //matrix3d 16 on IE10 - if (self.timerscroll && self.timerscroll.bz) return self.timerscroll.bz.getNow(); - } - return self.doc.translate.y; - }; - - this.getScrollLeft = function(last) { - if (!last) { - var mtx = getMatrixValues(); - if (mtx) return (mtx.length == 16) ? -mtx[12] : -mtx[4]; //matrix3d 16 on IE10 - if (self.timerscroll && self.timerscroll.bh) return self.timerscroll.bh.getNow(); - } - return self.doc.translate.x; - }; - - this.notifyScrollEvent = function(el) { - var e = document.createEvent("UIEvents"); - e.initUIEvent("scroll", false, true, window, 1); - e.niceevent = true; - el.dispatchEvent(e); - }; - - var cxscrollleft = (this.isrtlmode) ? 1 : -1; - - if (cap.hastranslate3d && self.opt.enabletranslate3d) { - this.setScrollTop = function(val, silent) { - self.doc.translate.y = val; - self.doc.translate.ty = (val * -1) + "px"; - self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)"); - if (!silent) self.notifyScrollEvent(self.win[0]); - }; - this.setScrollLeft = function(val, silent) { - self.doc.translate.x = val; - self.doc.translate.tx = (val * cxscrollleft) + "px"; - self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)"); - if (!silent) self.notifyScrollEvent(self.win[0]); - }; - } else { - this.setScrollTop = function(val, silent) { - self.doc.translate.y = val; - self.doc.translate.ty = (val * -1) + "px"; - self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")"); - if (!silent) self.notifyScrollEvent(self.win[0]); - }; - this.setScrollLeft = function(val, silent) { - self.doc.translate.x = val; - self.doc.translate.tx = (val * cxscrollleft) + "px"; - self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")"); - if (!silent) self.notifyScrollEvent(self.win[0]); - }; - } - } else { - // native scroll - this.getScrollTop = function() { - return self.docscroll.scrollTop(); - }; - this.setScrollTop = function(val) { - return self.docscroll.scrollTop(val); - }; - this.getScrollLeft = function() { - if (self.detected.ismozilla && self.isrtlmode) - return Math.abs(self.docscroll.scrollLeft()); - return self.docscroll.scrollLeft(); - }; - this.setScrollLeft = function(val) { - return self.docscroll.scrollLeft((self.detected.ismozilla && self.isrtlmode) ? -val : val); - }; - } - - this.getTarget = function(e) { - if (!e) return false; - if (e.target) return e.target; - if (e.srcElement) return e.srcElement; - return false; - }; - - this.hasParent = function(e, id) { - if (!e) return false; - var el = e.target || e.srcElement || e || false; - while (el && el.id != id) { - el = el.parentNode || false; - } - return (el !== false); - }; - - function getZIndex() { - var dom = self.win; - if ("zIndex" in dom) return dom.zIndex(); // use jQuery UI method when available - while (dom.length > 0) { - if (dom[0].nodeType == 9) return false; - var zi = dom.css('zIndex'); - if (!isNaN(zi) && zi != 0) return parseInt(zi); - dom = dom.parent(); - } - return false; - } - - //inspired by http://forum.jquery.com/topic/width-includes-border-width-when-set-to-thin-medium-thick-in-ie - var _convertBorderWidth = { - "thin": 1, - "medium": 3, - "thick": 5 - }; - - function getWidthToPixel(dom, prop, chkheight) { - var wd = dom.css(prop); - var px = parseFloat(wd); - if (isNaN(px)) { - px = _convertBorderWidth[wd] || 0; - var brd = (px == 3) ? ((chkheight) ? (self.win.outerHeight() - self.win.innerHeight()) : (self.win.outerWidth() - self.win.innerWidth())) : 1; //DON'T TRUST CSS - if (self.isie8 && px) px += 1; - return (brd) ? px : 0; - } - return px; - } - - this.getDocumentScrollOffset = function() { - return {top:window.pageYOffset||document.documentElement.scrollTop, - left:window.pageXOffset||document.documentElement.scrollLeft}; - } - - this.getOffset = function() { - if (self.isfixed) { - var ofs = self.win.offset(); // fix Chrome auto issue (when right/bottom props only) - var scrl = self.getDocumentScrollOffset(); - ofs.top-=scrl.top; - ofs.left-=scrl.left; - return ofs; - } - var ww = self.win.offset(); - if (!self.viewport) return ww; - var vp = self.viewport.offset(); - return { - top: ww.top - vp.top,// + self.viewport.scrollTop(), - left: ww.left - vp.left // + self.viewport.scrollLeft() - }; - }; - - this.updateScrollBar = function(len) { - if (self.ishwscroll) { - self.rail.css({ //** - height: self.win.innerHeight() - (self.opt.railpadding.top + self.opt.railpadding.bottom) - }); - if (self.railh) self.railh.css({ //** - width: self.win.innerWidth() - (self.opt.railpadding.left + self.opt.railpadding.right) - }); - - } else { - var wpos = self.getOffset(); - var pos = { - top: wpos.top, - left: wpos.left - (self.opt.railpadding.left + self.opt.railpadding.right) - }; - pos.top += getWidthToPixel(self.win, 'border-top-width', true); - pos.left += (self.rail.align) ? self.win.outerWidth() - getWidthToPixel(self.win, 'border-right-width') - self.rail.width : getWidthToPixel(self.win, 'border-left-width'); - - var off = self.opt.railoffset; - if (off) { - if (off.top) pos.top += off.top; - if (self.rail.align && off.left) pos.left += off.left; - } - - if (!self.railslocked) self.rail.css({ - top: pos.top, - left: pos.left, - height: ((len) ? len.h : self.win.innerHeight()) - (self.opt.railpadding.top + self.opt.railpadding.bottom) - }); - - if (self.zoom) { - self.zoom.css({ - top: pos.top + 1, - left: (self.rail.align == 1) ? pos.left - 20 : pos.left + self.rail.width + 4 - }); - } - - if (self.railh && !self.railslocked) { - var pos = { - top: wpos.top, - left: wpos.left - }; - var off = self.opt.railhoffset; - if (!!off) { - if (!!off.top) pos.top += off.top; - if (!!off.left) pos.left += off.left; - } - var y = (self.railh.align) ? pos.top + getWidthToPixel(self.win, 'border-top-width', true) + self.win.innerHeight() - self.railh.height : pos.top + getWidthToPixel(self.win, 'border-top-width', true); - var x = pos.left + getWidthToPixel(self.win, 'border-left-width'); - self.railh.css({ - top: y - (self.opt.railpadding.top + self.opt.railpadding.bottom), - left: x, - width: self.railh.width - }); - } - - - } - }; - - this.doRailClick = function(e, dbl, hr) { - var fn, pg, cur, pos; - - if (self.railslocked) return; - self.cancelEvent(e); - - if (dbl) { - fn = (hr) ? self.doScrollLeft : self.doScrollTop; - cur = (hr) ? ((e.pageX - self.railh.offset().left - (self.cursorwidth / 2)) * self.scrollratio.x) : ((e.pageY - self.rail.offset().top - (self.cursorheight / 2)) * self.scrollratio.y); - fn(cur); - } else { - fn = (hr) ? self.doScrollLeftBy : self.doScrollBy; - cur = (hr) ? self.scroll.x : self.scroll.y; - pos = (hr) ? e.pageX - self.railh.offset().left : e.pageY - self.rail.offset().top; - pg = (hr) ? self.view.w : self.view.h; - fn((cur >= pos) ? pg: -pg);// (cur >= pos) ? fn(pg): fn(-pg); - } - - }; - - self.hasanimationframe = (setAnimationFrame); - self.hascancelanimationframe = (clearAnimationFrame); - - if (!self.hasanimationframe) { - setAnimationFrame = function(fn) { - return setTimeout(fn, 15 - Math.floor((+new Date()) / 1000) % 16); - }; // 1000/60)}; - clearAnimationFrame = clearInterval; - } else if (!self.hascancelanimationframe) clearAnimationFrame = function() { - self.cancelAnimationFrame = true; - }; - - this.init = function() { - - self.saved.css = []; - - if (cap.isie7mobile) return true; // SORRY, DO NOT WORK! - if (cap.isoperamini) return true; // SORRY, DO NOT WORK! - - if (cap.hasmstouch) self.css((self.ispage) ? $("html") : self.win, { - '-ms-touch-action': 'none' - }); - - self.zindex = "auto"; - if (!self.ispage && self.opt.zindex == "auto") { - self.zindex = getZIndex() || "auto"; - } else { - self.zindex = self.opt.zindex; - } - - if (!self.ispage && self.zindex != "auto") { - if (self.zindex > globalmaxzindex) globalmaxzindex = self.zindex; - } - - if (self.isie && self.zindex == 0 && self.opt.zindex == "auto") { // fix IE auto == 0 - self.zindex = "auto"; - } - - if (!self.ispage || (!cap.cantouch && !cap.isieold && !cap.isie9mobile)) { - - var cont = self.docscroll; - if (self.ispage) cont = (self.haswrapper) ? self.win : self.doc; - - if (!cap.isie9mobile) self.css(cont, { - 'overflow-y': 'hidden' - }); - - if (self.ispage && cap.isie7) { - if (self.doc[0].nodeName == 'BODY') self.css($("html"), { - 'overflow-y': 'hidden' - }); //IE7 double scrollbar issue - else if (self.doc[0].nodeName == 'HTML') self.css($("body"), { - 'overflow-y': 'hidden' - }); //IE7 double scrollbar issue - } - - if (cap.isios && !self.ispage && !self.haswrapper) self.css($("body"), { - "-webkit-overflow-scrolling": "touch" - }); //force hw acceleration - - var cursor = $(document.createElement('div')); - cursor.css({ - position: "relative", - top: 0, - "float": "right", - width: self.opt.cursorwidth, - height: "0px", - 'background-color': self.opt.cursorcolor, - border: self.opt.cursorborder, - 'background-clip': 'padding-box', - '-webkit-border-radius': self.opt.cursorborderradius, - '-moz-border-radius': self.opt.cursorborderradius, - 'border-radius': self.opt.cursorborderradius - }); - - cursor.hborder = parseFloat(cursor.outerHeight() - cursor.innerHeight()); - - cursor.addClass('nicescroll-cursors'); - - self.cursor = cursor; - - var rail = $(document.createElement('div')); - rail.attr('id', self.id); - rail.addClass('nicescroll-rails nicescroll-rails-vr'); - - var v, a, kp = ["left","right","top","bottom"]; //** - for (var n in kp) { - a = kp[n]; - v = self.opt.railpadding[a]; - (v) ? rail.css("padding-"+a,v+"px") : self.opt.railpadding[a] = 0; - } - - rail.append(cursor); - - rail.width = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerWidth()); - rail.css({ - width: rail.width + "px", - 'zIndex': self.zindex, - "background": self.opt.background, - cursor: "default" - }); - - rail.visibility = true; - rail.scrollable = true; - - rail.align = (self.opt.railalign == "left") ? 0 : 1; - - self.rail = rail; - - self.rail.drag = false; - - var zoom = false; - if (self.opt.boxzoom && !self.ispage && !cap.isieold) { - zoom = document.createElement('div'); - - self.bind(zoom, "click", self.doZoom); - self.bind(zoom, "mouseenter", function() { - self.zoom.css('opacity', self.opt.cursoropacitymax); - }); - self.bind(zoom, "mouseleave", function() { - self.zoom.css('opacity', self.opt.cursoropacitymin); - }); - - self.zoom = $(zoom); - self.zoom.css({ - "cursor": "pointer", - 'z-index': self.zindex, - 'backgroundImage': 'url(' + self.opt.scriptpath + 'zoomico.png)', - 'height': 18, - 'width': 18, - 'backgroundPosition': '0px 0px' - }); - if (self.opt.dblclickzoom) self.bind(self.win, "dblclick", self.doZoom); - if (cap.cantouch && self.opt.gesturezoom) { - self.ongesturezoom = function(e) { - if (e.scale > 1.5) self.doZoomIn(e); - if (e.scale < 0.8) self.doZoomOut(e); - return self.cancelEvent(e); - }; - self.bind(self.win, "gestureend", self.ongesturezoom); - } - } - - // init HORIZ - - self.railh = false; - var railh; - - if (self.opt.horizrailenabled) { - - self.css(cont, { - 'overflow-x': 'hidden' - }); - - var cursor = $(document.createElement('div')); - cursor.css({ - position: "absolute", - top: 0, - height: self.opt.cursorwidth, - width: "0px", - 'background-color': self.opt.cursorcolor, - border: self.opt.cursorborder, - 'background-clip': 'padding-box', - '-webkit-border-radius': self.opt.cursorborderradius, - '-moz-border-radius': self.opt.cursorborderradius, - 'border-radius': self.opt.cursorborderradius - }); - - if (cap.isieold) cursor.css({'overflow':'hidden'}); //IE6 horiz scrollbar issue - - cursor.wborder = parseFloat(cursor.outerWidth() - cursor.innerWidth()); - - cursor.addClass('nicescroll-cursors'); - - self.cursorh = cursor; - - railh = $(document.createElement('div')); - railh.attr('id', self.id + '-hr'); - railh.addClass('nicescroll-rails nicescroll-rails-hr'); - railh.height = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerHeight()); - railh.css({ - height: railh.height + "px", - 'zIndex': self.zindex, - "background": self.opt.background - }); - - railh.append(cursor); - - railh.visibility = true; - railh.scrollable = true; - - railh.align = (self.opt.railvalign == "top") ? 0 : 1; - - self.railh = railh; - - self.railh.drag = false; - - } - - // - - if (self.ispage) { - rail.css({ - position: "fixed", - top: "0px", - height: "100%" - }); - (rail.align) ? rail.css({ - right: "0px" - }): rail.css({ - left: "0px" - }); - self.body.append(rail); - if (self.railh) { - railh.css({ - position: "fixed", - left: "0px", - width: "100%" - }); - (railh.align) ? railh.css({ - bottom: "0px" - }): railh.css({ - top: "0px" - }); - self.body.append(railh); - } - } else { - if (self.ishwscroll) { - if (self.win.css('position') == 'static') self.css(self.win, { - 'position': 'relative' - }); - var bd = (self.win[0].nodeName == 'HTML') ? self.body : self.win; - $(bd).scrollTop(0).scrollLeft(0); // fix rail position if content already scrolled - if (self.zoom) { - self.zoom.css({ - position: "absolute", - top: 1, - right: 0, - "margin-right": rail.width + 4 - }); - bd.append(self.zoom); - } - rail.css({ - position: "absolute", - top: 0 - }); - (rail.align) ? rail.css({ - right: 0 - }): rail.css({ - left: 0 - }); - bd.append(rail); - if (railh) { - railh.css({ - position: "absolute", - left: 0, - bottom: 0 - }); - (railh.align) ? railh.css({ - bottom: 0 - }): railh.css({ - top: 0 - }); - bd.append(railh); - } - } else { - self.isfixed = (self.win.css("position") == "fixed"); - var rlpos = (self.isfixed) ? "fixed" : "absolute"; - - if (!self.isfixed) self.viewport = self.getViewport(self.win[0]); - if (self.viewport) { - self.body = self.viewport; - if ((/fixed|absolute/.test(self.viewport.css("position"))) == false) self.css(self.viewport, { - "position": "relative" - }); - } - - rail.css({ - position: rlpos - }); - if (self.zoom) self.zoom.css({ - position: rlpos - }); - self.updateScrollBar(); - self.body.append(rail); - if (self.zoom) self.body.append(self.zoom); - if (self.railh) { - railh.css({ - position: rlpos - }); - self.body.append(railh); - } - } - - if (cap.isios) self.css(self.win, { - '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', - '-webkit-touch-callout': 'none' - }); // prevent grey layer on click - - if (cap.isie && self.opt.disableoutline) self.win.attr("hideFocus", "true"); // IE, prevent dotted rectangle on focused div - if (cap.iswebkit && self.opt.disableoutline) self.win.css({"outline": "none"}); // Webkit outline - //if (cap.isopera&&self.opt.disableoutline) self.win.css({"outline":"0"}); // Opera 12- to test [TODO] - - } - - if (self.opt.autohidemode === false) { - self.autohidedom = false; - self.rail.css({ - opacity: self.opt.cursoropacitymax - }); - if (self.railh) self.railh.css({ - opacity: self.opt.cursoropacitymax - }); - } else if ((self.opt.autohidemode === true) || (self.opt.autohidemode === "leave")) { - self.autohidedom = $().add(self.rail); - if (cap.isie8) self.autohidedom = self.autohidedom.add(self.cursor); - if (self.railh) self.autohidedom = self.autohidedom.add(self.railh); - if (self.railh && cap.isie8) self.autohidedom = self.autohidedom.add(self.cursorh); - } else if (self.opt.autohidemode == "scroll") { - self.autohidedom = $().add(self.rail); - if (self.railh) self.autohidedom = self.autohidedom.add(self.railh); - } else if (self.opt.autohidemode == "cursor") { - self.autohidedom = $().add(self.cursor); - if (self.railh) self.autohidedom = self.autohidedom.add(self.cursorh); - } else if (self.opt.autohidemode == "hidden") { - self.autohidedom = false; - self.hide(); - self.railslocked = false; - } - - if (cap.isie9mobile) { - - self.scrollmom = new ScrollMomentumClass2D(self); - - self.onmangotouch = function() { - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - - if ((py == self.scrollmom.lastscrolly) && (px == self.scrollmom.lastscrollx)) return true; - - var dfy = py - self.mangotouch.sy; - var dfx = px - self.mangotouch.sx; - var df = Math.round(Math.sqrt(Math.pow(dfx, 2) + Math.pow(dfy, 2))); - if (df == 0) return; - - var dry = (dfy < 0) ? -1 : 1; - var drx = (dfx < 0) ? -1 : 1; - - var tm = +new Date(); - if (self.mangotouch.lazy) clearTimeout(self.mangotouch.lazy); - - if (((tm - self.mangotouch.tm) > 80) || (self.mangotouch.dry != dry) || (self.mangotouch.drx != drx)) { - self.scrollmom.stop(); - self.scrollmom.reset(px, py); - self.mangotouch.sy = py; - self.mangotouch.ly = py; - self.mangotouch.sx = px; - self.mangotouch.lx = px; - self.mangotouch.dry = dry; - self.mangotouch.drx = drx; - self.mangotouch.tm = tm; - } else { - - self.scrollmom.stop(); - self.scrollmom.update(self.mangotouch.sx - dfx, self.mangotouch.sy - dfy); - self.mangotouch.tm = tm; - - var ds = Math.max(Math.abs(self.mangotouch.ly - py), Math.abs(self.mangotouch.lx - px)); - self.mangotouch.ly = py; - self.mangotouch.lx = px; - - if (ds > 2) { - self.mangotouch.lazy = setTimeout(function() { - self.mangotouch.lazy = false; - self.mangotouch.dry = 0; - self.mangotouch.drx = 0; - self.mangotouch.tm = 0; - self.scrollmom.doMomentum(30); - }, 100); - } - } - }; - - var top = self.getScrollTop(); - var lef = self.getScrollLeft(); - self.mangotouch = { - sy: top, - ly: top, - dry: 0, - sx: lef, - lx: lef, - drx: 0, - lazy: false, - tm: 0 - }; - - self.bind(self.docscroll, "scroll", self.onmangotouch); - - } else { - - if (cap.cantouch || self.istouchcapable || self.opt.touchbehavior || cap.hasmstouch) { - - self.scrollmom = new ScrollMomentumClass2D(self); - - self.ontouchstart = function(e) { - if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; - - self.hasmoving = false; - - if (!self.railslocked) { - - var tg; - if (cap.hasmstouch) { - tg = (e.target) ? e.target : false; - while (tg) { - var nc = $(tg).getNiceScroll(); - if ((nc.length > 0) && (nc[0].me == self.me)) break; - if (nc.length > 0) return false; - if ((tg.nodeName == 'DIV') && (tg.id == self.id)) break; - tg = (tg.parentNode) ? tg.parentNode : false; - } - } - - self.cancelScroll(); - - tg = self.getTarget(e); - - if (tg) { - var skp = (/INPUT/i.test(tg.nodeName)) && (/range/i.test(tg.type)); - if (skp) return self.stopPropagation(e); - } - - if (!("clientX" in e) && ("changedTouches" in e)) { - e.clientX = e.changedTouches[0].clientX; - e.clientY = e.changedTouches[0].clientY; - } - - if (self.forcescreen) { - var le = e; - e = { - "original": (e.original) ? e.original : e - }; - e.clientX = le.screenX; - e.clientY = le.screenY; - } - - self.rail.drag = { - x: e.clientX, - y: e.clientY, - sx: self.scroll.x, - sy: self.scroll.y, - st: self.getScrollTop(), - sl: self.getScrollLeft(), - pt: 2, - dl: false - }; - - if (self.ispage || !self.opt.directionlockdeadzone) { - self.rail.drag.dl = "f"; - } else { - - var view = { - w: $(window).width(), - h: $(window).height() - }; - - var page = { - w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth), - h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - }; - - var maxh = Math.max(0, page.h - view.h); - var maxw = Math.max(0, page.w - view.w); - - if (!self.rail.scrollable && self.railh.scrollable) self.rail.drag.ck = (maxh > 0) ? "v" : false; - else if (self.rail.scrollable && !self.railh.scrollable) self.rail.drag.ck = (maxw > 0) ? "h" : false; - else self.rail.drag.ck = false; - if (!self.rail.drag.ck) self.rail.drag.dl = "f"; - } - - if (self.opt.touchbehavior && self.isiframe && cap.isie) { - var wp = self.win.position(); - self.rail.drag.x += wp.left; - self.rail.drag.y += wp.top; - } - - self.hasmoving = false; - self.lastmouseup = false; - self.scrollmom.reset(e.clientX, e.clientY); - - if (!cap.cantouch && !this.istouchcapable && !e.pointerType) { - - var ip = (tg) ? /INPUT|SELECT|TEXTAREA/i.test(tg.nodeName) : false; - if (!ip) { - if (!self.ispage && cap.hasmousecapture) tg.setCapture(); - if (self.opt.touchbehavior) { - if (tg.onclick && !(tg._onclick || false)) { // intercept DOM0 onclick event - tg._onclick = tg.onclick; - tg.onclick = function(e) { - if (self.hasmoving) return false; - tg._onclick.call(this, e); - }; - } - return self.cancelEvent(e); - } - return self.stopPropagation(e); - } - - if (/SUBMIT|CANCEL|BUTTON/i.test($(tg).attr('type'))) { - pc = { - "tg": tg, - "click": false - }; - self.preventclick = pc; - } - - } - } - - }; - - self.ontouchend = function(e) { - if (!self.rail.drag) return true; - if (self.rail.drag.pt == 2) { - if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; - self.scrollmom.doMomentum(); - self.rail.drag = false; - if (self.hasmoving) { - self.lastmouseup = true; - self.hideCursor(); - if (cap.hasmousecapture) document.releaseCapture(); - if (!cap.cantouch) return self.cancelEvent(e); - } - } - else if (self.rail.drag.pt == 1) { - return self.onmouseup(e); - } - - }; - - var moveneedoffset = (self.opt.touchbehavior && self.isiframe && !cap.hasmousecapture); - - self.ontouchmove = function(e, byiframe) { - - if (!self.rail.drag) return false; - - if (e.targetTouches && self.opt.preventmultitouchscrolling) { - if (e.targetTouches.length > 1) return false; // multitouch - } - - if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false; - - if (self.rail.drag.pt == 2) { - if (cap.cantouch && (cap.isios) && (typeof e.original == "undefined")) return true; // prevent ios "ghost" events by clickable elements - - self.hasmoving = true; - - if (self.preventclick && !self.preventclick.click) { - self.preventclick.click = self.preventclick.tg.onclick || false; - self.preventclick.tg.onclick = self.onpreventclick; - } - - var ev = $.extend({ - "original": e - }, e); - e = ev; - - if (("changedTouches" in e)) { - e.clientX = e.changedTouches[0].clientX; - e.clientY = e.changedTouches[0].clientY; - } - - if (self.forcescreen) { - var le = e; - e = { - "original": (e.original) ? e.original : e - }; - e.clientX = le.screenX; - e.clientY = le.screenY; - } - - var ofy,ofx; - ofx = ofy = 0; - - if (moveneedoffset && !byiframe) { - var wp = self.win.position(); - ofx = -wp.left; - ofy = -wp.top; - } - - var fy = e.clientY + ofy; - var my = (fy - self.rail.drag.y); - var fx = e.clientX + ofx; - var mx = (fx - self.rail.drag.x); - - var ny = self.rail.drag.st - my; - - if (self.ishwscroll && self.opt.bouncescroll) { - if (ny < 0) { - ny = Math.round(ny / 2); - // fy = 0; - } else if (ny > self.page.maxh) { - ny = self.page.maxh + Math.round((ny - self.page.maxh) / 2); - // fy = 0; - } - } else { - if (ny < 0) { - ny = 0; - fy = 0; - } - if (ny > self.page.maxh) { - ny = self.page.maxh; - fy = 0; - } - } - - var nx; - if (self.railh && self.railh.scrollable) { - nx = (self.isrtlmode) ? mx - self.rail.drag.sl : self.rail.drag.sl - mx; - - if (self.ishwscroll && self.opt.bouncescroll) { - if (nx < 0) { - nx = Math.round(nx / 2); - // fx = 0; - } else if (nx > self.page.maxw) { - nx = self.page.maxw + Math.round((nx - self.page.maxw) / 2); - // fx = 0; - } - } else { - if (nx < 0) { - nx = 0; - fx = 0; - } - if (nx > self.page.maxw) { - nx = self.page.maxw; - fx = 0; - } - } - - } - - var grabbed = false; - if (self.rail.drag.dl) { - grabbed = true; - if (self.rail.drag.dl == "v") nx = self.rail.drag.sl; - else if (self.rail.drag.dl == "h") ny = self.rail.drag.st; - } else { - var ay = Math.abs(my); - var ax = Math.abs(mx); - var dz = self.opt.directionlockdeadzone; - if (self.rail.drag.ck == "v") { - if (ay > dz && (ax <= (ay * 0.3))) { - self.rail.drag = false; - return true; - } else if (ax > dz) { - self.rail.drag.dl = "f"; - $("body").scrollTop($("body").scrollTop()); // stop iOS native scrolling (when active javascript has blocked) - } - } else if (self.rail.drag.ck == "h") { - if (ax > dz && (ay <= (ax * 0.3))) { - self.rail.drag = false; - return true; - } else if (ay > dz) { - self.rail.drag.dl = "f"; - $("body").scrollLeft($("body").scrollLeft()); // stop iOS native scrolling (when active javascript has blocked) - } - } - } - - self.synched("touchmove", function() { - if (self.rail.drag && (self.rail.drag.pt == 2)) { - if (self.prepareTransition) self.prepareTransition(0); - if (self.rail.scrollable) self.setScrollTop(ny); - self.scrollmom.update(fx, fy); - if (self.railh && self.railh.scrollable) { - self.setScrollLeft(nx); - self.showCursor(ny, nx); - } else { - self.showCursor(ny); - } - if (cap.isie10) document.selection.clear(); - } - }); - - if (cap.ischrome && self.istouchcapable) grabbed = false; //chrome touch emulation doesn't like! - if (grabbed) return self.cancelEvent(e); - } - else if (self.rail.drag.pt == 1) { // drag on cursor - return self.onmousemove(e); - } - - }; - - } - - self.onmousedown = function(e, hronly) { - if (self.rail.drag && self.rail.drag.pt != 1) return; - if (self.railslocked) return self.cancelEvent(e); - self.cancelScroll(); - self.rail.drag = { - x: e.clientX, - y: e.clientY, - sx: self.scroll.x, - sy: self.scroll.y, - pt: 1, - hr: (!!hronly) - }; - var tg = self.getTarget(e); - if (!self.ispage && cap.hasmousecapture) tg.setCapture(); - if (self.isiframe && !cap.hasmousecapture) { - self.saved.csspointerevents = self.doc.css("pointer-events"); - self.css(self.doc, { - "pointer-events": "none" - }); - } - self.hasmoving = false; - return self.cancelEvent(e); - }; - - self.onmouseup = function(e) { - if (self.rail.drag) { - if (self.rail.drag.pt != 1) return true; - if (cap.hasmousecapture) document.releaseCapture(); - if (self.isiframe && !cap.hasmousecapture) self.doc.css("pointer-events", self.saved.csspointerevents); - self.rail.drag = false; - //if (!self.rail.active) self.hideCursor(); - if (self.hasmoving) self.triggerScrollEnd(); // TODO - check &&!self.scrollrunning - return self.cancelEvent(e); - } - }; - - self.onmousemove = function(e) { - if (self.rail.drag) { - if (self.rail.drag.pt != 1) return; - - if (cap.ischrome && e.which == 0) return self.onmouseup(e); - - self.cursorfreezed = true; - self.hasmoving = true; - - if (self.rail.drag.hr) { - self.scroll.x = self.rail.drag.sx + (e.clientX - self.rail.drag.x); - if (self.scroll.x < 0) self.scroll.x = 0; - var mw = self.scrollvaluemaxw; - if (self.scroll.x > mw) self.scroll.x = mw; - } else { - self.scroll.y = self.rail.drag.sy + (e.clientY - self.rail.drag.y); - if (self.scroll.y < 0) self.scroll.y = 0; - var my = self.scrollvaluemax; - if (self.scroll.y > my) self.scroll.y = my; - } - - self.synched('mousemove', function() { - if (self.rail.drag && (self.rail.drag.pt == 1)) { - self.showCursor(); - if (self.rail.drag.hr) { - if (self.hasreversehr) { - self.doScrollLeft(self.scrollvaluemaxw-Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed); - } else { - self.doScrollLeft(Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed); - } - } - else self.doScrollTop(Math.round(self.scroll.y * self.scrollratio.y), self.opt.cursordragspeed); - } - }); - - return self.cancelEvent(e); - } - /* - else { - self.checkarea = true; - } -*/ - }; - - if (cap.cantouch || self.opt.touchbehavior) { - - self.onpreventclick = function(e) { - if (self.preventclick) { - self.preventclick.tg.onclick = self.preventclick.click; - self.preventclick = false; - return self.cancelEvent(e); - } - } - - self.bind(self.win, "mousedown", self.ontouchstart); // control content dragging - - self.onclick = (cap.isios) ? false : function(e) { - if (self.lastmouseup) { - self.lastmouseup = false; - return self.cancelEvent(e); - } else { - return true; - } - }; - - if (self.opt.grabcursorenabled && cap.cursorgrabvalue) { - self.css((self.ispage) ? self.doc : self.win, { - 'cursor': cap.cursorgrabvalue - }); - self.css(self.rail, { - 'cursor': cap.cursorgrabvalue - }); - } - - } else { - - var checkSelectionScroll = function(e) { - if (!self.selectiondrag) return; - - if (e) { - var ww = self.win.outerHeight(); - var df = (e.pageY - self.selectiondrag.top); - if (df > 0 && df < ww) df = 0; - if (df >= ww) df -= ww; - self.selectiondrag.df = df; - } - if (self.selectiondrag.df == 0) return; - - var rt = -Math.floor(self.selectiondrag.df / 6) * 2; - self.doScrollBy(rt); - - self.debounced("doselectionscroll", function() { - checkSelectionScroll() - }, 50); - }; - - if ("getSelection" in document) { // A grade - Major browsers - self.hasTextSelected = function() { - return (document.getSelection().rangeCount > 0); - }; - } else if ("selection" in document) { //IE9- - self.hasTextSelected = function() { - return (document.selection.type != "None"); - }; - } else { - self.hasTextSelected = function() { // no support - return false; - }; - } - - self.onselectionstart = function(e) { -/* More testing - severe chrome issues - if (!self.haswrapper&&(e.which&&e.which==2)) { // fool browser to manage middle button scrolling - self.win.css({'overflow':'auto'}); - setTimeout(function(){ - self.win.css({'overflow':''}); - },10); - return true; - } -*/ - if (self.ispage) return; - self.selectiondrag = self.win.offset(); - }; - - self.onselectionend = function(e) { - self.selectiondrag = false; - }; - self.onselectiondrag = function(e) { - if (!self.selectiondrag) return; - if (self.hasTextSelected()) self.debounced("selectionscroll", function() { - checkSelectionScroll(e) - }, 250); - }; - - - } - - if (cap.hasw3ctouch) { //IE11+ - self.css(self.rail, { - 'touch-action': 'none' - }); - self.css(self.cursor, { - 'touch-action': 'none' - }); - self.bind(self.win, "pointerdown", self.ontouchstart); - self.bind(document, "pointerup", self.ontouchend); - self.bind(document, "pointermove", self.ontouchmove); - } else if (cap.hasmstouch) { //IE10 - self.css(self.rail, { - '-ms-touch-action': 'none' - }); - self.css(self.cursor, { - '-ms-touch-action': 'none' - }); - self.bind(self.win, "MSPointerDown", self.ontouchstart); - self.bind(document, "MSPointerUp", self.ontouchend); - self.bind(document, "MSPointerMove", self.ontouchmove); - self.bind(self.cursor, "MSGestureHold", function(e) { - e.preventDefault() - }); - self.bind(self.cursor, "contextmenu", function(e) { - e.preventDefault() - }); - } else if (this.istouchcapable) { //desktop with screen touch enabled - self.bind(self.win, "touchstart", self.ontouchstart); - self.bind(document, "touchend", self.ontouchend); - self.bind(document, "touchcancel", self.ontouchend); - self.bind(document, "touchmove", self.ontouchmove); - } - - - if (self.opt.cursordragontouch || (!cap.cantouch && !self.opt.touchbehavior)) { - - self.rail.css({ - "cursor": "default" - }); - self.railh && self.railh.css({ - "cursor": "default" - }); - - self.jqbind(self.rail, "mouseenter", function() { - if (!self.ispage && !self.win.is(":visible")) return false; - if (self.canshowonmouseevent) self.showCursor(); - self.rail.active = true; - }); - self.jqbind(self.rail, "mouseleave", function() { - self.rail.active = false; - if (!self.rail.drag) self.hideCursor(); - }); - - if (self.opt.sensitiverail) { - self.bind(self.rail, "click", function(e) { - self.doRailClick(e, false, false) - }); - self.bind(self.rail, "dblclick", function(e) { - self.doRailClick(e, true, false) - }); - self.bind(self.cursor, "click", function(e) { - self.cancelEvent(e) - }); - self.bind(self.cursor, "dblclick", function(e) { - self.cancelEvent(e) - }); - } - - if (self.railh) { - self.jqbind(self.railh, "mouseenter", function() { - if (!self.ispage && !self.win.is(":visible")) return false; - if (self.canshowonmouseevent) self.showCursor(); - self.rail.active = true; - }); - self.jqbind(self.railh, "mouseleave", function() { - self.rail.active = false; - if (!self.rail.drag) self.hideCursor(); - }); - - if (self.opt.sensitiverail) { - self.bind(self.railh, "click", function(e) { - self.doRailClick(e, false, true) - }); - self.bind(self.railh, "dblclick", function(e) { - self.doRailClick(e, true, true) - }); - self.bind(self.cursorh, "click", function(e) { - self.cancelEvent(e) - }); - self.bind(self.cursorh, "dblclick", function(e) { - self.cancelEvent(e) - }); - } - - } - - } - - if (!cap.cantouch && !self.opt.touchbehavior) { - - self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.onmouseup); - self.bind(document, "mousemove", self.onmousemove); - if (self.onclick) self.bind(document, "click", self.onclick); - - self.bind(self.cursor, "mousedown", self.onmousedown); - self.bind(self.cursor, "mouseup", self.onmouseup); - - if (self.railh) { - self.bind(self.cursorh, "mousedown", function(e) { - self.onmousedown(e, true) - }); - self.bind(self.cursorh, "mouseup", self.onmouseup); - } - - if (!self.ispage && self.opt.enablescrollonselection) { - self.bind(self.win[0], "mousedown", self.onselectionstart); - self.bind(document, "mouseup", self.onselectionend); - self.bind(self.cursor, "mouseup", self.onselectionend); - if (self.cursorh) self.bind(self.cursorh, "mouseup", self.onselectionend); - self.bind(document, "mousemove", self.onselectiondrag); - } - - if (self.zoom) { - self.jqbind(self.zoom, "mouseenter", function() { - if (self.canshowonmouseevent) self.showCursor(); - self.rail.active = true; - }); - self.jqbind(self.zoom, "mouseleave", function() { - self.rail.active = false; - if (!self.rail.drag) self.hideCursor(); - }); - } - - } else { - - self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.ontouchend); - self.bind(document, "mousemove", self.ontouchmove); - if (self.onclick) self.bind(document, "click", self.onclick); - - if (self.opt.cursordragontouch) { - self.bind(self.cursor, "mousedown", self.onmousedown); - self.bind(self.cursor, "mouseup", self.onmouseup); - //self.bind(self.cursor, "mousemove", self.onmousemove); - self.cursorh && self.bind(self.cursorh, "mousedown", function(e) { - self.onmousedown(e, true) - }); - //self.cursorh && self.bind(self.cursorh, "mousemove", self.onmousemove); - self.cursorh && self.bind(self.cursorh, "mouseup", self.onmouseup); - } - - } - - if (self.opt.enablemousewheel) { - if (!self.isiframe) self.bind((cap.isie && self.ispage) ? document : self.win /*self.docscroll*/ , "mousewheel", self.onmousewheel); - self.bind(self.rail, "mousewheel", self.onmousewheel); - if (self.railh) self.bind(self.railh, "mousewheel", self.onmousewheelhr); - } - - if (!self.ispage && !cap.cantouch && !(/HTML|^BODY/.test(self.win[0].nodeName))) { - if (!self.win.attr("tabindex")) self.win.attr({ - "tabindex": tabindexcounter++ - }); - - self.jqbind(self.win, "focus", function(e) { - domfocus = (self.getTarget(e)).id || true; - self.hasfocus = true; - if (self.canshowonmouseevent) self.noticeCursor(); - }); - self.jqbind(self.win, "blur", function(e) { - domfocus = false; - self.hasfocus = false; - }); - - self.jqbind(self.win, "mouseenter", function(e) { - mousefocus = (self.getTarget(e)).id || true; - self.hasmousefocus = true; - if (self.canshowonmouseevent) self.noticeCursor(); - }); - self.jqbind(self.win, "mouseleave", function() { - mousefocus = false; - self.hasmousefocus = false; - if (!self.rail.drag) self.hideCursor(); - }); - - } - - } // !ie9mobile - - //Thanks to http://www.quirksmode.org !! - self.onkeypress = function(e) { - if (self.railslocked && self.page.maxh == 0) return true; - - e = (e) ? e : window.e; - var tg = self.getTarget(e); - if (tg && /INPUT|TEXTAREA|SELECT|OPTION/.test(tg.nodeName)) { - var tp = tg.getAttribute('type') || tg.type || false; - if ((!tp) || !(/submit|button|cancel/i.tp)) return true; - } - - if ($(tg).attr('contenteditable')) return true; - - if (self.hasfocus || (self.hasmousefocus && !domfocus) || (self.ispage && !domfocus && !mousefocus)) { - var key = e.keyCode; - - if (self.railslocked && key != 27) return self.cancelEvent(e); - - var ctrl = e.ctrlKey || false; - var shift = e.shiftKey || false; - - var ret = false; - switch (key) { - case 38: - case 63233: //safari - self.doScrollBy(24 * 3); - ret = true; - break; - case 40: - case 63235: //safari - self.doScrollBy(-24 * 3); - ret = true; - break; - case 37: - case 63232: //safari - if (self.railh) { - (ctrl) ? self.doScrollLeft(0): self.doScrollLeftBy(24 * 3); - ret = true; - } - break; - case 39: - case 63234: //safari - if (self.railh) { - (ctrl) ? self.doScrollLeft(self.page.maxw): self.doScrollLeftBy(-24 * 3); - ret = true; - } - break; - case 33: - case 63276: // safari - self.doScrollBy(self.view.h); - ret = true; - break; - case 34: - case 63277: // safari - self.doScrollBy(-self.view.h); - ret = true; - break; - case 36: - case 63273: // safari - (self.railh && ctrl) ? self.doScrollPos(0, 0): self.doScrollTo(0); - ret = true; - break; - case 35: - case 63275: // safari - (self.railh && ctrl) ? self.doScrollPos(self.page.maxw, self.page.maxh): self.doScrollTo(self.page.maxh); - ret = true; - break; - case 32: - if (self.opt.spacebarenabled) { - (shift) ? self.doScrollBy(self.view.h): self.doScrollBy(-self.view.h); - ret = true; - } - break; - case 27: // ESC - if (self.zoomactive) { - self.doZoom(); - ret = true; - } - break; - } - if (ret) return self.cancelEvent(e); - } - }; - - if (self.opt.enablekeyboard) self.bind(document, (cap.isopera && !cap.isopera12) ? "keypress" : "keydown", self.onkeypress); - - self.bind(document, "keydown", function(e) { - var ctrl = e.ctrlKey || false; - if (ctrl) self.wheelprevented = true; - }); - self.bind(document, "keyup", function(e) { - var ctrl = e.ctrlKey || false; - if (!ctrl) self.wheelprevented = false; - }); - self.bind(window,"blur",function(e){ - self.wheelprevented = false; - }); - - self.bind(window, 'resize', self.lazyResize); - self.bind(window, 'orientationchange', self.lazyResize); - - self.bind(window, "load", self.lazyResize); - - if (cap.ischrome && !self.ispage && !self.haswrapper) { //chrome void scrollbar bug - it persists in version 26 - var tmp = self.win.attr("style"); - var ww = parseFloat(self.win.css("width")) + 1; - self.win.css('width', ww); - self.synched("chromefix", function() { - self.win.attr("style", tmp) - }); - } - - - // Trying a cross-browser implementation - good luck! - - self.onAttributeChange = function(e) { - self.lazyResize(self.isieold ? 250 : 30); - }; - - if (ClsMutationObserver !== false) { - self.observerbody = new ClsMutationObserver(function(mutations) { - mutations.forEach(function(mut){ - if (mut.type=="attributes") { - return ($("body").hasClass("modal-open")) ? self.hide() : self.show(); // Support for Bootstrap modal - } - }); - if (document.body.scrollHeight!=self.page.maxh) return self.lazyResize(30); - }); - self.observerbody.observe(document.body, { - childList: true, - subtree: true, - characterData: false, - attributes: true, - attributeFilter: ['class'] - }); - } - - if (!self.ispage && !self.haswrapper) { - // redesigned MutationObserver for Chrome18+/Firefox14+/iOS6+ with support for: remove div, add/remove content - if (ClsMutationObserver !== false) { - self.observer = new ClsMutationObserver(function(mutations) { - mutations.forEach(self.onAttributeChange); - }); - self.observer.observe(self.win[0], { - childList: true, - characterData: false, - attributes: true, - subtree: false - }); - self.observerremover = new ClsMutationObserver(function(mutations) { - mutations.forEach(function(mo) { - if (mo.removedNodes.length > 0) { - for (var dd in mo.removedNodes) { - if (!!self && (mo.removedNodes[dd] == self.win[0])) return self.remove(); - } - } - }); - }); - self.observerremover.observe(self.win[0].parentNode, { - childList: true, - characterData: false, - attributes: false, - subtree: false - }); - } else { - self.bind(self.win, (cap.isie && !cap.isie9) ? "propertychange" : "DOMAttrModified", self.onAttributeChange); - if (cap.isie9) self.win[0].attachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug - self.bind(self.win, "DOMNodeRemoved", function(e) { - if (e.target == self.win[0]) self.remove(); - }); - } - } - - // - - if (!self.ispage && self.opt.boxzoom) self.bind(window, "resize", self.resizeZoom); - if (self.istextarea) self.bind(self.win, "mouseup", self.lazyResize); - - // self.checkrtlmode = true; - self.lazyResize(30); - - } - - if (this.doc[0].nodeName == 'IFRAME') { - var oniframeload = function() { - self.iframexd = false; - var doc; - try { - doc = 'contentDocument' in this ? this.contentDocument : this.contentWindow.document; - var a = doc.domain; - } catch (e) { - self.iframexd = true; - doc = false - } - - if (self.iframexd) { - if ("console" in window) console.log('NiceScroll error: policy restriced iframe'); - return true; //cross-domain - I can't manage this - } - - self.forcescreen = true; - - if (self.isiframe) { - self.iframe = { - "doc": $(doc), - "html": self.doc.contents().find('html')[0], - "body": self.doc.contents().find('body')[0] - }; - self.getContentSize = function() { - return { - w: Math.max(self.iframe.html.scrollWidth, self.iframe.body.scrollWidth), - h: Math.max(self.iframe.html.scrollHeight, self.iframe.body.scrollHeight) - }; - }; - self.docscroll = $(self.iframe.body); //$(this.contentWindow); - } - - if (!cap.isios && self.opt.iframeautoresize && !self.isiframe) { - self.win.scrollTop(0); // reset position - self.doc.height(""); //reset height to fix browser bug - var hh = Math.max(doc.getElementsByTagName('html')[0].scrollHeight, doc.body.scrollHeight); - self.doc.height(hh); - } - self.lazyResize(30); - - if (cap.isie7) self.css($(self.iframe.html), { - 'overflow-y': 'hidden' - }); - self.css($(self.iframe.body), { - 'overflow-y': 'hidden' - }); - - if (cap.isios && self.haswrapper) { - self.css($(doc.body), { - '-webkit-transform': 'translate3d(0,0,0)' - }); // avoid iFrame content clipping - thanks to http://blog.derraab.com/2012/04/02/avoid-iframe-content-clipping-with-css-transform-on-ios/ - } - - if ('contentWindow' in this) { - self.bind(this.contentWindow, "scroll", self.onscroll); //IE8 & minor - } else { - self.bind(doc, "scroll", self.onscroll); - } - - if (self.opt.enablemousewheel) { - self.bind(doc, "mousewheel", self.onmousewheel); - } - - if (self.opt.enablekeyboard) self.bind(doc, (cap.isopera) ? "keypress" : "keydown", self.onkeypress); - - if (cap.cantouch || self.opt.touchbehavior) { - self.bind(doc, "mousedown", self.ontouchstart); - self.bind(doc, "mousemove", function(e) { - return self.ontouchmove(e, true) - }); - if (self.opt.grabcursorenabled && cap.cursorgrabvalue) self.css($(doc.body), { - 'cursor': cap.cursorgrabvalue - }); - } - - self.bind(doc, "mouseup", self.ontouchend); - - if (self.zoom) { - if (self.opt.dblclickzoom) self.bind(doc, 'dblclick', self.doZoom); - if (self.ongesturezoom) self.bind(doc, "gestureend", self.ongesturezoom); - } - }; - - if (this.doc[0].readyState && this.doc[0].readyState == "complete") { - setTimeout(function() { - oniframeload.call(self.doc[0], false) - }, 500); - } - self.bind(this.doc, "load", oniframeload); - - } - - }; - - this.showCursor = function(py, px) { - if (self.cursortimeout) { - clearTimeout(self.cursortimeout); - self.cursortimeout = 0; - } - if (!self.rail) return; - if (self.autohidedom) { - self.autohidedom.stop().css({ - opacity: self.opt.cursoropacitymax - }); - self.cursoractive = true; - } - - if (!self.rail.drag || self.rail.drag.pt != 1) { - if ((typeof py != "undefined") && (py !== false)) { - self.scroll.y = Math.round(py * 1 / self.scrollratio.y); - } - if (typeof px != "undefined") { - self.scroll.x = Math.round(px * 1 / self.scrollratio.x); - } - } - - self.cursor.css({ - height: self.cursorheight, - top: self.scroll.y - }); - if (self.cursorh) { - var lx = (self.hasreversehr) ? self.scrollvaluemaxw-self.scroll.x : self.scroll.x; - (!self.rail.align && self.rail.visibility) ? self.cursorh.css({ - width: self.cursorwidth, - left: lx + self.rail.width - }): self.cursorh.css({ - width: self.cursorwidth, - left: lx - }); - self.cursoractive = true; - } - - if (self.zoom) self.zoom.stop().css({ - opacity: self.opt.cursoropacitymax - }); - }; - - this.hideCursor = function(tm) { - if (self.cursortimeout) return; - if (!self.rail) return; - if (!self.autohidedom) return; - if (self.hasmousefocus && self.opt.autohidemode == "leave") return; - self.cursortimeout = setTimeout(function() { - if (!self.rail.active || !self.showonmouseevent) { - self.autohidedom.stop().animate({ - opacity: self.opt.cursoropacitymin - }); - if (self.zoom) self.zoom.stop().animate({ - opacity: self.opt.cursoropacitymin - }); - self.cursoractive = false; - } - self.cursortimeout = 0; - }, tm || self.opt.hidecursordelay); - }; - - this.noticeCursor = function(tm, py, px) { - self.showCursor(py, px); - if (!self.rail.active) self.hideCursor(tm); - }; - - this.getContentSize = - (self.ispage) ? - function() { - return { - w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth), - h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - } - } : (self.haswrapper) ? - function() { - return { - w: self.doc.outerWidth() + parseInt(self.win.css('paddingLeft')) + parseInt(self.win.css('paddingRight')), - h: self.doc.outerHeight() + parseInt(self.win.css('paddingTop')) + parseInt(self.win.css('paddingBottom')) - } - } : function() { - return { - w: self.docscroll[0].scrollWidth, - h: self.docscroll[0].scrollHeight - } - }; - - this.onResize = function(e, page) { - - if (!self || !self.win) return false; - - if (!self.haswrapper && !self.ispage) { - if (self.win.css('display') == 'none') { - if (self.visibility) self.hideRail().hideRailHr(); - return false; - } else { - if (!self.hidden && !self.visibility) self.showRail().showRailHr(); - } - } - - var premaxh = self.page.maxh; - var premaxw = self.page.maxw; - - var preview = { - h: self.view.h, - w: self.view.w - }; - - self.view = { - w: (self.ispage) ? self.win.width() : parseInt(self.win[0].clientWidth), - h: (self.ispage) ? self.win.height() : parseInt(self.win[0].clientHeight) - }; - - self.page = (page) ? page : self.getContentSize(); - - self.page.maxh = Math.max(0, self.page.h - self.view.h); - self.page.maxw = Math.max(0, self.page.w - self.view.w); - - if ((self.page.maxh == premaxh) && (self.page.maxw == premaxw) && (self.view.w == preview.w) && (self.view.h == preview.h)) { - // test position - if (!self.ispage) { - var pos = self.win.offset(); - if (self.lastposition) { - var lst = self.lastposition; - if ((lst.top == pos.top) && (lst.left == pos.left)) return self; //nothing to do - } - self.lastposition = pos; - } else { - return self; //nothing to do - } - } - - if (self.page.maxh == 0) { - self.hideRail(); - self.scrollvaluemax = 0; - self.scroll.y = 0; - self.scrollratio.y = 0; - self.cursorheight = 0; - self.setScrollTop(0); - self.rail.scrollable = false; - } else { - self.page.maxh -= (self.opt.railpadding.top + self.opt.railpadding.bottom); //** - self.rail.scrollable = true; - } - - if (self.page.maxw == 0) { - self.hideRailHr(); - self.scrollvaluemaxw = 0; - self.scroll.x = 0; - self.scrollratio.x = 0; - self.cursorwidth = 0; - self.setScrollLeft(0); - self.railh.scrollable = false; - } else { - self.page.maxw -= (self.opt.railpadding.left + self.opt.railpadding.right); //** - self.railh.scrollable = true; - } - - self.railslocked = (self.locked) || ((self.page.maxh == 0) && (self.page.maxw == 0)); - if (self.railslocked) { - if (!self.ispage) self.updateScrollBar(self.view); - return false; - } - - if (!self.hidden && !self.visibility) { - self.showRail().showRailHr(); - } - else if (!self.hidden && !self.railh.visibility) self.showRailHr(); - - if (self.istextarea && self.win.css('resize') && self.win.css('resize') != 'none') self.view.h -= 20; - - self.cursorheight = Math.min(self.view.h, Math.round(self.view.h * (self.view.h / self.page.h))); - self.cursorheight = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorheight); - - self.cursorwidth = Math.min(self.view.w, Math.round(self.view.w * (self.view.w / self.page.w))); - self.cursorwidth = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorwidth); - - self.scrollvaluemax = self.view.h - self.cursorheight - self.cursor.hborder - (self.opt.railpadding.top + self.opt.railpadding.bottom); //** - - if (self.railh) { - self.railh.width = (self.page.maxh > 0) ? (self.view.w - self.rail.width) : self.view.w; - self.scrollvaluemaxw = self.railh.width - self.cursorwidth - self.cursorh.wborder - (self.opt.railpadding.left + self.opt.railpadding.right); //** - } - - /* - if (self.checkrtlmode&&self.railh) { - self.checkrtlmode = false; - if (self.opt.rtlmode&&self.scroll.x==0) self.setScrollLeft(self.page.maxw); - } -*/ - - if (!self.ispage) self.updateScrollBar(self.view); - - self.scrollratio = { - x: (self.page.maxw / self.scrollvaluemaxw), - y: (self.page.maxh / self.scrollvaluemax) - }; - - var sy = self.getScrollTop(); - if (sy > self.page.maxh) { - self.doScrollTop(self.page.maxh); - } else { - self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y)); - self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x)); - if (self.cursoractive) self.noticeCursor(); - } - - if (self.scroll.y && (self.getScrollTop() == 0)) self.doScrollTo(Math.floor(self.scroll.y * self.scrollratio.y)); - - return self; - }; - - this.resize = self.onResize; - - this.lazyResize = function(tm) { // event debounce - tm = (isNaN(tm)) ? 30 : tm; - self.debounced('resize', self.resize, tm); - return self; - }; - - // modified by MDN https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/wheel - function _modernWheelEvent(dom, name, fn, bubble) { - self._bind(dom, name, function(e) { - var e = (e) ? e : window.event; - var event = { - original: e, - target: e.target || e.srcElement, - type: "wheel", - deltaMode: e.type == "MozMousePixelScroll" ? 0 : 1, - deltaX: 0, - deltaZ: 0, - preventDefault: function() { - e.preventDefault ? e.preventDefault() : e.returnValue = false; - return false; - }, - stopImmediatePropagation: function() { - (e.stopImmediatePropagation) ? e.stopImmediatePropagation(): e.cancelBubble = true; - } - }; - - if (name == "mousewheel") { - event.deltaY = -1 / 40 * e.wheelDelta; - e.wheelDeltaX && (event.deltaX = -1 / 40 * e.wheelDeltaX); - } else { - event.deltaY = e.detail; - } - - return fn.call(dom, event); - }, bubble); - }; - - - - this.jqbind = function(dom, name, fn) { // use jquery bind for non-native events (mouseenter/mouseleave) - self.events.push({ - e: dom, - n: name, - f: fn, - q: true - }); - $(dom).bind(name, fn); - }; - - this.bind = function(dom, name, fn, bubble) { // touch-oriented & fixing jquery bind - var el = ("jquery" in dom) ? dom[0] : dom; - - if (name == 'mousewheel') { - if (window.addEventListener||'onwheel' in document) { // modern brosers & IE9 detection fix - self._bind(el, "wheel", fn, bubble || false); - } else { - var wname = (typeof document.onmousewheel != "undefined") ? "mousewheel" : "DOMMouseScroll"; // older IE/Firefox - _modernWheelEvent(el, wname, fn, bubble || false); - if (wname == "DOMMouseScroll") _modernWheelEvent(el, "MozMousePixelScroll", fn, bubble || false); // Firefox legacy - } - } else if (el.addEventListener) { - if (cap.cantouch && /mouseup|mousedown|mousemove/.test(name)) { // touch device support - var tt = (name == 'mousedown') ? 'touchstart' : (name == 'mouseup') ? 'touchend' : 'touchmove'; - self._bind(el, tt, function(e) { - if (e.touches) { - if (e.touches.length < 2) { - var ev = (e.touches.length) ? e.touches[0] : e; - ev.original = e; - fn.call(this, ev); - } - } else if (e.changedTouches) { - var ev = e.changedTouches[0]; - ev.original = e; - fn.call(this, ev); - } //blackberry - }, bubble || false); - } - self._bind(el, name, fn, bubble || false); - if (cap.cantouch && name == "mouseup") self._bind(el, "touchcancel", fn, bubble || false); - } else { - self._bind(el, name, function(e) { - e = e || window.event || false; - if (e) { - if (e.srcElement) e.target = e.srcElement; - } - if (!("pageY" in e)) { - e.pageX = e.clientX + document.documentElement.scrollLeft; - e.pageY = e.clientY + document.documentElement.scrollTop; - } - return ((fn.call(el, e) === false) || bubble === false) ? self.cancelEvent(e) : true; - }); - } - }; - - if (cap.haseventlistener) { // W3C standard model - this._bind = function(el, name, fn, bubble) { // primitive bind - self.events.push({ - e: el, - n: name, - f: fn, - b: bubble, - q: false - }); - el.addEventListener(name, fn, bubble || false); - }; - this.cancelEvent = function(e) { - if (!e) return false; - var e = (e.original) ? e.original : e; - e.preventDefault(); - e.stopPropagation(); - if (e.preventManipulation) e.preventManipulation(); //IE10 - return false; - }; - this.stopPropagation = function(e) { - if (!e) return false; - var e = (e.original) ? e.original : e; - e.stopPropagation(); - return false; - }; - this._unbind = function(el, name, fn, bub) { // primitive unbind - el.removeEventListener(name, fn, bub); - }; - } else { // old IE model - this._bind = function(el, name, fn, bubble) { // primitive bind - self.events.push({ - e: el, - n: name, - f: fn, - b: bubble, - q: false - }); - if (el.attachEvent) { - el.attachEvent("on" + name, fn); - } else { - el["on" + name] = fn; - } - }; - // Thanks to http://www.switchonthecode.com !! - this.cancelEvent = function(e) { - var e = window.event || false; - if (!e) return false; - e.cancelBubble = true; - e.cancel = true; - e.returnValue = false; - return false; - }; - this.stopPropagation = function(e) { - var e = window.event || false; - if (!e) return false; - e.cancelBubble = true; - return false; - }; - this._unbind = function(el, name, fn, bub) { // primitive unbind IE old - if (el.detachEvent) { - el.detachEvent('on' + name, fn); - } else { - el['on' + name] = false; - } - }; - } - - this.unbindAll = function() { - for (var a = 0; a < self.events.length; a++) { - var r = self.events[a]; - (r.q) ? r.e.unbind(r.n, r.f): self._unbind(r.e, r.n, r.f, r.b); - } - }; - - this.showRail = function() { - if ((self.page.maxh != 0) && (self.ispage || self.win.css('display') != 'none')) { - self.visibility = true; - self.rail.visibility = true; - self.rail.css('display', 'block'); - } - return self; - }; - - this.showRailHr = function() { - if (!self.railh) return self; - if ((self.page.maxw != 0) && (self.ispage || self.win.css('display') != 'none')) { - self.railh.visibility = true; - self.railh.css('display', 'block'); - } - return self; - }; - - this.hideRail = function() { - self.visibility = false; - self.rail.visibility = false; - self.rail.css('display', 'none'); - return self; - }; - - this.hideRailHr = function() { - if (!self.railh) return self; - self.railh.visibility = false; - self.railh.css('display', 'none'); - return self; - }; - - this.show = function() { - self.hidden = false; - self.railslocked = false; - return self.showRail().showRailHr(); - }; - - this.hide = function() { - self.hidden = true; - self.railslocked = true; - return self.hideRail().hideRailHr(); - }; - - this.toggle = function() { - return (self.hidden) ? self.show() : self.hide(); - }; - - this.remove = function() { - self.stop(); - if (self.cursortimeout) clearTimeout(self.cursortimeout); - self.doZoomOut(); - self.unbindAll(); - - if (cap.isie9) self.win[0].detachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug - - if (self.observer !== false) self.observer.disconnect(); - if (self.observerremover !== false) self.observerremover.disconnect(); - if (self.observerbody !== false) self.observerbody.disconnect(); - - self.events = null; - - if (self.cursor) { - self.cursor.remove(); - } - if (self.cursorh) { - self.cursorh.remove(); - } - if (self.rail) { - self.rail.remove(); - } - if (self.railh) { - self.railh.remove(); - } - if (self.zoom) { - self.zoom.remove(); - } - for (var a = 0; a < self.saved.css.length; a++) { - var d = self.saved.css[a]; - d[0].css(d[1], (typeof d[2] == "undefined") ? '' : d[2]); - } - self.saved = false; - self.me.data('__nicescroll', ''); //erase all traces - - // memory leak fixed by GianlucaGuarini - thanks a lot! - // remove the current nicescroll from the $.nicescroll array & normalize array - var lst = $.nicescroll; - lst.each(function(i) { - if (!this) return; - if (this.id === self.id) { - delete lst[i]; - for (var b = ++i; b < lst.length; b++, i++) lst[i] = lst[b]; - lst.length--; - if (lst.length) delete lst[lst.length]; - } - }); - - for (var i in self) { - self[i] = null; - delete self[i]; - } - - self = null; - - }; - - this.scrollstart = function(fn) { - this.onscrollstart = fn; - return self; - }; - this.scrollend = function(fn) { - this.onscrollend = fn; - return self; - }; - this.scrollcancel = function(fn) { - this.onscrollcancel = fn; - return self; - }; - - this.zoomin = function(fn) { - this.onzoomin = fn; - return self; - }; - this.zoomout = function(fn) { - this.onzoomout = fn; - return self; - }; - - this.isScrollable = function(e) { - var dom = (e.target) ? e.target : e; - if (dom.nodeName == 'OPTION') return true; - while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) { - var dd = $(dom); - var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || ''; - if (/scroll|auto/.test(ov)) return (dom.clientHeight != dom.scrollHeight); - dom = (dom.parentNode) ? dom.parentNode : false; - } - return false; - }; - - this.getViewport = function(me) { - var dom = (me && me.parentNode) ? me.parentNode : false; - while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) { - var dd = $(dom); - if (/fixed|absolute/.test(dd.css("position"))) return dd; - var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || ''; - if ((/scroll|auto/.test(ov)) && (dom.clientHeight != dom.scrollHeight)) return dd; - if (dd.getNiceScroll().length > 0) return dd; - dom = (dom.parentNode) ? dom.parentNode : false; - } - return false; //(dom) ? $(dom) : false; - }; - - this.triggerScrollEnd = function() { - if (!self.onscrollend) return; - - var px = self.getScrollLeft(); - var py = self.getScrollTop(); - - var info = { - "type": "scrollend", - "current": { - "x": px, - "y": py - }, - "end": { - "x": px, - "y": py - } - }; - self.onscrollend.call(self, info); - } - - function execScrollWheel(e, hr, chkscroll) { - var px, py; - - if (e.deltaMode == 0) { // PIXEL - px = -Math.floor(e.deltaX * (self.opt.mousescrollstep / (18 * 3))); - py = -Math.floor(e.deltaY * (self.opt.mousescrollstep / (18 * 3))); - } else if (e.deltaMode == 1) { // LINE - px = -Math.floor(e.deltaX * self.opt.mousescrollstep); - py = -Math.floor(e.deltaY * self.opt.mousescrollstep); - } - - if (hr && self.opt.oneaxismousemode && (px == 0) && py) { // classic vertical-only mousewheel + browser with x/y support - px = py; - py = 0; - - if (chkscroll) { - var hrend = (px < 0) ? (self.getScrollLeft() >= self.page.maxw) : (self.getScrollLeft() <= 0); - if (hrend) { // preserve vertical scrolling - py = px; - px = 0; - } - } - - } - - if (px) { - if (self.scrollmom) { - self.scrollmom.stop() - } - self.lastdeltax += px; - self.debounced("mousewheelx", function() { - var dt = self.lastdeltax; - self.lastdeltax = 0; - if (!self.rail.drag) { - self.doScrollLeftBy(dt) - } - }, 15); - } - if (py) { - if (self.opt.nativeparentscrolling && chkscroll && !self.ispage && !self.zoomactive) { - if (py < 0) { - if (self.getScrollTop() >= self.page.maxh) return true; - } else { - if (self.getScrollTop() <= 0) return true; - } - } - if (self.scrollmom) { - self.scrollmom.stop() - } - self.lastdeltay += py; - self.debounced("mousewheely", function() { - var dt = self.lastdeltay; - self.lastdeltay = 0; - if (!self.rail.drag) { - self.doScrollBy(dt) - } - }, 15); - } - - e.stopImmediatePropagation(); - return e.preventDefault(); - }; - - this.onmousewheel = function(e) { - if (self.wheelprevented) return; - if (self.railslocked) { - self.debounced("checkunlock", self.resize, 250); - return true; - } - if (self.rail.drag) return self.cancelEvent(e); - - if (self.opt.oneaxismousemode == "auto" && e.deltaX != 0) self.opt.oneaxismousemode = false; // check two-axis mouse support (not very elegant) - - if (self.opt.oneaxismousemode && e.deltaX == 0) { - if (!self.rail.scrollable) { - if (self.railh && self.railh.scrollable) { - return self.onmousewheelhr(e); - } else { - return true; - } - } - } - - var nw = +(new Date()); - var chk = false; - if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) { - self.nativescrollingarea = self.isScrollable(e); - chk = true; - } - self.checkarea = nw; - if (self.nativescrollingarea) return true; // this isn't my business - var ret = execScrollWheel(e, false, chk); - if (ret) self.checkarea = 0; - return ret; - }; - - this.onmousewheelhr = function(e) { - if (self.wheelprevented) return; - if (self.railslocked || !self.railh.scrollable) return true; - if (self.rail.drag) return self.cancelEvent(e); - - var nw = +(new Date()); - var chk = false; - if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) { - self.nativescrollingarea = self.isScrollable(e); - chk = true; - } - self.checkarea = nw; - if (self.nativescrollingarea) return true; // this isn't my business - if (self.railslocked) return self.cancelEvent(e); - - return execScrollWheel(e, true, chk); - }; - - this.stop = function() { - self.cancelScroll(); - if (self.scrollmon) self.scrollmon.stop(); - self.cursorfreezed = false; - self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y)); - self.noticeCursor(); - return self; - }; - - this.getTransitionSpeed = function(dif) { - var sp = Math.round(self.opt.scrollspeed * 10); - var ex = Math.min(sp, Math.round((dif / 20) * self.opt.scrollspeed)); - return (ex > 20) ? ex : 0; - }; - - if (!self.opt.smoothscroll) { - this.doScrollLeft = function(x, spd) { //direct - var y = self.getScrollTop(); - self.doScrollPos(x, y, spd); - }; - this.doScrollTop = function(y, spd) { //direct - var x = self.getScrollLeft(); - self.doScrollPos(x, y, spd); - }; - this.doScrollPos = function(x, y, spd) { //direct - var nx = (x > self.page.maxw) ? self.page.maxw : x; - if (nx < 0) nx = 0; - var ny = (y > self.page.maxh) ? self.page.maxh : y; - if (ny < 0) ny = 0; - self.synched('scroll', function() { - self.setScrollTop(ny); - self.setScrollLeft(nx); - }); - }; - this.cancelScroll = function() {}; // direct - } else if (self.ishwscroll && cap.hastransition && self.opt.usetransition && !!self.opt.smoothscroll) { - this.prepareTransition = function(dif, istime) { - var ex = (istime) ? ((dif > 20) ? dif : 0) : self.getTransitionSpeed(dif); - var trans = (ex) ? cap.prefixstyle + 'transform ' + ex + 'ms ease-out' : ''; - if (!self.lasttransitionstyle || self.lasttransitionstyle != trans) { - self.lasttransitionstyle = trans; - self.doc.css(cap.transitionstyle, trans); - } - return ex; - }; - - this.doScrollLeft = function(x, spd) { //trans - var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop(); - self.doScrollPos(x, y, spd); - }; - - this.doScrollTop = function(y, spd) { //trans - var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft(); - self.doScrollPos(x, y, spd); - }; - - this.doScrollPos = function(x, y, spd) { //trans - - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - - if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection - - if (self.opt.bouncescroll == false) { - if (y < 0) y = 0; - else if (y > self.page.maxh) y = self.page.maxh; - if (x < 0) x = 0; - else if (x > self.page.maxw) x = self.page.maxw; - } - - if (self.scrollrunning && x == self.newscrollx && y == self.newscrolly) return false; - - self.newscrolly = y; - self.newscrollx = x; - - self.newscrollspeed = spd || false; - - if (self.timer) return false; - - self.timer = setTimeout(function() { - - var top = self.getScrollTop(); - var lft = self.getScrollLeft(); - - var dst = {}; - dst.x = x - lft; - dst.y = y - top; - dst.px = lft; - dst.py = top; - - var dd = Math.round(Math.sqrt(Math.pow(dst.x, 2) + Math.pow(dst.y, 2))); - var ms = (self.newscrollspeed && self.newscrollspeed > 1) ? self.newscrollspeed : self.getTransitionSpeed(dd); - if (self.newscrollspeed && self.newscrollspeed <= 1) ms *= self.newscrollspeed; - - self.prepareTransition(ms, true); - - if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm); - - if (ms > 0) { - - if (!self.scrollrunning && self.onscrollstart) { - var info = { - "type": "scrollstart", - "current": { - "x": lft, - "y": top - }, - "request": { - "x": x, - "y": y - }, - "end": { - "x": self.newscrollx, - "y": self.newscrolly - }, - "speed": ms - }; - self.onscrollstart.call(self, info); - } - - if (cap.transitionend) { - if (!self.scrollendtrapped) { - self.scrollendtrapped = true; - self.bind(self.doc, cap.transitionend, self.onScrollTransitionEnd, false); //I have got to do something usefull!! - } - } else { - if (self.scrollendtrapped) clearTimeout(self.scrollendtrapped); - self.scrollendtrapped = setTimeout(self.onScrollTransitionEnd, ms); // simulate transitionend event - } - - var py = top; - var px = lft; - self.timerscroll = { - bz: new BezierClass(py, self.newscrolly, ms, 0, 0, 0.58, 1), - bh: new BezierClass(px, self.newscrollx, ms, 0, 0, 0.58, 1) - }; - if (!self.cursorfreezed) self.timerscroll.tm = setInterval(function() { - self.showCursor(self.getScrollTop(), self.getScrollLeft()) - }, 60); - - } - - self.synched("doScroll-set", function() { - self.timer = 0; - if (self.scrollendtrapped) self.scrollrunning = true; - self.setScrollTop(self.newscrolly); - self.setScrollLeft(self.newscrollx); - if (!self.scrollendtrapped) self.onScrollTransitionEnd(); - }); - - - }, 50); - - }; - - this.cancelScroll = function() { - if (!self.scrollendtrapped) return true; - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - self.scrollrunning = false; - if (!cap.transitionend) clearTimeout(cap.transitionend); - self.scrollendtrapped = false; - self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd); - self.prepareTransition(0); - self.setScrollTop(py); // fire event onscroll - if (self.railh) self.setScrollLeft(px); - if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm); - self.timerscroll = false; - - self.cursorfreezed = false; - - self.showCursor(py, px); - return self; - }; - this.onScrollTransitionEnd = function() { - if (self.scrollendtrapped) self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd); - self.scrollendtrapped = false; - self.prepareTransition(0); - if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm); - self.timerscroll = false; - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - self.setScrollTop(py); // fire event onscroll - if (self.railh) self.setScrollLeft(px); // fire event onscroll left - - self.noticeCursor(false, py, px); - - self.cursorfreezed = false; - - if (py < 0) py = 0 - else if (py > self.page.maxh) py = self.page.maxh; - if (px < 0) px = 0 - else if (px > self.page.maxw) px = self.page.maxw; - if ((py != self.newscrolly) || (px != self.newscrollx)) return self.doScrollPos(px, py, self.opt.snapbackspeed); - - if (self.onscrollend && self.scrollrunning) { - self.triggerScrollEnd(); - } - self.scrollrunning = false; - - }; - - } else { - - this.doScrollLeft = function(x, spd) { //no-trans - var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop(); - self.doScrollPos(x, y, spd); - }; - - this.doScrollTop = function(y, spd) { //no-trans - var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft(); - self.doScrollPos(x, y, spd); - }; - - this.doScrollPos = function(x, y, spd) { //no-trans - var y = ((typeof y == "undefined") || (y === false)) ? self.getScrollTop(true) : y; - - if ((self.timer) && (self.newscrolly == y) && (self.newscrollx == x)) return true; - - if (self.timer) clearAnimationFrame(self.timer); - self.timer = 0; - - var py = self.getScrollTop(); - var px = self.getScrollLeft(); - - if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection - - self.newscrolly = y; - self.newscrollx = x; - - if (!self.bouncescroll || !self.rail.visibility) { - if (self.newscrolly < 0) { - self.newscrolly = 0; - } else if (self.newscrolly > self.page.maxh) { - self.newscrolly = self.page.maxh; - } - } - if (!self.bouncescroll || !self.railh.visibility) { - if (self.newscrollx < 0) { - self.newscrollx = 0; - } else if (self.newscrollx > self.page.maxw) { - self.newscrollx = self.page.maxw; - } - } - - self.dst = {}; - self.dst.x = x - px; - self.dst.y = y - py; - self.dst.px = px; - self.dst.py = py; - - var dst = Math.round(Math.sqrt(Math.pow(self.dst.x, 2) + Math.pow(self.dst.y, 2))); - - self.dst.ax = self.dst.x / dst; - self.dst.ay = self.dst.y / dst; - - var pa = 0; - var pe = dst; - - if (self.dst.x == 0) { - pa = py; - pe = y; - self.dst.ay = 1; - self.dst.py = 0; - } else if (self.dst.y == 0) { - pa = px; - pe = x; - self.dst.ax = 1; - self.dst.px = 0; - } - - var ms = self.getTransitionSpeed(dst); - if (spd && spd <= 1) ms *= spd; - if (ms > 0) { - self.bzscroll = (self.bzscroll) ? self.bzscroll.update(pe, ms) : new BezierClass(pa, pe, ms, 0, 1, 0, 1); - } else { - self.bzscroll = false; - } - - if (self.timer) return; - - if ((py == self.page.maxh && y >= self.page.maxh) || (px == self.page.maxw && x >= self.page.maxw)) self.checkContentSize(); - - var sync = 1; - - function scrolling() { - if (self.cancelAnimationFrame) return true; - - self.scrollrunning = true; - - sync = 1 - sync; - if (sync) return (self.timer = setAnimationFrame(scrolling) || 1); - - var done = 0; - var sx, sy; - - var sc = sy = self.getScrollTop(); - if (self.dst.ay) { - sc = (self.bzscroll) ? self.dst.py + (self.bzscroll.getNow() * self.dst.ay) : self.newscrolly; - var dr = sc - sy; - if ((dr < 0 && sc < self.newscrolly) || (dr > 0 && sc > self.newscrolly)) sc = self.newscrolly; - self.setScrollTop(sc); - if (sc == self.newscrolly) done = 1; - } else { - done = 1; - } - - var scx = sx = self.getScrollLeft(); - if (self.dst.ax) { - scx = (self.bzscroll) ? self.dst.px + (self.bzscroll.getNow() * self.dst.ax) : self.newscrollx; - var dr = scx - sx; - if ((dr < 0 && scx < self.newscrollx) || (dr > 0 && scx > self.newscrollx)) scx = self.newscrollx; - self.setScrollLeft(scx); - if (scx == self.newscrollx) done += 1; - } else { - done += 1; - } - - if (done == 2) { - self.timer = 0; - self.cursorfreezed = false; - self.bzscroll = false; - self.scrollrunning = false; - if (sc < 0) sc = 0; - else if (sc > self.page.maxh) sc = self.page.maxh; - if (scx < 0) scx = 0; - else if (scx > self.page.maxw) scx = self.page.maxw; - if ((scx != self.newscrollx) || (sc != self.newscrolly)) self.doScrollPos(scx, sc); - else { - if (self.onscrollend) { - self.triggerScrollEnd(); - } - } - } else { - self.timer = setAnimationFrame(scrolling) || 1; - } - }; - self.cancelAnimationFrame = false; - self.timer = 1; - - if (self.onscrollstart && !self.scrollrunning) { - var info = { - "type": "scrollstart", - "current": { - "x": px, - "y": py - }, - "request": { - "x": x, - "y": y - }, - "end": { - "x": self.newscrollx, - "y": self.newscrolly - }, - "speed": ms - }; - self.onscrollstart.call(self, info); - } - - scrolling(); - - if ((py == self.page.maxh && y >= py) || (px == self.page.maxw && x >= px)) self.checkContentSize(); - - self.noticeCursor(); - }; - - this.cancelScroll = function() { - if (self.timer) clearAnimationFrame(self.timer); - self.timer = 0; - self.bzscroll = false; - self.scrollrunning = false; - return self; - }; - - } - - this.doScrollBy = function(stp, relative) { - var ny = 0; - if (relative) { - ny = Math.floor((self.scroll.y - stp) * self.scrollratio.y) - } else { - var sy = (self.timer) ? self.newscrolly : self.getScrollTop(true); - ny = sy - stp; - } - if (self.bouncescroll) { - var haf = Math.round(self.view.h / 2); - if (ny < -haf) ny = -haf - else if (ny > (self.page.maxh + haf)) ny = (self.page.maxh + haf); - } - self.cursorfreezed = false; - - var py = self.getScrollTop(true); - if (ny < 0 && py <= 0) return self.noticeCursor(); - else if (ny > self.page.maxh && py >= self.page.maxh) { - self.checkContentSize(); - return self.noticeCursor(); - } - - self.doScrollTop(ny); - }; - - this.doScrollLeftBy = function(stp, relative) { - var nx = 0; - if (relative) { - nx = Math.floor((self.scroll.x - stp) * self.scrollratio.x) - } else { - var sx = (self.timer) ? self.newscrollx : self.getScrollLeft(true); - nx = sx - stp; - } - if (self.bouncescroll) { - var haf = Math.round(self.view.w / 2); - if (nx < -haf) nx = -haf; - else if (nx > (self.page.maxw + haf)) nx = (self.page.maxw + haf); - } - self.cursorfreezed = false; - - var px = self.getScrollLeft(true); - if (nx < 0 && px <= 0) return self.noticeCursor(); - else if (nx > self.page.maxw && px >= self.page.maxw) return self.noticeCursor(); - - self.doScrollLeft(nx); - }; - - this.doScrollTo = function(pos, relative) { - var ny = (relative) ? Math.round(pos * self.scrollratio.y) : pos; - if (ny < 0) ny = 0; - else if (ny > self.page.maxh) ny = self.page.maxh; - self.cursorfreezed = false; - self.doScrollTop(pos); - }; - - this.checkContentSize = function() { - var pg = self.getContentSize(); - if ((pg.h != self.page.h) || (pg.w != self.page.w)) self.resize(false, pg); - }; - - self.onscroll = function(e) { - if (self.rail.drag) return; - if (!self.cursorfreezed) { - self.synched('scroll', function() { - self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y)); - if (self.railh) self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x)); - self.noticeCursor(); - }); - } - }; - self.bind(self.docscroll, "scroll", self.onscroll); - - this.doZoomIn = function(e) { - if (self.zoomactive) return; - self.zoomactive = true; - - self.zoomrestore = { - style: {} - }; - var lst = ['position', 'top', 'left', 'zIndex', 'backgroundColor', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight']; - var win = self.win[0].style; - for (var a in lst) { - var pp = lst[a]; - self.zoomrestore.style[pp] = (typeof win[pp] != "undefined") ? win[pp] : ''; - } - - self.zoomrestore.style.width = self.win.css('width'); - self.zoomrestore.style.height = self.win.css('height'); - - self.zoomrestore.padding = { - w: self.win.outerWidth() - self.win.width(), - h: self.win.outerHeight() - self.win.height() - }; - - if (cap.isios4) { - self.zoomrestore.scrollTop = $(window).scrollTop(); - $(window).scrollTop(0); - } - - self.win.css({ - "position": (cap.isios4) ? "absolute" : "fixed", - "top": 0, - "left": 0, - "z-index": globalmaxzindex + 100, - "margin": "0px" - }); - var bkg = self.win.css("backgroundColor"); - if (bkg == "" || /transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(bkg)) self.win.css("backgroundColor", "#fff"); - self.rail.css({ - "z-index": globalmaxzindex + 101 - }); - self.zoom.css({ - "z-index": globalmaxzindex + 102 - }); - self.zoom.css('backgroundPosition', '0px -18px'); - self.resizeZoom(); - - if (self.onzoomin) self.onzoomin.call(self); - - return self.cancelEvent(e); - }; - - this.doZoomOut = function(e) { - if (!self.zoomactive) return; - self.zoomactive = false; - - self.win.css("margin", ""); - self.win.css(self.zoomrestore.style); - - if (cap.isios4) { - $(window).scrollTop(self.zoomrestore.scrollTop); - } - - self.rail.css({ - "z-index": self.zindex - }); - self.zoom.css({ - "z-index": self.zindex - }); - self.zoomrestore = false; - self.zoom.css('backgroundPosition', '0px 0px'); - self.onResize(); - - if (self.onzoomout) self.onzoomout.call(self); - - return self.cancelEvent(e); - }; - - this.doZoom = function(e) { - return (self.zoomactive) ? self.doZoomOut(e) : self.doZoomIn(e); - }; - - this.resizeZoom = function() { - if (!self.zoomactive) return; - - var py = self.getScrollTop(); //preserve scrolling position - self.win.css({ - width: $(window).width() - self.zoomrestore.padding.w + "px", - height: $(window).height() - self.zoomrestore.padding.h + "px" - }); - self.onResize(); - - self.setScrollTop(Math.min(self.page.maxh, py)); - }; - - this.init(); - - $.nicescroll.push(this); - - }; - - // Inspired by the work of Kin Blas - // http://webpro.host.adobe.com/people/jblas/momentum/includes/jquery.momentum.0.7.js - - - var ScrollMomentumClass2D = function(nc) { - var self = this; - this.nc = nc; - - this.lastx = 0; - this.lasty = 0; - this.speedx = 0; - this.speedy = 0; - this.lasttime = 0; - this.steptime = 0; - this.snapx = false; - this.snapy = false; - this.demulx = 0; - this.demuly = 0; - - this.lastscrollx = -1; - this.lastscrolly = -1; - - this.chkx = 0; - this.chky = 0; - - this.timer = 0; - - this.time = function() { - return +new Date(); //beautifull hack - }; - - this.reset = function(px, py) { - self.stop(); - var now = self.time(); - self.steptime = 0; - self.lasttime = now; - self.speedx = 0; - self.speedy = 0; - self.lastx = px; - self.lasty = py; - self.lastscrollx = -1; - self.lastscrolly = -1; - }; - - this.update = function(px, py) { - var now = self.time(); - self.steptime = now - self.lasttime; - self.lasttime = now; - var dy = py - self.lasty; - var dx = px - self.lastx; - var sy = self.nc.getScrollTop(); - var sx = self.nc.getScrollLeft(); - var newy = sy + dy; - var newx = sx + dx; - self.snapx = (newx < 0) || (newx > self.nc.page.maxw); - self.snapy = (newy < 0) || (newy > self.nc.page.maxh); - self.speedx = dx; - self.speedy = dy; - self.lastx = px; - self.lasty = py; - }; - - this.stop = function() { - self.nc.unsynched("domomentum2d"); - if (self.timer) clearTimeout(self.timer); - self.timer = 0; - self.lastscrollx = -1; - self.lastscrolly = -1; - }; - - this.doSnapy = function(nx, ny) { - var snap = false; - - if (ny < 0) { - ny = 0; - snap = true; - } else if (ny > self.nc.page.maxh) { - ny = self.nc.page.maxh; - snap = true; - } - - if (nx < 0) { - nx = 0; - snap = true; - } else if (nx > self.nc.page.maxw) { - nx = self.nc.page.maxw; - snap = true; - } - - (snap) ? self.nc.doScrollPos(nx, ny, self.nc.opt.snapbackspeed): self.nc.triggerScrollEnd(); - }; - - this.doMomentum = function(gp) { - var t = self.time(); - var l = (gp) ? t + gp : self.lasttime; - - var sl = self.nc.getScrollLeft(); - var st = self.nc.getScrollTop(); - - var pageh = self.nc.page.maxh; - var pagew = self.nc.page.maxw; - - self.speedx = (pagew > 0) ? Math.min(60, self.speedx) : 0; - self.speedy = (pageh > 0) ? Math.min(60, self.speedy) : 0; - - var chk = l && (t - l) <= 60; - - if ((st < 0) || (st > pageh) || (sl < 0) || (sl > pagew)) chk = false; - - var sy = (self.speedy && chk) ? self.speedy : false; - var sx = (self.speedx && chk) ? self.speedx : false; - - if (sy || sx) { - var tm = Math.max(16, self.steptime); //timeout granularity - - if (tm > 50) { // do smooth - var xm = tm / 50; - self.speedx *= xm; - self.speedy *= xm; - tm = 50; - } - - self.demulxy = 0; - - self.lastscrollx = self.nc.getScrollLeft(); - self.chkx = self.lastscrollx; - self.lastscrolly = self.nc.getScrollTop(); - self.chky = self.lastscrolly; - - var nx = self.lastscrollx; - var ny = self.lastscrolly; - - var onscroll = function() { - var df = ((self.time() - t) > 600) ? 0.04 : 0.02; - - if (self.speedx) { - nx = Math.floor(self.lastscrollx - (self.speedx * (1 - self.demulxy))); - self.lastscrollx = nx; - if ((nx < 0) || (nx > pagew)) df = 0.10; - } - - if (self.speedy) { - ny = Math.floor(self.lastscrolly - (self.speedy * (1 - self.demulxy))); - self.lastscrolly = ny; - if ((ny < 0) || (ny > pageh)) df = 0.10; - } - - self.demulxy = Math.min(1, self.demulxy + df); - - self.nc.synched("domomentum2d", function() { - - if (self.speedx) { - var scx = self.nc.getScrollLeft(); - if (scx != self.chkx) self.stop(); - self.chkx = nx; - self.nc.setScrollLeft(nx); - } - - if (self.speedy) { - var scy = self.nc.getScrollTop(); - if (scy != self.chky) self.stop(); - self.chky = ny; - self.nc.setScrollTop(ny); - } - - if (!self.timer) { - self.nc.hideCursor(); - self.doSnapy(nx, ny); - } - - }); - - if (self.demulxy < 1) { - self.timer = setTimeout(onscroll, tm); - } else { - self.stop(); - self.nc.hideCursor(); - self.doSnapy(nx, ny); - } - }; - - onscroll(); - - } else { - self.doSnapy(self.nc.getScrollLeft(), self.nc.getScrollTop()); - } - - } - - }; - - - // override jQuery scrollTop - - var _scrollTop = jQuery.fn.scrollTop; // preserve original function - - jQuery.cssHooks["pageYOffset"] = { - get: function(elem, computed, extra) { - var nice = $.data(elem, '__nicescroll') || false; - return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(elem); - }, - set: function(elem, value) { - var nice = $.data(elem, '__nicescroll') || false; - (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call(elem, value); - return this; - } - }; - - /* - $.fx.step["scrollTop"] = function(fx){ - $.cssHooks["scrollTop"].set( fx.elem, fx.now + fx.unit ); - }; -*/ - - jQuery.fn.scrollTop = function(value) { - if (typeof value == "undefined") { - var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false; - return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(this); - } else { - return this.each(function() { - var nice = $.data(this, '__nicescroll') || false; - (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call($(this), value); - }); - } - }; - - // override jQuery scrollLeft - - var _scrollLeft = jQuery.fn.scrollLeft; // preserve original function - - $.cssHooks.pageXOffset = { - get: function(elem, computed, extra) { - var nice = $.data(elem, '__nicescroll') || false; - return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(elem); - }, - set: function(elem, value) { - var nice = $.data(elem, '__nicescroll') || false; - (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call(elem, value); - return this; - } - }; - - /* - $.fx.step["scrollLeft"] = function(fx){ - $.cssHooks["scrollLeft"].set( fx.elem, fx.now + fx.unit ); - }; -*/ - - jQuery.fn.scrollLeft = function(value) { - if (typeof value == "undefined") { - var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false; - return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(this); - } else { - return this.each(function() { - var nice = $.data(this, '__nicescroll') || false; - (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call($(this), value); - }); - } - }; - - var NiceScrollArray = function(doms) { - var self = this; - this.length = 0; - this.name = "nicescrollarray"; - - this.each = function(fn) { - for (var a = 0, i = 0; a < self.length; a++) fn.call(self[a], i++); - return self; - }; - - this.push = function(nice) { - self[self.length] = nice; - self.length++; - }; - - this.eq = function(idx) { - return self[idx]; - }; - - if (doms) { - for (var a = 0; a < doms.length; a++) { - var nice = $.data(doms[a], '__nicescroll') || false; - if (nice) { - this[this.length] = nice; - this.length++; - } - }; - } - - return this; - }; - - function mplex(el, lst, fn) { - for (var a = 0; a < lst.length; a++) fn(el, lst[a]); - }; - mplex( - NiceScrollArray.prototype, ['show', 'hide', 'toggle', 'onResize', 'resize', 'remove', 'stop', 'doScrollPos'], - function(e, n) { - e[n] = function() { - var args = arguments; - return this.each(function() { - this[n].apply(this, args); - }); - }; - } - ); - - jQuery.fn.getNiceScroll = function(index) { - if (typeof index == "undefined") { - return new NiceScrollArray(this); - } else { - var nice = this[index] && $.data(this[index], '__nicescroll') || false; - return nice; - } - }; - - jQuery.extend(jQuery.expr[':'], { - nicescroll: function(a) { - return ($.data(a, '__nicescroll')) ? true : false; - } - }); - - $.fn.niceScroll = function(wrapper, opt) { - if (typeof opt == "undefined") { - if ((typeof wrapper == "object") && !("jquery" in wrapper)) { - opt = wrapper; - wrapper = false; - } - } - opt = $.extend({},opt); // cloning - var ret = new NiceScrollArray(); - if (typeof opt == "undefined") opt = {}; - - if (wrapper || false) { - opt.doc = $(wrapper); - opt.win = $(this); - } - var docundef = !("doc" in opt); - if (!docundef && !("win" in opt)) opt.win = $(this); - - this.each(function() { - var nice = $(this).data('__nicescroll') || false; - if (!nice) { - opt.doc = (docundef) ? $(this) : opt.doc; - nice = new NiceScrollClass(opt, $(this)); - $(this).data('__nicescroll', nice); - } - ret.push(nice); - }); - return (ret.length == 1) ? ret[0] : ret; - }; - - window.NiceScroll = { - getjQuery: function() { - return jQuery - } - }; - - if (!$.nicescroll) { - $.nicescroll = new NiceScrollArray(); - $.nicescroll.options = _globaloptions; - } - -})); \ No newline at end of file -- cgit v1.2.1 From e35199a5aab454322bc9ddce14fe7582793b7635 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 2 Aug 2017 18:14:14 -0500 Subject: fix sidebar job list in IE 11 --- app/assets/stylesheets/pages/builds.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index fc77e119edb..486424fb729 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -246,6 +246,7 @@ .blocks-container { padding: 0 $gl-padding; + width: 289px; } .block { @@ -343,6 +344,7 @@ border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; max-height: 300px; + width: 289px; overflow: auto; svg { -- cgit v1.2.1 From fbd0280362fdc32bbb9f41fb8780c46248c5d8d2 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 4 Aug 2017 16:38:45 -0500 Subject: fix failing specs due phantomJS scroll position --- spec/features/issues/issue_sidebar_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 8e22441e0e8..af11b474842 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -130,8 +130,8 @@ feature 'Issue Sidebar' do it 'adds new label' do page.within('.block.labels') do fill_in 'new_label_name', with: 'wontfix' - page.find(".suggest-colors a", match: :first).click - click_button 'Create' + page.find('.suggest-colors a', match: :first).trigger('click') + page.find('button', text: 'Create').trigger('click') page.within('.dropdown-page-one') do expect(page).to have_content 'wontfix' @@ -142,8 +142,8 @@ feature 'Issue Sidebar' do it 'shows error message if label title is taken' do page.within('.block.labels') do fill_in 'new_label_name', with: label.title - page.find('.suggest-colors a', match: :first).click - click_button 'Create' + page.find('.suggest-colors a', match: :first).trigger('click') + page.find('button', text: 'Create').trigger('click') page.within('.dropdown-page-two') do expect(page).to have_content 'Title has already been taken' -- cgit v1.2.1 From 7c5757ad9a416c9947bb0e9fae9065af47da8a0d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 7 Aug 2017 11:30:09 +0100 Subject: Fix eslint error --- app/assets/javascripts/commons/bootstrap.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js index 4b589b66dc3..c11b7d5f340 100644 --- a/app/assets/javascripts/commons/bootstrap.js +++ b/app/assets/javascripts/commons/bootstrap.js @@ -10,7 +10,6 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/tab'; import 'bootstrap-sass/assets/javascripts/bootstrap/transition'; import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip'; import 'bootstrap-sass/assets/javascripts/bootstrap/popover'; -import 'bootstrap-sass/assets/javascripts/bootstrap/button'; // custom jQuery functions $.fn.extend({ -- cgit v1.2.1 From 149528f472f3d2f3865ae01c764b81c6a97f9380 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 3 Aug 2017 12:50:06 +0100 Subject: Support references to group milestones Group milestones can only be referred to by name, not IID. They also do not support cross-project references. --- app/helpers/gitlab_routing_helper.rb | 16 -- app/helpers/milestones_routing_helper.rb | 17 ++ app/models/milestone.rb | 12 +- config/application.rb | 4 + doc/user/markdown.md | 6 +- lib/banzai/filter/abstract_reference_filter.rb | 41 ++++- lib/banzai/filter/milestone_reference_filter.rb | 34 +++- spec/fixtures/markdown.md.erb | 5 +- spec/helpers/gitlab_routing_helper_spec.rb | 40 ----- spec/helpers/milestones_routing_helper_spec.rb | 46 +++++ .../filter/milestone_reference_filter_spec.rb | 196 ++++++++++++++------- spec/models/milestone_spec.rb | 38 +++- spec/support/markdown_feature.rb | 6 +- spec/support/matchers/markdown_matchers.rb | 2 +- 14 files changed, 313 insertions(+), 150 deletions(-) create mode 100644 app/helpers/milestones_routing_helper.rb create mode 100644 spec/helpers/milestones_routing_helper_spec.rb diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 1f7db9b2eb8..d4a91e533c1 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -47,14 +47,6 @@ module GitlabRoutingHelper project_pipeline_path(pipeline.project, pipeline.id, *args) end - def milestone_path(entity, *args) - if entity.is_group_milestone? - group_milestone_path(entity.group, entity, *args) - elsif entity.is_project_milestone? - project_milestone_path(entity.project, entity, *args) - end - end - def issue_url(entity, *args) project_issue_url(entity.project, entity, *args) end @@ -67,14 +59,6 @@ module GitlabRoutingHelper project_pipeline_url(pipeline.project, pipeline.id, *args) end - def milestone_url(entity, *args) - if entity.is_group_milestone? - group_milestone_url(entity.group, entity, *args) - elsif entity.is_project_milestone? - project_milestone_url(entity.project, entity, *args) - end - end - def pipeline_job_url(pipeline, build, *args) project_job_url(pipeline.project, build.id, *args) end diff --git a/app/helpers/milestones_routing_helper.rb b/app/helpers/milestones_routing_helper.rb new file mode 100644 index 00000000000..766d5262018 --- /dev/null +++ b/app/helpers/milestones_routing_helper.rb @@ -0,0 +1,17 @@ +module MilestonesRoutingHelper + def milestone_path(milestone, *args) + if milestone.is_group_milestone? + group_milestone_path(milestone.group, milestone, *args) + elsif milestone.is_project_milestone? + project_milestone_path(milestone.project, milestone, *args) + end + end + + def milestone_url(milestone, *args) + if milestone.is_group_milestone? + group_milestone_url(milestone.group, milestone, *args) + elsif milestone.is_project_milestone? + project_milestone_url(milestone.project, milestone, *args) + end + end +end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 48d00764965..01e0d0155a3 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -149,7 +149,9 @@ class Milestone < ActiveRecord::Base end ## - # Returns the String necessary to reference this Milestone in Markdown + # Returns the String necessary to reference this Milestone in Markdown. Group + # milestones only support name references, and do not support cross-project + # references. # # format - Symbol format to use (default: :iid, optional: :name) # @@ -161,12 +163,16 @@ class Milestone < ActiveRecord::Base # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # def to_reference(from_project = nil, format: :iid, full: false) - return if is_group_milestone? + return if is_group_milestone? && format != :name format_reference = milestone_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" - "#{project.to_reference(from_project, full: full)}#{reference}" + if project + "#{project.to_reference(from_project, full: full)}#{reference}" + else + reference + end end def reference_link_text(from_project = nil) diff --git a/config/application.rb b/config/application.rb index f7145566262..47887bf8596 100644 --- a/config/application.rb +++ b/config/application.rb @@ -181,7 +181,11 @@ module Gitlab end end + # We add the MilestonesRoutingHelper because we know that this does not + # conflict with the methods defined in `project_url_helpers`, and we want + # these methods available in the same places. Gitlab::Routing.add_helpers(project_url_helpers) + Gitlab::Routing.add_helpers(MilestonesRoutingHelper) end end end diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 0d29b471d52..b42b8f0a525 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -248,7 +248,7 @@ GFM will recognize the following: | `~123` | label by ID | | `~bug` | one-word label by name | | `~"feature request"` | multi-word label by name | -| `%123` | milestone by ID | +| `%123` | project milestone by ID | | `%v1.23` | one-word milestone by name | | `%"release candidate"` | multi-word milestone by name | | `9ba12248` | specific commit | @@ -262,7 +262,7 @@ GFM also recognizes certain cross-project references: |:----------------------------------------|:------------------------| | `namespace/project#123` | issue | | `namespace/project!123` | merge request | -| `namespace/project%123` | milestone | +| `namespace/project%123` | project milestone | | `namespace/project$123` | snippet | | `namespace/project@9ba12248` | specific commit | | `namespace/project@9ba12248...b19a04f5` | commit range comparison | @@ -274,7 +274,7 @@ It also has a shorthand version to reference other projects from the same namesp |:------------------------------|:------------------------| | `project#123` | issue | | `project!123` | merge request | -| `project%123` | milestone | +| `project%123` | project milestone | | `project$123` | snippet | | `project@9ba12248` | specific commit | | `project@9ba12248...b19a04f5` | commit range comparison | diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 685b43605ae..5db4fe77885 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -59,6 +59,12 @@ module Banzai # Example: project.merge_requests.find end + # Override if the link reference pattern produces a different ID (global + # ID vs internal ID, for instance) to the regular reference pattern. + def find_object_from_link(project, id) + find_object(project, id) + end + def find_object_cached(project, id) if RequestStore.active? cache = find_objects_cache[object_class][project.id] @@ -69,6 +75,16 @@ module Banzai end end + def find_object_from_link_cached(project, id) + if RequestStore.active? + cache = find_objects_from_link_cache[object_class][project.id] + + get_or_set_cache(cache, id) { find_object_from_link(project, id) } + else + find_object_from_link(project, id) + end + end + def project_from_ref_cached(ref) if RequestStore.active? cache = project_refs_cache @@ -120,7 +136,7 @@ module Banzai if link == inner_html && inner_html =~ /\A#{link_pattern}/ replace_link_node_with_text(node, link) do - object_link_filter(inner_html, link_pattern) + object_link_filter(inner_html, link_pattern, link_reference: true) end next @@ -128,7 +144,7 @@ module Banzai if link =~ /\A#{link_pattern}\z/ replace_link_node_with_href(node, link) do - object_link_filter(link, link_pattern, link_content: inner_html) + object_link_filter(link, link_pattern, link_content: inner_html, link_reference: true) end next @@ -146,15 +162,26 @@ module Banzai # text - String text to replace references in. # pattern - Reference pattern to match against. # link_content - Original content of the link being replaced. + # link_reference - True if this was using the link reference pattern, + # false otherwise. # # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. - def object_link_filter(text, pattern, link_content: nil) + def object_link_filter(text, pattern, link_content: nil, link_reference: false) references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches| project_path = full_project_path(namespace_ref, project_ref) project = project_from_ref_cached(project_path) - if project && object = find_object_cached(project, id) + if project + object = + if link_reference + find_object_from_link_cached(project, id) + else + find_object_cached(project, id) + end + end + + if object title = object_link_title(object) klass = reference_class(object_sym) @@ -303,6 +330,12 @@ module Banzai end end + def find_objects_from_link_cache + RequestStore[:banzai_find_objects_from_link_cache] ||= Hash.new do |hash, key| + hash[key] = Hash.new { |h, k| h[k] = {} } + end + end + def url_for_object_cache RequestStore[:banzai_url_for_object] ||= Hash.new do |hash, key| hash[key] = Hash.new { |h, k| h[k] = {} } diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 45c033d32a8..4fc5f211e84 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -8,8 +8,15 @@ module Banzai Milestone end + # Links to project milestones contain the IID, but when we're handling + # 'regular' references, we need to use the global ID to disambiguate + # between group and project milestones. def find_object(project, id) - project.milestones.find_by(iid: id) + find_milestone_with_finder(project, id: id) + end + + def find_object_from_link(project, iid) + find_milestone_with_finder(project, iid: iid) end def references_in(text, pattern = Milestone.reference_pattern) @@ -22,7 +29,7 @@ module Banzai milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name]) if milestone - yield match, milestone.iid, $~[:project], $~[:namespace], $~ + yield match, milestone.id, $~[:project], $~[:namespace], $~ else match end @@ -36,7 +43,8 @@ module Banzai return unless project milestone_params = milestone_params(milestone_id, milestone_name) - project.milestones.find_by(milestone_params) + + find_milestone_with_finder(project, milestone_params) end def milestone_params(iid, name) @@ -47,15 +55,27 @@ module Banzai end end + def find_milestone_with_finder(project, params) + finder_params = { project_ids: [project.id], order: nil } + + # We don't support IID lookups for group milestones, because IIDs can + # clash between group and project milestones. + if project.group && !params[:iid] + finder_params[:group_ids] = [project.group.id] + end + + MilestonesFinder.new(finder_params).execute.find_by(params) + end + def url_for_object(milestone, project) - h = Gitlab::Routing.url_helpers - h.project_milestone_url(project, milestone, - only_path: context[:only_path]) + Gitlab::Routing + .url_helpers + .milestone_url(milestone, only_path: context[:only_path]) end def object_link_text(object, matches) milestone_link = escape_once(super) - reference = object.project.to_reference(project) + reference = object.project&.to_reference(project) if reference.present? "#{milestone_link} in #{reference}".html_safe diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 58b43805705..4f46e40ce7a 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -227,8 +227,11 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Milestone in another project: <%= xmilestone.to_reference(project) %> - Ignored in code: `<%= simple_milestone.to_reference %>` - Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link) -- Milestone by URL: <%= urls.project_milestone_url(milestone.project, milestone) %> +- Milestone by URL: <%= urls.milestone_url(milestone) %> - Link to milestone by URL: [Milestone](<%= milestone.to_reference %>) +- Group milestone by name: <%= Milestone.reference_prefix %><%= group_milestone.name %> +- Group milestone by name in quotes: <%= group_milestone.to_reference(format: :name) %> +- Group milestone by URL is ignore: <%= urls.milestone_url(group_milestone) %> ### Task Lists diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb index 537e457513f..a44b200c5da 100644 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ b/spec/helpers/gitlab_routing_helper_spec.rb @@ -63,44 +63,4 @@ describe GitlabRoutingHelper do it { expect(resend_invite_group_member_path(group_member)).to eq resend_invite_group_group_member_path(group_member.source, group_member) } end end - - describe '#milestone_path' do - context 'for a group milestone' do - let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) } - - it 'links to the group milestone page' do - expect(milestone_path(milestone)) - .to eq(group_milestone_path(group, milestone)) - end - end - - context 'for a project milestone' do - let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) } - - it 'links to the project milestone page' do - expect(milestone_path(milestone)) - .to eq(project_milestone_path(project, milestone)) - end - end - end - - describe '#milestone_url' do - context 'for a group milestone' do - let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) } - - it 'links to the group milestone page' do - expect(milestone_url(milestone)) - .to eq(group_milestone_url(group, milestone)) - end - end - - context 'for a project milestone' do - let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) } - - it 'links to the project milestone page' do - expect(milestone_url(milestone)) - .to eq(project_milestone_url(project, milestone)) - end - end - end end diff --git a/spec/helpers/milestones_routing_helper_spec.rb b/spec/helpers/milestones_routing_helper_spec.rb new file mode 100644 index 00000000000..dc13a43c2ab --- /dev/null +++ b/spec/helpers/milestones_routing_helper_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe MilestonesRoutingHelper do + let(:project) { build_stubbed(:project) } + let(:group) { build_stubbed(:group) } + + describe '#milestone_path' do + context 'for a group milestone' do + let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) } + + it 'links to the group milestone page' do + expect(milestone_path(milestone)) + .to eq(group_milestone_path(group, milestone)) + end + end + + context 'for a project milestone' do + let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) } + + it 'links to the project milestone page' do + expect(milestone_path(milestone)) + .to eq(project_milestone_path(project, milestone)) + end + end + end + + describe '#milestone_url' do + context 'for a group milestone' do + let(:milestone) { build_stubbed(:milestone, group: group, iid: 1) } + + it 'links to the group milestone page' do + expect(milestone_url(milestone)) + .to eq(group_milestone_url(group, milestone)) + end + end + + context 'for a project milestone' do + let(:milestone) { build_stubbed(:milestone, project: project, iid: 1) } + + it 'links to the project milestone page' do + expect(milestone_url(milestone)) + .to eq(project_milestone_url(project, milestone)) + end + end + end +end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 5db77566513..ebd6c79077e 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -3,57 +3,57 @@ require 'spec_helper' describe Banzai::Filter::MilestoneReferenceFilter do include FilterSpecHelper - let(:project) { create(:project, :public) } - let(:milestone) { create(:milestone, project: project) } - let(:reference) { milestone.to_reference } + let(:group) { create(:group, :public) } + let(:project) { create(:project, :public, group: group) } it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end - %w(pre code a style).each do |elem| - it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>milestone #{milestone.to_reference}" - expect(reference_filter(act).to_html).to eq exp + shared_examples 'reference parsing' do + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>milestone #{reference}" + expect(reference_filter(act).to_html).to eq exp + end end - end - it 'includes default classes' do - doc = reference_filter("Milestone #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip' - end + it 'includes default classes' do + doc = reference_filter("Milestone #{reference}") - it 'includes a data-project attribute' do - doc = reference_filter("Milestone #{reference}") - link = doc.css('a').first + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip' + end - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end + it 'includes a data-project attribute' do + doc = reference_filter("Milestone #{reference}") + link = doc.css('a').first - it 'includes a data-milestone attribute' do - doc = reference_filter("See #{reference}") - link = doc.css('a').first + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end - expect(link).to have_attribute('data-milestone') - expect(link.attr('data-milestone')).to eq milestone.id.to_s - end + it 'includes a data-milestone attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-milestone') + expect(link.attr('data-milestone')).to eq milestone.id.to_s + end - it 'supports an :only_path context' do - doc = reference_filter("Milestone #{reference}", only_path: true) - link = doc.css('a').first.attr('href') + it 'supports an :only_path context' do + doc = reference_filter("Milestone #{reference}", only_path: true) + link = doc.css('a').first.attr('href') - expect(link).not_to match %r(https?://) - expect(link).to eq urls - .project_milestone_path(project, milestone) + expect(link).not_to match %r(https?://) + expect(link).to eq urls.milestone_path(milestone) + end end - context 'Integer-based references' do + shared_examples 'Integer-based references' do it 'links to a valid reference' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls - .project_milestone_url(project, milestone) + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) end it 'links with adjacent text' do @@ -68,15 +68,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - context 'String-based single-word references' do - let(:milestone) { create(:milestone, name: 'gfm', project: project) } + shared_examples 'String-based single-word references' do let(:reference) { "#{Milestone.reference_prefix}#{milestone.name}" } + before do + milestone.update!(name: 'gfm') + end + it 'links to a valid reference' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls - .project_milestone_url(project, milestone) + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) expect(doc.text).to eq 'See gfm' end @@ -92,15 +94,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - context 'String-based multi-word references in quotes' do - let(:milestone) { create(:milestone, name: 'gfm references', project: project) } + shared_examples 'String-based multi-word references in quotes' do let(:reference) { milestone.to_reference(format: :name) } + before do + milestone.update!(name: 'gfm references') + end + it 'links to a valid reference' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls - .project_milestone_url(project, milestone) + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) expect(doc.text).to eq 'See gfm references' end @@ -116,23 +120,27 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - describe 'referencing a milestone in a link href' do - let(:reference) { %Q{Milestone} } + shared_examples 'referencing a milestone in a link href' do + let(:unquoted_reference) { "#{Milestone.reference_prefix}#{milestone.name}" } + let(:link_reference) { %Q{Milestone} } + + before do + milestone.update!(name: 'gfm') + end it 'links to a valid reference' do - doc = reference_filter("See #{reference}") + doc = reference_filter("See #{link_reference}") - expect(doc.css('a').first.attr('href')).to eq urls - .project_milestone_url(project, milestone) + expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone) end it 'links with adjacent text' do - doc = reference_filter("Milestone (#{reference}.)") + doc = reference_filter("Milestone (#{link_reference}.)") expect(doc.to_html).to match(%r(\(Milestone\.\))) end it 'includes a data-project attribute' do - doc = reference_filter("Milestone #{reference}") + doc = reference_filter("Milestone #{link_reference}") link = doc.css('a').first expect(link).to have_attribute('data-project') @@ -140,7 +148,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do end it 'includes a data-milestone attribute' do - doc = reference_filter("See #{reference}") + doc = reference_filter("See #{link_reference}") link = doc.css('a').first expect(link).to have_attribute('data-milestone') @@ -148,7 +156,35 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - describe 'cross-project / cross-namespace complete reference' do + shared_examples 'linking to a milestone as the entire link' do + let(:unquoted_reference) { "#{Milestone.reference_prefix}#{milestone.name}" } + let(:link) { urls.milestone_url(milestone) } + let(:link_reference) { %Q{#{link}} } + + it 'replaces the link text with the milestone reference' do + doc = reference_filter("See #{link}") + + expect(doc.css('a').first.text).to eq(unquoted_reference) + end + + it 'includes a data-project attribute' do + doc = reference_filter("Milestone #{link_reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-milestone attribute' do + doc = reference_filter("See #{link_reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-milestone') + expect(link.attr('data-milestone')).to eq milestone.id.to_s + end + end + + shared_examples 'cross-project / cross-namespace complete reference' do let(:namespace) { create(:namespace) } let(:another_project) { create(:project, :public, namespace: namespace) } let(:milestone) { create(:milestone, project: another_project) } @@ -184,7 +220,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - describe 'cross-project / same-namespace complete reference' do + shared_examples 'cross-project / same-namespace complete reference' do let(:namespace) { create(:namespace) } let(:project) { create(:project, :public, namespace: namespace) } let(:another_project) { create(:project, :public, namespace: namespace) } @@ -221,7 +257,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - describe 'cross project shorthand reference' do + shared_examples 'cross project shorthand reference' do let(:namespace) { create(:namespace) } let(:project) { create(:project, :public, namespace: namespace) } let(:another_project) { create(:project, :public, namespace: namespace) } @@ -258,27 +294,53 @@ describe Banzai::Filter::MilestoneReferenceFilter do end end - describe 'cross project milestone references' do - let(:another_project) { create(:project, :public) } - let(:project_path) { another_project.full_path } - let(:milestone) { create(:milestone, project: another_project) } - let(:reference) { milestone.to_reference(project) } + context 'project milestones' do + let(:milestone) { create(:milestone, project: project) } + let(:reference) { milestone.to_reference } - let!(:result) { reference_filter("See #{reference}") } + include_examples 'reference parsing' - it 'points to referenced project milestone page' do - expect(result.css('a').first.attr('href')).to eq urls - .project_milestone_url(another_project, milestone) + it_behaves_like 'Integer-based references' + it_behaves_like 'String-based single-word references' + it_behaves_like 'String-based multi-word references in quotes' + it_behaves_like 'referencing a milestone in a link href' + it_behaves_like 'cross-project / cross-namespace complete reference' + it_behaves_like 'cross-project / same-namespace complete reference' + it_behaves_like 'cross project shorthand reference' + end + + context 'group milestones' do + let(:milestone) { create(:milestone, group: group) } + let(:reference) { milestone.to_reference(format: :name) } + + include_examples 'reference parsing' + + it_behaves_like 'String-based single-word references' + it_behaves_like 'String-based multi-word references in quotes' + it_behaves_like 'referencing a milestone in a link href' + + it 'does not support references by IID' do + doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}") + + expect(doc.css('a')).to be_empty end - it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_path}" + it 'does not support references by link' do + doc = reference_filter("See #{urls.milestone_url(milestone)}") + + expect(doc.css('a').first.text).to eq(urls.milestone_url(milestone)) end - it 'escapes the name attribute' do - allow_any_instance_of(Milestone).to receive(:title).and_return(%{">whatever Date: Thu, 3 Aug 2017 18:37:32 +0100 Subject: Create system notes for group milestone changes --- app/services/issuable_base_service.rb | 5 +-- app/services/system_note_service.rb | 3 +- spec/services/system_note_service_spec.rb | 53 +++++++++++++++++++++++-------- spec/support/issuable_shared_examples.rb | 6 ++-- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 760a15e3ed0..7df5039f2e4 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -2,11 +2,8 @@ class IssuableBaseService < BaseService private def create_milestone_note(issuable) - milestone = issuable.milestone - return if milestone && milestone.is_group_milestone? - SystemNoteService.change_milestone( - issuable, issuable.project, current_user, milestone) + issuable, issuable.project, current_user, issuable.milestone) end def create_labels_note(issuable, old_labels) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 2dbee9c246e..1763f64a4e4 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -142,7 +142,8 @@ module SystemNoteService # # Returns the created Note object def change_milestone(noteable, project, author, milestone) - body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}" + format = milestone&.is_group_milestone? ? :name : :iid + body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project, format: format)}" create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone')) end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index e3805160b04..8f1eb4863d9 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe SystemNoteService do include Gitlab::Routing - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } let(:author) { create(:user) } let(:noteable) { create(:issue, project: project) } let(:issue) { noteable } @@ -242,25 +243,51 @@ describe SystemNoteService do end describe '.change_milestone' do - subject { described_class.change_milestone(noteable, project, author, milestone) } + context 'for a project milestone' do + subject { described_class.change_milestone(noteable, project, author, milestone) } - let(:milestone) { create(:milestone, project: project) } + let(:milestone) { create(:milestone, project: project) } - it_behaves_like 'a system note' do - let(:action) { 'milestone' } - end + it_behaves_like 'a system note' do + let(:action) { 'milestone' } + end - context 'when milestone added' do - it 'sets the note text' do - expect(subject.note).to eq "changed milestone to #{milestone.to_reference}" + context 'when milestone added' do + it 'sets the note text' do + expect(subject.note).to eq "changed milestone to #{milestone.to_reference}" + end + end + + context 'when milestone removed' do + let(:milestone) { nil } + + it 'sets the note text' do + expect(subject.note).to eq 'removed milestone' + end end end - context 'when milestone removed' do - let(:milestone) { nil } + context 'for a group milestone' do + subject { described_class.change_milestone(noteable, project, author, milestone) } - it 'sets the note text' do - expect(subject.note).to eq 'removed milestone' + let(:milestone) { create(:milestone, group: group) } + + it_behaves_like 'a system note' do + let(:action) { 'milestone' } + end + + context 'when milestone added' do + it 'sets the note text to use the milestone name' do + expect(subject.note).to eq "changed milestone to #{milestone.to_reference(format: :name)}" + end + end + + context 'when milestone removed' do + let(:milestone) { nil } + + it 'sets the note text' do + expect(subject.note).to eq 'removed milestone' + end end end end diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb index 970fe10db2b..42f3b4db23c 100644 --- a/spec/support/issuable_shared_examples.rb +++ b/spec/support/issuable_shared_examples.rb @@ -21,15 +21,15 @@ shared_examples 'system notes for milestones' do create(:group_member, group: group, user: user) end - it 'does not create system note' do + it 'creates a system note' do expect do update_issuable(milestone: group_milestone) - end.not_to change { Note.system.count } + end.to change { Note.system.count }.by(1) end end context 'project milestones' do - it 'creates system note' do + it 'creates a system note' do expect do update_issuable(milestone: create(:milestone)) end.to change { Note.system.count }.by(1) -- cgit v1.2.1 From 76b80d6e96bd003de7d46736eb7b6d2de98df1a1 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 3 Aug 2017 18:50:52 +0100 Subject: Show group milestones in autocomplete --- app/services/projects/autocomplete_service.rb | 10 +++++++- .../group-milestone-references-system-notes.yml | 4 ++++ doc/user/project/milestones/index.md | 3 ++- .../services/projects/autocomplete_service_spec.rb | 27 ++++++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/group-milestone-references-system-notes.yml diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index fc85f398935..724a77c873a 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -5,7 +5,15 @@ module Projects end def milestones - @project.milestones.active.reorder(due_date: :asc, title: :asc).select([:iid, :title]) + finder_params = { + project_ids: [@project.id], + state: :active, + order: { due_date: :asc, title: :asc } + } + + finder_params[:group_ids] = [@project.group.id] if @project.group + + MilestonesFinder.new(finder_params).execute.select([:iid, :title]) end def merge_requests diff --git a/changelogs/unreleased/group-milestone-references-system-notes.yml b/changelogs/unreleased/group-milestone-references-system-notes.yml new file mode 100644 index 00000000000..58215352305 --- /dev/null +++ b/changelogs/unreleased/group-milestone-references-system-notes.yml @@ -0,0 +1,4 @@ +--- +title: Support Markdown references, autocomplete, and quick actions for group milestones +merge_request: +author: diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md index 23ffde4e8bd..876b98a4dc5 100644 --- a/doc/user/project/milestones/index.md +++ b/doc/user/project/milestones/index.md @@ -56,4 +56,5 @@ total merge requests and issues. ## Quick actions -[Quick actions](../quick_actions.md) are available for assigning and removing project milestones only. [In the future](https://gitlab.com/gitlab-org/gitlab-ce/issues/34778), this will also apply to group milestones. +[Quick actions](../quick_actions.md) are available for assigning and removing +project and group milestones. diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index c1f098530bf..426593be428 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -88,4 +88,31 @@ describe Projects::AutocompleteService do end end end + + describe '#milestones' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let!(:group_milestone) { create(:milestone, group: group) } + let!(:project_milestone) { create(:milestone, project: project) } + + let(:milestone_titles) { described_class.new(project, user).milestones.map(&:title) } + + it 'includes project and group milestones' do + expect(milestone_titles).to eq([group_milestone.title, project_milestone.title]) + end + + it 'does not include closed milestones' do + group_milestone.close + + expect(milestone_titles).to eq([project_milestone.title]) + end + + it 'does not include milestones from other projects in the group' do + other_project = create(:project, group: group) + project_milestone.update!(project: other_project) + + expect(milestone_titles).to eq([group_milestone.title]) + end + end end -- cgit v1.2.1 From aae947cb1c1252a642e0464c61132ec2e965eb80 Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Wed, 19 Jul 2017 15:32:56 +0100 Subject: Fixes race condition in project uploads Originally picked as: 3fc0dbcbebcd470fe14f8b5fb7ad55dc3912402e. But given the internals changed, this was changed now. The changelog was removed too. --- app/services/projects/gitlab_projects_import_service.rb | 7 +++++-- lib/gitlab/import_export.rb | 4 +--- spec/features/projects/import_export/import_file_spec.rb | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb index 5ba1b6436f4..4ca6414b73b 100644 --- a/app/services/projects/gitlab_projects_import_service.rb +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -22,8 +22,11 @@ module Projects private def import_upload_path - @import_upload_path ||= Gitlab::ImportExport - .import_upload_path(filename: "#{params[:namespace_id]}-#{params[:path]}") + @import_upload_path ||= Gitlab::ImportExport.import_upload_path(filename: tmp_filename) + end + + def tmp_filename + "#{SecureRandom.hex}_#{params[:path]}" end def file diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 30b536383f9..3470a09eaf0 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -15,9 +15,7 @@ module Gitlab end def import_upload_path(filename:) - milliseconds = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) - - File.join(storage_path, 'uploads', "#{milliseconds}-#{filename}") + File.join(storage_path, 'uploads', filename) end def project_filename diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index c0cfb9eafe2..9b4235d9186 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -31,6 +31,7 @@ feature 'Import/Export - project import integration test', js: true do expect(page).to have_content('GitLab project export') expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=test-project-path") + expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A[0-9a-f]{32}_test_project_export\.tar\.gz\z/).and_call_original attach_file('file', file) -- cgit v1.2.1 From 2e6aa4f25e14fa7e57ea9f271e302e291ecc9e05 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 7 Aug 2017 14:21:28 +0200 Subject: After merge cleanup --- app/controllers/projects_controller.rb | 12 ++---------- app/services/projects/create_service.rb | 4 ++++ app/views/projects/new.html.haml | 2 +- changelogs/unreleased/zj-project-templates.yml | 4 ++++ doc/development/rake_tasks.md | 17 +++++++++++++++++ lib/tasks/gitlab/update_templates.rake | 7 ++++--- spec/features/projects_spec.rb | 6 +----- spec/lib/gitlab/project_template_spec.rb | 2 +- .../projects/create_from_template_service_spec.rb | 4 ++-- 9 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 changelogs/unreleased/zj-project-templates.yml diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 5c7c99fe7ef..08219a5112e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -26,13 +26,9 @@ class ProjectsController < Projects::ApplicationController render 'edit' end + def create - @project = - if project_from_template? - ::Projects::CreateFromTemplateService.new(current_user, project_params).execute - else - ::Projects::CreateService.new(current_user, project_params).execute - end + @project = ::Projects::CreateService.new(current_user, project_params).execute if @project.saved? cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) } @@ -351,10 +347,6 @@ class ProjectsController < Projects::ApplicationController false end - def project_from_template? - project_params[:template_name]&.present? - end - def project_view_files? if current_user current_user.project_view == 'files' diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index e874a2d8789..48578b6d9e5 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -5,6 +5,10 @@ module Projects end def execute + if @params[:template_name]&.present? + return ::Projects::CreateFromTemplateService.new(current_user, params).execute + end + forked_from_project_id = params.delete(:forked_from_project_id) import_data = params.delete(:import_data) @skip_wiki = params.delete(:skip_wiki) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 05521ac75b9..e3bbebbcf4c 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -25,7 +25,7 @@ .form-group = f.label :template_project, class: 'label-light' do Create from template - = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What's included in a template?" }, title: "What's included in a template?", class: 'has-tooltip', data: { placement: 'top'} + = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: "What’s included in a template?" }, title: "What’s included in a template?", class: 'has-tooltip', data: { placement: 'top'} %div = render 'project_templates', f: f .second-column diff --git a/changelogs/unreleased/zj-project-templates.yml b/changelogs/unreleased/zj-project-templates.yml new file mode 100644 index 00000000000..ab6e0f2d5f2 --- /dev/null +++ b/changelogs/unreleased/zj-project-templates.yml @@ -0,0 +1,4 @@ +--- +title: Projects can be created from templates +merge_request: 13108 +author: diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 42bb5e8619c..bfd80aab6a4 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -146,3 +146,20 @@ If new emoji are added, the spritesheet may change size. To compensate for such changes, first generate the `emoji.png` spritesheet with the above Rake task, then check the dimensions of the new spritesheet and update the `SPRITESHEET_WIDTH` and `SPRITESHEET_HEIGHT` constants accordingly. + +## Updating project templates + +Starting a project from a template needs this project to be exported. On a +up to date master branch with run: + +``` +gdk run db +# In a new terminal window +bundle exec rake gitlab:update_project_templates +git checkout -b update-project-templates +git add vendor/project_templates +git commit +git push -u origin update-project-templates +``` + +Now create a merge request and merge that to master. diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake index 26f6276e84b..17199c1871d 100644 --- a/lib/tasks/gitlab/update_templates.rake +++ b/lib/tasks/gitlab/update_templates.rake @@ -24,16 +24,17 @@ namespace :gitlab do path: template.title, skip_wiki: true } + puts "Creating project for #{template.name}" - project = Projects::CreateService.new(admin, project).execute + project = Projects::CreateService.new(admin, params).execute loop do - if project.import_status == "finished" + if project.finished? puts "Import finished for #{template.name}" break end - if project.import_status == "failed" + if project.failed? puts "Failed to import from #{project_params[:import_url]}".red exit(1) end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 48805de2f3b..c2ad3a25365 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' -<<<<<<< HEAD -feature 'Project', feature: true do +feature 'Project' do describe 'creating from template' do let(:user) { create(:user) } let(:template) { Gitlab::ProjectTemplate.find(:rails) } @@ -23,9 +22,6 @@ feature 'Project', feature: true do end end -======= -feature 'Project' do ->>>>>>> master describe 'description' do let(:project) { create(:project, :repository) } let(:path) { project_path(project) } diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index 0f68e87a41a..12e75cdd5d0 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::ProjectTemplate do describe 'instance methods' do subject { described_class.new('phoenix', 'Phoenix Framework') } - it { is_expected.to respond_to(:logo_path, :file, :archive_path) } + it { is_expected.to respond_to(:logo, :file, :archive_path) } end describe 'validate all templates' do diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb index 9125b5bf161..9919ec254c6 100644 --- a/spec/services/projects/create_from_template_service_spec.rb +++ b/spec/services/projects/create_from_template_service_spec.rb @@ -5,7 +5,7 @@ describe Projects::CreateFromTemplateService do let(:project_params) do { path: user.to_param, - template_title: 'rails' + template_name: 'rails' } end @@ -21,6 +21,6 @@ describe Projects::CreateFromTemplateService do project = subject.execute expect(project).to be_saved - expect(project.import_status).to eq('scheduled') + expect(project.scheduled?).to be(true) end end -- cgit v1.2.1 From 4d7c072da3b61d26ef86df0fde096c5f8dad4fc5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 7 Aug 2017 17:11:11 +0800 Subject: Reset only migration models So that we could make sure migration tests could run even if geo is not setup in EE. This is because we have a model like this: ``` ruby class Geo::BaseRegistry < ActiveRecord::Base def self.connection raise 'Geo secondary database is not configured' unless Gitlab::Geo.geo_database_configured? super end end ``` --- spec/spec_helper.rb | 4 ++-- spec/support/migrations_helpers.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 06769b241ad..0ba6ed56314 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -134,13 +134,13 @@ RSpec.configure do |config| ActiveRecord::Migrator .migrate(migrations_paths, previous_migration.version) - ActiveRecord::Base.descendants.each(&:reset_column_information) + reset_column_in_migration_models end config.after(:example, :migration) do ActiveRecord::Migrator.migrate(migrations_paths) - ActiveRecord::Base.descendants.each(&:reset_column_information) + reset_column_in_migration_models end config.around(:each, :nested_groups) do |example| diff --git a/spec/support/migrations_helpers.rb b/spec/support/migrations_helpers.rb index 91fbb4eaf48..aabdad13047 100644 --- a/spec/support/migrations_helpers.rb +++ b/spec/support/migrations_helpers.rb @@ -15,6 +15,16 @@ module MigrationsHelpers ActiveRecord::Migrator.migrations(migrations_paths) end + def reset_column_in_migration_models + described_class.constants.sort.each do |name| + const = described_class.const_get(name) + + if const.is_a?(Class) && const < ActiveRecord::Base + const.reset_column_information + end + end + end + def previous_migration migrations.each_cons(2) do |previous, migration| break previous if migration.name == described_class.name -- cgit v1.2.1 From db65499e26d6bc0c882f4855fe0e376bb1df3e6d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 7 Aug 2017 15:32:43 +0200 Subject: Fix last feature test for project templates --- app/controllers/projects_controller.rb | 1 - lib/tasks/gitlab/update_templates.rake | 3 ++- spec/features/projects_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 08219a5112e..0bffae6decd 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -26,7 +26,6 @@ class ProjectsController < Projects::ApplicationController render 'edit' end - def create @project = ::Projects::CreateService.new(current_user, project_params).execute diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake index 17199c1871d..a7e30423c7a 100644 --- a/lib/tasks/gitlab/update_templates.rake +++ b/lib/tasks/gitlab/update_templates.rake @@ -40,8 +40,9 @@ namespace :gitlab do end puts "Waiting for the import to finish" + sleep(5) - project = project.reload + project.reload end Projects::ImportExport::ExportService.new(project, admin).execute diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index c2ad3a25365..7e4d53332e5 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -11,14 +11,14 @@ feature 'Project' do end it "allows creation from templates" do - fill_in("project_template_name", with: template.title) + page.choose(template.name) fill_in("project_path", with: template.name) page.within '#content-body' do click_button "Create project" end - expect(page).to have_content 'Import in progress' + expect(page).to have_content 'This project Loading..' end end -- cgit v1.2.1 From 9ef3c431e4859e1bc03267735b956d5920d5dd42 Mon Sep 17 00:00:00 2001 From: Jarka Kadlecova Date: Tue, 1 Aug 2017 14:38:45 +0200 Subject: Move some after_create parts to worker to improve performance --- app/models/concerns/issuable.rb | 1 + app/services/issuable_base_service.rb | 1 - app/services/issues/create_service.rb | 7 ++- app/services/merge_requests/create_service.rb | 8 +++- app/workers/concerns/new_issuable.rb | 23 +++++++++ app/workers/new_issue_worker.rb | 17 +++++++ app/workers/new_merge_request_worker.rb | 17 +++++++ .../unreleased/32844-issuables-performance.yml | 4 ++ config/sidekiq_queues.yml | 2 + .../issues/create_branch_merge_request_spec.rb | 14 ++++-- spec/models/issue_spec.rb | 4 -- spec/support/cycle_analytics_helpers.rb | 4 +- spec/workers/new_issue_worker_spec.rb | 54 +++++++++++++++++++++ spec/workers/new_merge_request_worker_spec.rb | 56 ++++++++++++++++++++++ 14 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 app/workers/concerns/new_issuable.rb create mode 100644 app/workers/new_issue_worker.rb create mode 100644 app/workers/new_merge_request_worker.rb create mode 100644 changelogs/unreleased/32844-issuables-performance.yml create mode 100644 spec/workers/new_issue_worker_spec.rb create mode 100644 spec/workers/new_merge_request_worker_spec.rb diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 935ffe343ff..3731b7c8577 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -16,6 +16,7 @@ module Issuable include TimeTrackable include Importable include Editable + include AfterCommitQueue # This object is used to gather issuable meta data for displaying # upvotes, downvotes, notes and closing merge requests count for issues and merge requests diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index ea497729115..0e25c555136 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -182,7 +182,6 @@ class IssuableBaseService < BaseService if params.present? && create_issuable(issuable, params, label_ids: label_ids) after_create(issuable) - issuable.create_cross_references!(current_user) execute_hooks(issuable) invalidate_cache_counts(issuable, users: issuable.assignees) end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index 718a7ac1f22..9114f0ccc81 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -15,11 +15,14 @@ module Issues def before_create(issue) spam_check(issue, current_user) issue.move_to_end + + user = current_user + issue.run_after_commit do + NewIssueWorker.perform_async(issue.id, user.id) + end end def after_create(issuable) - event_service.open_issue(issuable, current_user) - notification_service.new_issue(issuable, current_user) todo_service.new_issue(issuable, current_user) user_agent_detail_service.create resolve_discussions_with_issue(issuable) diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 19189e64acf..eb04048b748 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -17,9 +17,15 @@ module MergeRequests create(merge_request) end + def before_create(merge_request) + user = current_user + merge_request.run_after_commit do + NewMergeRequestWorker.perform_async(merge_request.id, user.id) + end + end + def after_create(issuable) event_service.open_mr(issuable, current_user) - notification_service.new_merge_request(issuable, current_user) todo_service.new_merge_request(issuable, current_user) issuable.cache_merge_request_closes_issues!(current_user) end diff --git a/app/workers/concerns/new_issuable.rb b/app/workers/concerns/new_issuable.rb new file mode 100644 index 00000000000..3fd472bf0c1 --- /dev/null +++ b/app/workers/concerns/new_issuable.rb @@ -0,0 +1,23 @@ +module NewIssuable + attr_reader :issuable, :user + + def ensure_objects_found(issuable_id, user_id) + @issuable = issuable_class.find_by(id: issuable_id) + unless @issuable + log_error(issuable_class, issuable_id) + return false + end + + @user = User.find_by(id: user_id) + unless @user + log_error(User, user_id) + return false + end + + true + end + + def log_error(record_class, record_id) + Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job") + end +end diff --git a/app/workers/new_issue_worker.rb b/app/workers/new_issue_worker.rb new file mode 100644 index 00000000000..19a778ad522 --- /dev/null +++ b/app/workers/new_issue_worker.rb @@ -0,0 +1,17 @@ +class NewIssueWorker + include Sidekiq::Worker + include DedicatedSidekiqQueue + include NewIssuable + + def perform(issue_id, user_id) + return unless ensure_objects_found(issue_id, user_id) + + EventCreateService.new.open_issue(issuable, user) + NotificationService.new.new_issue(issuable, user) + issuable.create_cross_references!(user) + end + + def issuable_class + Issue + end +end diff --git a/app/workers/new_merge_request_worker.rb b/app/workers/new_merge_request_worker.rb new file mode 100644 index 00000000000..3c8a68016ff --- /dev/null +++ b/app/workers/new_merge_request_worker.rb @@ -0,0 +1,17 @@ +class NewMergeRequestWorker + include Sidekiq::Worker + include DedicatedSidekiqQueue + include NewIssuable + + def perform(merge_request_id, user_id) + return unless ensure_objects_found(merge_request_id, user_id) + + EventCreateService.new.open_mr(issuable, user) + NotificationService.new.new_merge_request(issuable, user) + issuable.create_cross_references!(user) + end + + def issuable_class + MergeRequest + end +end diff --git a/changelogs/unreleased/32844-issuables-performance.yml b/changelogs/unreleased/32844-issuables-performance.yml new file mode 100644 index 00000000000..e9b21c1aa45 --- /dev/null +++ b/changelogs/unreleased/32844-issuables-performance.yml @@ -0,0 +1,4 @@ +--- +title: Move some code from services to workers in order to improve performance +merge_request: 13326 +author: diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 7496bfa4fbb..83abc83c9f0 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -23,6 +23,8 @@ - [update_merge_requests, 3] - [process_commit, 3] - [new_note, 2] + - [new_issue, 2] + - [new_merge_request, 2] - [build, 2] - [pipeline, 2] - [gitlab_shell, 2] diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb index f59f687cf51..546dc7e8a49 100644 --- a/spec/features/issues/create_branch_merge_request_spec.rb +++ b/spec/features/issues/create_branch_merge_request_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Create Branch/Merge Request Dropdown on issue page', js: true do +feature 'Create Branch/Merge Request Dropdown on issue page', :feature, :js do let(:user) { create(:user) } let!(:project) { create(:project, :repository) } let(:issue) { create(:issue, project: project, title: 'Cherry-Coloured Funk') } @@ -14,10 +14,14 @@ feature 'Create Branch/Merge Request Dropdown on issue page', js: true do it 'allows creating a merge request from the issue page' do visit project_issue_path(project, issue) - select_dropdown_option('create-mr') - - expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"') - expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first)) + perform_enqueued_jobs do + select_dropdown_option('create-mr') + + expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"') + expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first)) + + wait_for_requests + end visit project_issue_path(project, issue) diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index d72790eefe5..c7b41ab6f4b 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -191,14 +191,10 @@ describe Issue do end it 'returns the merge request to close this issue' do - mr - expect(issue.closed_by_merge_requests(mr.author)).to eq([mr]) end it "returns an empty array when the merge request is closed already" do - closed_mr - expect(issue.closed_by_merge_requests(closed_mr.author)).to eq([]) end diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb index c0a5491a430..30911e7fa86 100644 --- a/spec/support/cycle_analytics_helpers.rb +++ b/spec/support/cycle_analytics_helpers.rb @@ -41,7 +41,9 @@ module CycleAnalyticsHelpers target_branch: 'master' } - MergeRequests::CreateService.new(project, user, opts).execute + mr = MergeRequests::CreateService.new(project, user, opts).execute + NewMergeRequestWorker.new.perform(mr, user) + mr end def merge_merge_requests_closing_issue(issue) diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb new file mode 100644 index 00000000000..ed49ce57c0b --- /dev/null +++ b/spec/workers/new_issue_worker_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe NewIssueWorker do + describe '#perform' do + let(:worker) { described_class.new } + + context 'when an issue not found' do + it 'does not call Services' do + expect(EventCreateService).not_to receive(:new) + expect(NotificationService).not_to receive(:new) + + worker.perform(99, create(:user).id) + end + + it 'logs an error' do + expect(Rails.logger).to receive(:error).with('NewIssueWorker: couldn\'t find Issue with ID=99, skipping job') + + worker.perform(99, create(:user).id) + end + end + + context 'when a user not found' do + it 'does not call Services' do + expect(EventCreateService).not_to receive(:new) + expect(NotificationService).not_to receive(:new) + + worker.perform(create(:issue).id, 99) + end + + it 'logs an error' do + expect(Rails.logger).to receive(:error).with('NewIssueWorker: couldn\'t find User with ID=99, skipping job') + + worker.perform(create(:issue).id, 99) + end + end + + context 'when everything is ok' do + let(:project) { create(:project, :public) } + let(:mentioned) { create(:user) } + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project, description: "issue for #{mentioned.to_reference}") } + + it 'creates a new event record' do + expect{ worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1) + end + + it 'creates a notification for the assignee' do + expect(Notify).to receive(:new_issue_email).with(mentioned.id, issue.id).and_return(double(deliver_later: true)) + + worker.perform(issue.id, user.id) + end + end + end +end diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb new file mode 100644 index 00000000000..85af6184d39 --- /dev/null +++ b/spec/workers/new_merge_request_worker_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe NewMergeRequestWorker do + describe '#perform' do + let(:worker) { described_class.new } + + context 'when a merge request not found' do + it 'does not call Services' do + expect(EventCreateService).not_to receive(:new) + expect(NotificationService).not_to receive(:new) + + worker.perform(99, create(:user).id) + end + + it 'logs an error' do + expect(Rails.logger).to receive(:error).with('NewMergeRequestWorker: couldn\'t find MergeRequest with ID=99, skipping job') + + worker.perform(99, create(:user).id) + end + end + + context 'when a user not found' do + it 'does not call Services' do + expect(EventCreateService).not_to receive(:new) + expect(NotificationService).not_to receive(:new) + + worker.perform(create(:merge_request).id, 99) + end + + it 'logs an error' do + expect(Rails.logger).to receive(:error).with('NewMergeRequestWorker: couldn\'t find User with ID=99, skipping job') + + worker.perform(create(:merge_request).id, 99) + end + end + + context 'when everything is ok' do + let(:project) { create(:project, :public) } + let(:mentioned) { create(:user) } + let(:user) { create(:user) } + let(:merge_request) do + create(:merge_request, source_project: project, description: "mr for #{mentioned.to_reference}") + end + + it 'creates a new event record' do + expect{ worker.perform(merge_request.id, user.id) }.to change { Event.count }.from(0).to(1) + end + + it 'creates a notification for the assignee' do + expect(Notify).to receive(:new_merge_request_email).with(mentioned.id, merge_request.id).and_return(double(deliver_later: true)) + + worker.perform(merge_request.id, user.id) + end + end + end +end -- cgit v1.2.1 From 29a1c5a126e7289dcaa710cc0933057bf274aff2 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 7 Aug 2017 16:26:50 +0200 Subject: Rename 'limit' to 'blob_size_limit' --- lib/gitlab/git/blob.rb | 13 ++++++------- spec/lib/gitlab/git/blob_spec.rb | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 0b98be3a14f..77b81d2d437 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -51,18 +51,17 @@ module Gitlab end # Returns an array of Blob instances, specified in blob_references as - # [[commit_sha, path], [commit_sha, path], ...]. If limit < 0 then the - # full blob contents are returned. If limit >= 0 then each blob will + # [[commit_sha, path], [commit_sha, path], ...]. If blob_size_limit < 0 then the + # full blob contents are returned. If blob_size_limit >= 0 then each blob will # contain no more than limit bytes in its data attribute. # # Keep in mind that this method may allocate a lot of memory. It is up - # to the caller to limit the number of blobs and/or the content limit - # for the individual blobs. + # to the caller to limit the number of blobs and blob_size_limit. # - def batch(repository, blob_references, limit: nil) - limit ||= MAX_DATA_DISPLAY_SIZE + def batch(repository, blob_references, blob_size_limit: nil) + blob_size_limit ||= MAX_DATA_DISPLAY_SIZE blob_references.map do |sha, path| - find_by_rugged(repository, sha, path, limit: limit) + find_by_rugged(repository, sha, path, limit: blob_size_limit) end end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index ed2a781b172..dd4bec653f2 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -181,10 +181,10 @@ describe Gitlab::Git::Blob, seed_helper: true do end context 'limiting' do - subject { described_class.batch(repository, blob_references, limit: limit) } + subject { described_class.batch(repository, blob_references, blob_size_limit: blob_size_limit) } context 'default' do - let(:limit) { nil } + let(:blob_size_limit) { nil } it 'limits to MAX_DATA_DISPLAY_SIZE' do stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100) @@ -194,19 +194,19 @@ describe Gitlab::Git::Blob, seed_helper: true do end context 'positive' do - let(:limit) { 10 } + let(:blob_size_limit) { 10 } it { expect(subject.first.data.size).to eq(10) } end context 'zero' do - let(:limit) { 0 } + let(:blob_size_limit) { 0 } it { expect(subject.first.data).to eq('') } end context 'negative' do - let(:limit) { -1 } + let(:blob_size_limit) { -1 } it 'ignores MAX_DATA_DISPLAY_SIZE' do stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100) -- cgit v1.2.1 From 1b8fb79287839d9784ae729f5aa9e246948b0f17 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 7 Aug 2017 16:31:47 +0200 Subject: Fix HAML lint errors --- app/views/projects/_project_templates.html.haml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml index c091e62cb6e..21baf35f2ac 100644 --- a/app/views/projects/_project_templates.html.haml +++ b/app/views/projects/_project_templates.html.haml @@ -1,11 +1,10 @@ - -.project-templates-buttons.import-buttons{ data: { toggle: "buttons" }} - %div.btn.blank-option.active +.project-templates-buttons.import-buttons{ data: { toggle: "buttons" } } + .btn.blank-option.active %input{ type: "radio", autocomplete: "off", name: "project_templates", id: "blank", checked: "true" } = icon('file-o', class: 'btn-template-icon') Blank - Gitlab::ProjectTemplate.all.each do |template| - %div.btn + .btn %input{ type: "radio", autocomplete: "off", name: "project_templates", id: template.name } = custom_icon(template.logo) = template.title -- cgit v1.2.1 From f278e5fb3e272673ca730b773821bc432f81597e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 7 Aug 2017 18:05:06 +0200 Subject: Set default options outside the raw_log method The raw_log method is meant to become the Gitaly RPC boundary. By setting the defaults before doing the RPC we keep the RPC implementation simpler. We also sidestep the unfortunate subtleties of what happens when options[:limit] is not set, or nil. --- lib/gitlab/git/repository.rb | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 1005a819f95..f6f9d49bf37 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -299,6 +299,21 @@ module Gitlab # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/446 def log(options) + default_options = { + limit: 10, + offset: 0, + path: nil, + follow: false, + skip_merges: false, + disable_walk: false, + after: nil, + before: nil + } + + options = default_options.merge(options) + options[:limit] ||= 0 + options[:offset] ||= 0 + raw_log(options).map { |c| Commit.decorate(c) } end @@ -712,20 +727,6 @@ module Gitlab end def raw_log(options) - default_options = { - limit: 10, - offset: 0, - path: nil, - follow: false, - skip_merges: false, - disable_walk: false, - after: nil, - before: nil - } - - options = default_options.merge(options) - options[:limit] ||= 0 - options[:offset] ||= 0 actual_ref = options[:ref] || root_ref begin sha = sha_from_ref(actual_ref) -- cgit v1.2.1 From 81995317f99550a0cd5c76e9b15fd91364665d1f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Jul 2017 13:40:04 -0300 Subject: Use project.import_url to fetch repositories from Github --- lib/github/import.rb | 19 ++++++++++--------- lib/tasks/import.rake | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/github/import.rb b/lib/github/import.rb index cea4be5460b..fd4059c612e 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -41,13 +41,16 @@ module Github self.reset_callbacks :validate end - attr_reader :project, :repository, :repo, :options, :errors, :cached, :verbose + attr_reader :project, :repository, :repo, :repo_url, :wiki_url, + :options, :errors, :cached, :verbose def initialize(project, options) @project = project @repository = project.repository @repo = project.import_source @options = options + @repo_url = project.import_url + @wiki_url = project.import_url.sub(/\.git\z/, '.wiki.git') @verbose = options.fetch(:verbose, false) @cached = Hash.new { |hash, key| hash[key] = Hash.new } @errors = [] @@ -81,23 +84,21 @@ module Github def fetch_repository begin - project.create_repository unless project.repository.exists? - project.repository.add_remote('github', "https://#{options.fetch(:token)}@github.com/#{repo}.git") + project.ensure_repository + project.repository.add_remote('github', repo_url) project.repository.set_remote_as_mirror('github') project.repository.fetch_remote('github', forced: true) rescue Gitlab::Shell::Error => e - error(:project, "https://github.com/#{repo}.git", e.message) + error(:project, repo_url, e.message) raise Github::RepositoryFetchError end end def fetch_wiki_repository - wiki_url = "https://#{options.fetch(:token)}@github.com/#{repo}.wiki.git" - wiki_path = "#{project.full_path}.wiki" + return if project.wiki.repository_exists? - unless project.wiki.repository_exists? - gitlab_shell.import_repository(project.repository_storage_path, wiki_path, wiki_url) - end + wiki_path = "#{project.path_with_namespace}.wiki" + gitlab_shell.import_repository(project.repository_storage_path, wiki_path, wiki_url) rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, # this means that repo has wiki enabled, but have no pages. So, diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index 50b8e331469..75ccd7af793 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -62,6 +62,7 @@ class GithubImport visibility_level: visibility_level, import_type: 'github', import_source: @repo['full_name'], + import_url: @repo['clone_url'].sub('://', "://#{options[:token]}@"), skip_wiki: @repo['has_wiki'] ).execute end -- cgit v1.2.1 From 51186e72c5b06e144960bd0caf9684dbc387920c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Jul 2017 13:43:25 -0300 Subject: Use a custom root endpoint if defined on GH ominiauth provider settings --- lib/github/import.rb | 17 ++++++++++++++++- lib/tasks/import.rake | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/github/import.rb b/lib/github/import.rb index fd4059c612e..69bc5f08d9a 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -48,9 +48,9 @@ module Github @project = project @repository = project.repository @repo = project.import_source - @options = options @repo_url = project.import_url @wiki_url = project.import_url.sub(/\.git\z/, '.wiki.git') + @options = options.reverse_merge(url: root_endpoint) @verbose = options.fetch(:verbose, false) @cached = Hash.new { |hash, key| hash[key] = Hash.new } @errors = [] @@ -383,5 +383,20 @@ module Github def error(type, url, message) errors << { type: type, url: Gitlab::UrlSanitizer.sanitize(url), error: message } end + + def root_endpoint + @root_endpoint ||= custom_endpoint || githup_endpoint + end + + def custom_endpoint + Gitlab.config.omniauth.providers + .find { |provider| provider.name == 'github' } + .to_h + .dig('args', 'client_options', 'site') + end + + def github_endpoint + OmniAuth::Strategies::GitHub.default_options[:client_options][:site] + end end end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index 75ccd7af793..90792dfe51d 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -7,7 +7,7 @@ class GithubImport end def initialize(token, gitlab_username, project_path, extras) - @options = { url: 'https://api.github.com', token: token, verbose: true } + @options = { token: token, verbose: true } @project_path = project_path @current_user = User.find_by_username(gitlab_username) @github_repo = extras.empty? ? nil : extras.first -- cgit v1.2.1 From a7363d4a54e95dcd417ca1a75ab65265d069e164 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Jul 2017 20:19:22 -0300 Subject: Move GitHub root endpoint methods to Github::Client --- lib/github/client.rb | 19 ++++++++++++++++++- lib/github/import.rb | 17 +---------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/github/client.rb b/lib/github/client.rb index e65d908d232..ac4e3a739f9 100644 --- a/lib/github/client.rb +++ b/lib/github/client.rb @@ -3,7 +3,7 @@ module Github attr_reader :connection, :rate_limit def initialize(options) - @connection = Faraday.new(url: options.fetch(:url)) do |faraday| + @connection = Faraday.new(url: options.fetch(:url, root_endpoint)) do |faraday| faraday.options.open_timeout = options.fetch(:timeout, 60) faraday.options.timeout = options.fetch(:timeout, 60) faraday.authorization 'token', options.fetch(:token) @@ -19,5 +19,22 @@ module Github Github::Response.new(connection.get(url, query)) end + + private + + def root_endpoint + custom_endpoint || githup_endpoint + end + + def custom_endpoint + Gitlab.config.omniauth.providers + .find { |provider| provider.name == 'github' } + .to_h + .dig('args', 'client_options', 'site') + end + + def github_endpoint + OmniAuth::Strategies::GitHub.default_options[:client_options][:site] + end end end diff --git a/lib/github/import.rb b/lib/github/import.rb index 69bc5f08d9a..8ca7b678633 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -50,7 +50,7 @@ module Github @repo = project.import_source @repo_url = project.import_url @wiki_url = project.import_url.sub(/\.git\z/, '.wiki.git') - @options = options.reverse_merge(url: root_endpoint) + @options = options @verbose = options.fetch(:verbose, false) @cached = Hash.new { |hash, key| hash[key] = Hash.new } @errors = [] @@ -383,20 +383,5 @@ module Github def error(type, url, message) errors << { type: type, url: Gitlab::UrlSanitizer.sanitize(url), error: message } end - - def root_endpoint - @root_endpoint ||= custom_endpoint || githup_endpoint - end - - def custom_endpoint - Gitlab.config.omniauth.providers - .find { |provider| provider.name == 'github' } - .to_h - .dig('args', 'client_options', 'site') - end - - def github_endpoint - OmniAuth::Strategies::GitHub.default_options[:client_options][:site] - end end end -- cgit v1.2.1 From 812620527ffee70ad2320eebcaec161a007b9d67 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Jul 2017 20:20:13 -0300 Subject: Fix small typoe on GitHub import rake task --- lib/tasks/import.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index 90792dfe51d..96b8f59242c 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -62,7 +62,7 @@ class GithubImport visibility_level: visibility_level, import_type: 'github', import_source: @repo['full_name'], - import_url: @repo['clone_url'].sub('://', "://#{options[:token]}@"), + import_url: @repo['clone_url'].sub('://', "://#{@options[:token]}@"), skip_wiki: @repo['has_wiki'] ).execute end -- cgit v1.2.1 From 4caa4293742cbe15c68c5d5882637fd6bf8816cf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Jul 2017 20:45:06 -0300 Subject: Set the GitHub API token on options hash --- lib/github/import.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/github/import.rb b/lib/github/import.rb index 8ca7b678633..fa2c6790ba7 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -44,13 +44,13 @@ module Github attr_reader :project, :repository, :repo, :repo_url, :wiki_url, :options, :errors, :cached, :verbose - def initialize(project, options) + def initialize(project, options = {}) @project = project @repository = project.repository @repo = project.import_source @repo_url = project.import_url @wiki_url = project.import_url.sub(/\.git\z/, '.wiki.git') - @options = options + @options = options.reverse_merge(token: project.import_data&.credentials&.fetch(:user)) @verbose = options.fetch(:verbose, false) @cached = Hash.new { |hash, key| hash[key] = Hash.new } @errors = [] -- cgit v1.2.1 From 29d380b03ce711d97fc95812618498a12f85cd02 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Jul 2017 20:47:46 -0300 Subject: Set the new GitHub import as import source --- lib/gitlab/import_sources.rb | 2 +- spec/lib/gitlab/import_sources_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 52276cbcd9a..5404dc11a87 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -8,7 +8,7 @@ module Gitlab ImportSource = Struct.new(:name, :title, :importer) ImportTable = [ - ImportSource.new('github', 'GitHub', Gitlab::GithubImport::Importer), + ImportSource.new('github', 'GitHub', Github::Import), ImportSource.new('bitbucket', 'Bitbucket', Gitlab::BitbucketImport::Importer), ImportSource.new('gitlab', 'GitLab.com', Gitlab::GitlabImport::Importer), ImportSource.new('google_code', 'Google Code', Gitlab::GoogleCodeImport::Importer), diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb index b3b5e5e7e33..c5725f47453 100644 --- a/spec/lib/gitlab/import_sources_spec.rb +++ b/spec/lib/gitlab/import_sources_spec.rb @@ -56,7 +56,7 @@ describe Gitlab::ImportSources do describe '.importer' do import_sources = { - 'github' => Gitlab::GithubImport::Importer, + 'github' => Github::Import, 'bitbucket' => Gitlab::BitbucketImport::Importer, 'gitlab' => Gitlab::GitlabImport::Importer, 'google_code' => Gitlab::GoogleCodeImport::Importer, -- cgit v1.2.1 From 517a9f97c7fefb350a2f8cfac84060e8b76e2818 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Jul 2017 21:16:52 -0300 Subject: Does not fetch repository when importing from GitHub on import service --- app/services/projects/import_service.rb | 6 +++-- spec/services/projects/import_service_spec.rb | 32 +++++++-------------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 50ec3651515..cd661166c3b 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -34,8 +34,10 @@ module Projects def import_repository raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url) + return if project.github_import? + begin - if project.github_import? || project.gitea_import? + if project.gitea_import? fetch_repository else clone_repository @@ -55,7 +57,7 @@ module Projects end def fetch_repository - project.create_repository + project.ensure_repository 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) diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index c0ab1ea704d..034065aab00 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -38,8 +38,7 @@ describe Projects::ImportService do context 'with a Github repository' do it 'succeeds if repository import is successfully' 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) + expect_any_instance_of(Github::Import).to receive(:execute).and_return(true) result = subject.execute @@ -52,16 +51,7 @@ describe Projects::ImportService do result = subject.execute expect(result[:status]).to eq :error - expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.full_path} - 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') + expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - The remote data could not be imported." end end @@ -102,8 +92,7 @@ describe Projects::ImportService do end it 'succeeds if importer succeeds' do - allow_any_instance_of(Repository).to receive(:fetch_remote).and_return(true) - allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true) + allow_any_instance_of(Github::Import).to receive(:execute).and_return(true) result = subject.execute @@ -111,10 +100,7 @@ describe Projects::ImportService do end it 'flushes various caches' do - allow_any_instance_of(Repository).to receive(:fetch_remote) - .and_return(true) - - allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute) + allow_any_instance_of(Github::Import).to receive(:execute) .and_return(true) expect_any_instance_of(Repository).to receive(:expire_content_cache) @@ -123,8 +109,7 @@ describe Projects::ImportService do end it 'fails if importer fails' do - allow_any_instance_of(Repository).to receive(:fetch_remote).and_return(true) - allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false) + allow_any_instance_of(Github::Import).to receive(:execute).and_return(false) result = subject.execute @@ -133,8 +118,7 @@ describe Projects::ImportService do end it 'fails if importer raise an error' do - allow_any_instance_of(Gitlab::Shell).to receive(:fetch_remote).and_return(true) - allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API')) + allow_any_instance_of(Github::Import).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API')) result = subject.execute @@ -143,9 +127,9 @@ describe Projects::ImportService do end it 'expires content cache after error' do - allow_any_instance_of(Project).to receive(:repository_exists?).and_return(false, true) + allow_any_instance_of(Project).to receive(:repository_exists?).and_return(false) - expect_any_instance_of(Gitlab::Shell).to receive(:fetch_remote).and_raise(Gitlab::Shell::Error.new('Failed to import the repository')) + expect_any_instance_of(Repository).to receive(:fetch_remote).and_raise(Gitlab::Shell::Error.new) expect_any_instance_of(Repository).to receive(:expire_content_cache) subject.execute -- cgit v1.2.1 From 8021a7244d3448ebc1631259c7d9d43503ccf29d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Jul 2017 21:17:22 -0300 Subject: Expire content cache when repository fetch fails --- lib/github/import.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/github/import.rb b/lib/github/import.rb index fa2c6790ba7..fa279a85433 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -75,6 +75,7 @@ module Github true rescue Github::RepositoryFetchError + expire_repository_cache false ensure keep_track_of_errors @@ -88,7 +89,7 @@ module Github project.repository.add_remote('github', repo_url) project.repository.set_remote_as_mirror('github') project.repository.fetch_remote('github', forced: true) - rescue Gitlab::Shell::Error => e + rescue Gitlab::Git::Repository::NoRepository, Gitlab::Shell::Error => e error(:project, repo_url, e.message) raise Github::RepositoryFetchError end @@ -368,7 +369,7 @@ module Github end def expire_repository_cache - repository.expire_content_cache + repository.expire_content_cache if project.repository_exists? end def keep_track_of_errors -- cgit v1.2.1 From 6f642e3f7bf4667dad6efeb5e43f79b9711bc204 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Jul 2017 21:18:47 -0300 Subject: Fix small typoe on Github::Client#root_endpoint --- lib/github/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/client.rb b/lib/github/client.rb index ac4e3a739f9..0102406fd20 100644 --- a/lib/github/client.rb +++ b/lib/github/client.rb @@ -23,7 +23,7 @@ module Github private def root_endpoint - custom_endpoint || githup_endpoint + custom_endpoint || github_endpoint end def custom_endpoint -- cgit v1.2.1 From cb08cb22188ef1e14ea6d4570586056495614e79 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 3 Aug 2017 17:06:27 -0300 Subject: Extrac common timeout to a constant on Github::Client --- lib/github/client.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/github/client.rb b/lib/github/client.rb index 0102406fd20..7d77f1a91bc 100644 --- a/lib/github/client.rb +++ b/lib/github/client.rb @@ -1,11 +1,13 @@ module Github class Client + TIMEOUT = 60 + attr_reader :connection, :rate_limit def initialize(options) @connection = Faraday.new(url: options.fetch(:url, root_endpoint)) do |faraday| - faraday.options.open_timeout = options.fetch(:timeout, 60) - faraday.options.timeout = options.fetch(:timeout, 60) + faraday.options.open_timeout = options.fetch(:timeout, TIMEOUT) + faraday.options.timeout = options.fetch(:timeout, TIMEOUT) faraday.authorization 'token', options.fetch(:token) faraday.adapter :net_http end -- cgit v1.2.1 From d20a9132bd3b58202ce945a985214b392ac7fc2f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 3 Aug 2017 17:07:08 -0300 Subject: Fix Github::Import cache for user ids --- lib/github/import.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/import.rb b/lib/github/import.rb index fa279a85433..9c61d44e023 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -339,7 +339,7 @@ module Github def user_id(user, fallback_id = nil) return unless user.present? - return cached[:user_ids][user.id] if cached[:user_ids].key?(user.id) + return cached[:user_ids][user.id] if cached[:user_ids][user.id].present? gitlab_user_id = user_id_by_external_uid(user.id) || user_id_by_email(user.email) -- cgit v1.2.1 From e43099e407b9794728e967b1f757686fd8941179 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sun, 6 Aug 2017 23:14:42 -0500 Subject: replace stats-webpack-plugin with webpack-stats-plugin --- config/webpack.config.js | 20 +++++++++++++------- package.json | 4 ++-- yarn.lock | 8 ++++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/config/webpack.config.js b/config/webpack.config.js index 1205b90de40..100fdc31cbb 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -3,7 +3,7 @@ var fs = require('fs'); var path = require('path'); var webpack = require('webpack'); -var StatsPlugin = require('stats-webpack-plugin'); +var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; var CompressionPlugin = require('compression-webpack-plugin'); var NameAllModulesPlugin = require('name-all-modules-plugin'); var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; @@ -127,12 +127,18 @@ var config = { plugins: [ // manifest filename must match config.webpack.manifest_filename // webpack-rails only needs assetsByChunkName to function properly - new StatsPlugin('manifest.json', { - chunkModules: false, - source: false, - chunks: false, - modules: false, - assets: true + new StatsWriterPlugin({ + filename: 'manifest.json', + transform: function(data, opts) { + var stats = opts.compiler.getStats().toJson({ + chunkModules: false, + source: false, + chunks: false, + modules: false, + assets: true + }); + return JSON.stringify(stats, null, 2); + } }), // prevent pikaday from including moment.js diff --git a/package.json b/package.json index eb3084f30bc..57fd3735db6 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "react-dev-utils": "^0.5.2", "select2": "3.5.2-browserify", "sql.js": "^0.4.0", - "stats-webpack-plugin": "^0.4.3", "three": "^0.84.0", "three-orbit-controls": "^82.1.0", "three-stl-loader": "^1.0.4", @@ -61,7 +60,8 @@ "vue-resource": "^1.3.4", "vue-template-compiler": "^2.2.6", "webpack": "^2.6.1", - "webpack-bundle-analyzer": "^2.8.2" + "webpack-bundle-analyzer": "^2.8.2", + "webpack-stats-plugin": "^0.1.5" }, "devDependencies": { "babel-plugin-istanbul": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 63d4e99cddc..4ffd7ed11fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5259,10 +5259,6 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -stats-webpack-plugin@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/stats-webpack-plugin/-/stats-webpack-plugin-0.4.3.tgz#b2f618202f28dd04ab47d7ecf54ab846137b7aea" - "statuses@>= 1.3.1 < 2", statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -5845,6 +5841,10 @@ webpack-sources@^0.2.3: source-list-map "^1.1.1" source-map "~0.5.3" +webpack-stats-plugin@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9" + webpack@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.6.1.tgz#2e0457f0abb1ac5df3ab106c69c672f236785f07" -- cgit v1.2.1 From 223de8b4427304d5d37b5b268e4e8f7f42e886bf Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 20 Jun 2017 01:54:39 -0500 Subject: upgrade webpack to v3.0.0 --- package.json | 2 +- yarn.lock | 79 +++++++++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 57fd3735db6..5681464e35f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "vue-loader": "^11.3.4", "vue-resource": "^1.3.4", "vue-template-compiler": "^2.2.6", - "webpack": "^2.6.1", + "webpack": "^3.0.0", "webpack-bundle-analyzer": "^2.8.2", "webpack-stats-plugin": "^0.1.5" }, diff --git a/yarn.lock b/yarn.lock index 4ffd7ed11fb..d20c958ed6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -41,10 +41,14 @@ after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" -ajv-keywords@^1.0.0, ajv-keywords@^1.1.1: +ajv-keywords@^1.0.0: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" +ajv-keywords@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + ajv@^4.7.0: version "4.11.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.2.tgz#f166c3c11cbc6cb9dcc102a5bcfe5b72c95287e6" @@ -52,6 +56,15 @@ ajv@^4.7.0: co "^4.6.0" json-stable-stringify "^1.0.1" +ajv@^5.1.5: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.0.tgz#c1735024c5da2ef75cc190713073d44f098bf486" + dependencies: + co "^4.6.0" + fast-deep-equal "^0.1.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -2236,6 +2249,10 @@ extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +fast-deep-equal@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-0.1.0.tgz#5c6f4599aba6b333ee3342e2ed978672f1001f8d" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -3230,6 +3247,10 @@ json-loader@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" +json-schema-traverse@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.0.tgz#0016c0b1ca1efe46d44d37541bcdfc19dcfae0db" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -3402,7 +3423,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.5: +loader-utils@^0.2.11, loader-utils@^0.2.5: version "0.2.16" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" dependencies: @@ -4683,7 +4704,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.1.0, readable-stream@^2.2.2: +readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" dependencies: @@ -4695,7 +4716,7 @@ readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2. string_decoder "~0.10.x" util-deprecate "~1.0.1" -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.0.6: +readable-stream@^2.0.2, readable-stream@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" dependencies: @@ -5164,9 +5185,9 @@ source-list-map@^0.1.7, source-list-map@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" -source-list-map@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4" +source-list-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" source-map-support@^0.4.2: version "0.4.11" @@ -5542,7 +5563,7 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -uglify-js@^2.6, uglify-js@^2.8.27: +uglify-js@^2.6: version "2.8.27" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c" dependencies: @@ -5551,10 +5572,27 @@ uglify-js@^2.6, uglify-js@^2.8.27: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^2.8.29: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" +uglifyjs-webpack-plugin@^0.4.4: + version "0.4.6" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" + dependencies: + source-map "^0.5.6" + uglify-js "^2.8.29" + webpack-sources "^1.0.1" + uid-number@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -5834,41 +5872,42 @@ webpack-sources@^0.1.0: source-list-map "~0.1.7" source-map "~0.5.3" -webpack-sources@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb" +webpack-sources@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" dependencies: - source-list-map "^1.1.1" + source-list-map "^2.0.0" source-map "~0.5.3" webpack-stats-plugin@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9" -webpack@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.6.1.tgz#2e0457f0abb1ac5df3ab106c69c672f236785f07" +webpack@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.0.0.tgz#ee9bcebf21247f7153cb410168cab45e3a59d4d7" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" - ajv "^4.7.0" - ajv-keywords "^1.1.1" + ajv "^5.1.5" + ajv-keywords "^2.0.0" async "^2.1.2" enhanced-resolve "^3.0.0" + escope "^3.6.0" interpret "^1.0.0" json-loader "^0.5.4" json5 "^0.5.1" loader-runner "^2.3.0" - loader-utils "^0.2.16" + loader-utils "^1.1.0" memory-fs "~0.4.1" mkdirp "~0.5.0" node-libs-browser "^2.0.0" source-map "^0.5.3" supports-color "^3.1.0" tapable "~0.2.5" - uglify-js "^2.8.27" + uglifyjs-webpack-plugin "^0.4.4" watchpack "^1.3.1" - webpack-sources "^0.2.3" + webpack-sources "^1.0.1" yargs "^6.0.0" websocket-driver@>=0.3.6, websocket-driver@>=0.5.1: -- cgit v1.2.1 From df6682b66db60da62ea40d9b043a0f1634af7182 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Jul 2017 12:35:08 -0500 Subject: update babel-loader to v7.1.x --- package.json | 2 +- yarn.lock | 52 +++++++++++++++++++++++++++------------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 5681464e35f..eed90fa2522 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dependencies": { "babel-core": "^6.22.1", "babel-eslint": "^7.2.1", - "babel-loader": "^6.2.10", + "babel-loader": "^7.1.1", "babel-plugin-transform-define": "^1.2.0", "babel-preset-latest": "^6.24.0", "babel-preset-stage-2": "^6.22.0", diff --git a/yarn.lock b/yarn.lock index d20c958ed6a..afa414a608d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -418,14 +418,13 @@ babel-helpers@^6.23.0: babel-runtime "^6.22.0" babel-template "^6.23.0" -babel-loader@^6.2.10: - version "6.2.10" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.2.10.tgz#adefc2b242320cd5d15e65b31cea0e8b1b02d4b0" +babel-loader@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.1.tgz#b87134c8b12e3e4c2a94e0546085bc680a2b8488" dependencies: - find-cache-dir "^0.1.1" - loader-utils "^0.2.11" + find-cache-dir "^1.0.0" + loader-utils "^1.0.2" mkdirp "^0.5.1" - object-assign "^4.0.1" babel-messages@^6.23.0: version "6.23.0" @@ -2340,13 +2339,13 @@ finalhandler@1.0.3, finalhandler@~1.0.3: statuses "~1.3.1" unpipe "~1.0.0" -find-cache-dir@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" +find-cache-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" dependencies: commondir "^1.0.1" - mkdirp "^0.5.1" - pkg-dir "^1.0.0" + make-dir "^1.0.0" + pkg-dir "^2.0.0" find-root@^0.1.1: version "0.1.2" @@ -3423,7 +3422,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.11, loader-utils@^0.2.5: +loader-utils@^0.2.5: version "0.2.16" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" dependencies: @@ -3634,6 +3633,12 @@ macaddress@^0.2.8: version "0.2.8" resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" +make-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" + dependencies: + pify "^2.3.0" + map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" @@ -4202,7 +4207,7 @@ pbkdf2@^3.0.3: dependencies: create-hmac "^1.1.2" -pify@^2.0.0: +pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -4228,6 +4233,12 @@ pkg-dir@^1.0.0: dependencies: find-up "^1.0.0" +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + pkg-up@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26" @@ -4704,7 +4715,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@^2.2.2: +readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.1.0, readable-stream@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" dependencies: @@ -4716,7 +4727,7 @@ readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2. string_decoder "~0.10.x" util-deprecate "~1.0.1" -readable-stream@^2.0.2, readable-stream@~2.0.6: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" dependencies: @@ -5563,16 +5574,7 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -uglify-js@^2.6: - version "2.8.27" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c" - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" - -uglify-js@^2.8.29: +uglify-js@^2.6, uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: -- cgit v1.2.1 From 145e12b1e2cdb80d1c879ae28a3cbd244b822aed Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Jul 2017 12:43:18 -0500 Subject: update eslint-import-resolver-webpack to v0.8.3 --- package.json | 2 +- yarn.lock | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index eed90fa2522..19ee9e73d34 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "babel-plugin-istanbul": "^4.0.0", "eslint": "^3.10.1", "eslint-config-airbnb-base": "^10.0.1", - "eslint-import-resolver-webpack": "^0.8.1", + "eslint-import-resolver-webpack": "^0.8.3", "eslint-plugin-filenames": "^1.1.0", "eslint-plugin-import": "^2.2.0", "eslint-plugin-jasmine": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index afa414a608d..93793bf4fbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1585,6 +1585,12 @@ debug@^2.1.0, debug@^2.1.1, debug@^2.2.0: dependencies: ms "0.7.2" +debug@^2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1979,12 +1985,12 @@ eslint-import-resolver-node@^0.2.0: object-assign "^4.0.1" resolve "^1.1.6" -eslint-import-resolver-webpack@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.1.tgz#c7f8b4d5bd3c5b489457e5728c5db1c4ffbac9aa" +eslint-import-resolver-webpack@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.3.tgz#ad61e28df378a474459d953f246fd43f92675385" dependencies: array-find "^1.0.0" - debug "^2.2.0" + debug "^2.6.8" enhanced-resolve "~0.9.0" find-root "^0.1.1" has "^1.0.1" @@ -4933,11 +4939,11 @@ resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" -resolve@1.1.x: +resolve@1.1.x, resolve@^1.1.6: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6, resolve@^1.2.0: +resolve@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c" -- cgit v1.2.1 From 8a4400276065c8913db7e2eb6c9c8dd4e238acfc Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Jul 2017 12:45:13 -0500 Subject: update karma-webpack to v2.0.4 --- package.json | 2 +- yarn.lock | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 19ee9e73d34..ea0a9fcb8ec 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "karma-jasmine": "^1.1.0", "karma-mocha-reporter": "^2.2.2", "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^2.0.2", + "karma-webpack": "^2.0.4", "nodemon": "^1.11.0", "webpack-dev-server": "^2.4.2" } diff --git a/yarn.lock b/yarn.lock index 93793bf4fbb..fef76cdacaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1579,18 +1579,18 @@ debug@2.6.7: dependencies: ms "2.0.0" -debug@^2.1.0, debug@^2.1.1, debug@^2.2.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" - dependencies: - ms "0.7.2" - -debug@^2.6.8: +debug@^2.1.0, debug@^2.2.0, debug@^2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: ms "2.0.0" +debug@^2.1.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" + dependencies: + ms "0.7.2" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -3337,9 +3337,9 @@ karma-sourcemap-loader@^0.3.7: dependencies: graceful-fs "^4.1.2" -karma-webpack@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.2.tgz#bd38350af5645c9644090770939ebe7ce726f864" +karma-webpack@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.4.tgz#3e2d4f48ba94a878e1c66bb8e1ae6128987a175b" dependencies: async "~0.9.0" loader-utils "^0.2.5" @@ -4939,11 +4939,11 @@ resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" -resolve@1.1.x, resolve@^1.1.6: +resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.2.0: +resolve@^1.1.6, resolve@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c" -- cgit v1.2.1 From 937bbac91126fb4221624eb1409534ddc03e3152 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Jul 2017 12:50:18 -0500 Subject: update webpack-dev-server to v2.6.1 [ci skip] --- package.json | 2 +- yarn.lock | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 228 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index ea0a9fcb8ec..c92eb10d25f 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,6 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.4", "nodemon": "^1.11.0", - "webpack-dev-server": "^2.4.2" + "webpack-dev-server": "^2.6.1" } } diff --git a/yarn.lock b/yarn.lock index fef76cdacaf..6a6669d50e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -141,6 +141,10 @@ arr-flatten@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b" +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + array-find@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8" @@ -149,6 +153,10 @@ array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" +array-flatten@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + array-slice@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" @@ -922,6 +930,17 @@ body-parser@^1.16.1: raw-body "~2.2.0" type-is "~1.6.15" +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -1015,6 +1034,10 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" +buffer-indexof@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" + buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" @@ -1061,10 +1084,21 @@ callsites@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" @@ -1533,6 +1567,12 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" @@ -1579,7 +1619,7 @@ debug@2.6.7: dependencies: ms "2.0.0" -debug@^2.1.0, debug@^2.2.0, debug@^2.6.8: +debug@^2.1.0, debug@^2.2.0, debug@^2.6.6, debug@^2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: @@ -1605,6 +1645,10 @@ decompress-response@^3.2.0: dependencies: mimic-response "^1.0.0" +deep-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + deep-extend@~0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" @@ -1641,6 +1685,17 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + dependencies: + globby "^6.1.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + p-map "^1.1.1" + pify "^3.0.0" + rimraf "^2.2.8" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1686,6 +1741,23 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + doctrine@1.5.0, doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -2483,6 +2555,10 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -2542,6 +2618,16 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + good-listener@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" @@ -2802,6 +2888,12 @@ imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -2851,6 +2943,12 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" +internal-ip@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + dependencies: + meow "^3.3.0" + interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" @@ -2865,6 +2963,10 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" +ip@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + ipaddr.js@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" @@ -3604,6 +3706,10 @@ log4js@^0.6.31: readable-stream "~1.0.2" semver "~4.3.3" +loglevel@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd" + longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -3614,6 +3720,13 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" @@ -3645,6 +3758,10 @@ make-dir@^1.0.0: dependencies: pify "^2.3.0" +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" @@ -3672,6 +3789,21 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +meow@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -3705,14 +3837,14 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.24.0 < 2", mime-db@~1.26.0: - version "1.26.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" - -mime-db@~1.27.0: +"mime-db@>= 1.24.0 < 2", mime-db@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" +mime-db@~1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" + mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.7: version "2.1.15" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" @@ -3747,7 +3879,7 @@ minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.2.0: +minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -3777,6 +3909,17 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.1.1.tgz#6e7de86a570872ab17058adea7160bbeca814dde" + dependencies: + dns-packet "^1.0.1" + thunky "^0.1.0" + mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" @@ -3803,6 +3946,10 @@ nested-error-stacks@^1.0.0: dependencies: inherits "~2.0.1" +node-forge@0.6.33: + version "0.6.33" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" + node-libs-browser@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-1.1.1.tgz#2a38243abedd7dffcd07a97c9aca5668975a6fea" @@ -3909,7 +4056,7 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.3.2: +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.3.5" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df" dependencies: @@ -4095,6 +4242,10 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-map@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" + p-timeout@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.0.tgz#9820f99434c5817868b4f34809ee5291660d5b6c" @@ -4217,6 +4368,10 @@ pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + pikaday@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.5.1.tgz#0a48549bc1a14ea1d08c44074d761bc2f2bfcfd3" @@ -4636,6 +4791,10 @@ querystringify@0.0.x: version "0.0.4" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c" +querystringify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" + randomatic@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb" @@ -4794,6 +4953,13 @@ recursive-readdir@2.1.1: dependencies: minimatch "3.0.3" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + reduce-css-calc@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" @@ -5006,6 +5172,12 @@ select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" +selfsigned@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.9.1.tgz#cdda4492d70d486570f87c65546023558e1dfa5a" + dependencies: + node-forge "0.6.33" + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -5174,16 +5346,16 @@ sockjs-client@1.0.1: json3 "^3.3.2" url-parse "^1.0.1" -sockjs-client@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.2.tgz#f0212a8550e4c9468c8cceaeefd2e3493c033ad5" +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" dependencies: - debug "^2.2.0" + debug "^2.6.6" eventsource "0.1.6" faye-websocket "~0.11.0" inherits "^2.0.1" json3 "^3.3.2" - url-parse "^1.1.1" + url-parse "^1.1.8" sockjs@0.3.18: version "0.3.18" @@ -5377,6 +5549,12 @@ strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + strip-json-comments@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" @@ -5481,6 +5659,10 @@ through@2, through@^2.3.6, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +thunky@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" + timeago.js@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-2.0.5.tgz#730c74fbdb0b0917a553675a4460e3a7f80db86c" @@ -5543,6 +5725,10 @@ traverse@0.6.6: version "0.6.6" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" @@ -5675,13 +5861,20 @@ url-parse@1.0.x: querystringify "0.0.x" requires-port "1.0.x" -url-parse@^1.0.1, url-parse@^1.1.1: +url-parse@^1.0.1: version "1.1.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.7.tgz#025cff999653a459ab34232147d89514cc87d74a" dependencies: querystringify "0.0.x" requires-port "1.0.x" +url-parse@^1.1.8: + version "1.1.9" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19" + dependencies: + querystringify "~1.0.0" + requires-port "1.0.x" + url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" @@ -5842,7 +6035,7 @@ webpack-bundle-analyzer@^2.8.2: opener "^1.4.3" ws "^2.3.1" -webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.9.0: +webpack-dev-middleware@^1.0.11: version "1.10.0" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.0.tgz#7d5be2651e692fddfafd8aaed177c16ff51f0eb8" dependencies: @@ -5851,26 +6044,40 @@ webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.9.0: path-is-absolute "^1.0.0" range-parser "^1.0.3" -webpack-dev-server@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.4.2.tgz#cf595d6b40878452b6d2ad7229056b686f8a16be" +webpack-dev-middleware@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9" + dependencies: + memory-fs "~0.4.1" + mime "^1.3.4" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + +webpack-dev-server@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.6.1.tgz#0b292a9da96daf80a65988f69f87b4166e5defe7" dependencies: ansi-html "0.0.7" + bonjour "^3.5.0" chokidar "^1.6.0" compression "^1.5.2" connect-history-api-fallback "^1.3.0" + del "^3.0.0" express "^4.13.3" html-entities "^1.2.0" http-proxy-middleware "~0.17.4" + internal-ip "^1.2.0" + loglevel "^1.4.1" opn "4.0.2" portfinder "^1.0.9" + selfsigned "^1.9.1" serve-index "^1.7.2" sockjs "0.3.18" - sockjs-client "1.1.2" + sockjs-client "1.1.4" spdy "^3.4.1" strip-ansi "^3.0.0" supports-color "^3.1.1" - webpack-dev-middleware "^1.9.0" + webpack-dev-middleware "^1.11.0" yargs "^6.0.0" webpack-sources@^0.1.0: -- cgit v1.2.1 From 83bea6e9b80a5cd0817be12b9a0ac329033904df Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Jul 2017 13:00:31 -0500 Subject: add disableHostCheck to devServer config since webpack dev server is proxied --- config/webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config/webpack.config.js b/config/webpack.config.js index 100fdc31cbb..cad76a5618c 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -257,6 +257,7 @@ if (IS_DEV_SERVER) { config.devServer = { host: DEV_SERVER_HOST, port: DEV_SERVER_PORT, + disableHostCheck: true, headers: { 'Access-Control-Allow-Origin': '*' }, stats: 'errors-only', hot: DEV_SERVER_LIVERELOAD, -- cgit v1.2.1 From e4937c69f21dd670eca48c9f4b89d8fa36d75428 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Jul 2017 13:23:50 -0500 Subject: dedupe webpack-dev-middleware --- yarn.lock | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6a6669d50e1..75df87afca9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6035,16 +6035,7 @@ webpack-bundle-analyzer@^2.8.2: opener "^1.4.3" ws "^2.3.1" -webpack-dev-middleware@^1.0.11: - version "1.10.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.0.tgz#7d5be2651e692fddfafd8aaed177c16ff51f0eb8" - dependencies: - memory-fs "~0.4.1" - mime "^1.3.4" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - -webpack-dev-middleware@^1.11.0: +webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9" dependencies: -- cgit v1.2.1 From 489c3bccd13584f2f6a64bded1f9b34b5df68afc Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Jul 2017 14:06:09 -0500 Subject: upgrade webpack to v3.1.0 --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index c92eb10d25f..32d4155fe26 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "vue-loader": "^11.3.4", "vue-resource": "^1.3.4", "vue-template-compiler": "^2.2.6", - "webpack": "^3.0.0", + "webpack": "^3.1.0", "webpack-bundle-analyzer": "^2.8.2", "webpack-stats-plugin": "^0.1.5" }, diff --git a/yarn.lock b/yarn.lock index 75df87afca9..d44234b2513 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1924,9 +1924,9 @@ engine.io@1.8.3: engine.io-parser "1.3.2" ws "1.1.2" -enhanced-resolve@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz#9f4b626f577245edcf4b2ad83d86e17f4f421dec" +enhanced-resolve@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz#950964ecc7f0332a42321b673b38dc8ff15535b3" dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0" @@ -5779,7 +5779,7 @@ uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" -uglifyjs-webpack-plugin@^0.4.4: +uglifyjs-webpack-plugin@^0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" dependencies: @@ -6089,16 +6089,16 @@ webpack-stats-plugin@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9" -webpack@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.0.0.tgz#ee9bcebf21247f7153cb410168cab45e3a59d4d7" +webpack@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.1.0.tgz#ac0675e500db835f9ab2369d29ba096f51ad0731" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" ajv "^5.1.5" ajv-keywords "^2.0.0" async "^2.1.2" - enhanced-resolve "^3.0.0" + enhanced-resolve "^3.3.0" escope "^3.6.0" interpret "^1.0.0" json-loader "^0.5.4" @@ -6111,7 +6111,7 @@ webpack@^3.0.0: source-map "^0.5.3" supports-color "^3.1.0" tapable "~0.2.5" - uglifyjs-webpack-plugin "^0.4.4" + uglifyjs-webpack-plugin "^0.4.6" watchpack "^1.3.1" webpack-sources "^1.0.1" yargs "^6.0.0" -- cgit v1.2.1 From 031d81ccb771bf8f616b7cd6a32376b7538d3f47 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 25 Jul 2017 15:11:51 -0500 Subject: upgrade webpack to v3.4.0 --- package.json | 2 +- yarn.lock | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 172 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 32d4155fe26..b50efee72ff 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "vue-loader": "^11.3.4", "vue-resource": "^1.3.4", "vue-template-compiler": "^2.2.6", - "webpack": "^3.1.0", + "webpack": "^3.4.0", "webpack-bundle-analyzer": "^2.8.2", "webpack-stats-plugin": "^0.1.5" }, diff --git a/yarn.lock b/yarn.lock index d44234b2513..3eda2787bca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1103,6 +1103,10 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + caniuse-api@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" @@ -1152,6 +1156,21 @@ chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0: optionalDependencies: fsevents "^1.0.0" +chokidar@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + cipher-base@^1.0.0, cipher-base@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07" @@ -1461,6 +1480,14 @@ cropper@^2.3.0: dependencies: jquery ">= 1.9.1" +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -1924,14 +1951,14 @@ engine.io@1.8.3: engine.io-parser "1.3.2" ws "1.1.2" -enhanced-resolve@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz#950964ecc7f0332a42321b673b38dc8ff15535b3" +enhanced-resolve@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0" object-assign "^4.0.1" - tapable "^0.2.5" + tapable "^0.2.7" enhanced-resolve@~0.9.0: version "0.9.1" @@ -2241,6 +2268,18 @@ evp_bytestokey@^1.0.0: dependencies: create-hash "^1.1.1" +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" @@ -2436,7 +2475,7 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -find-up@^2.1.0: +find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" dependencies: @@ -2725,6 +2764,10 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + has-symbol-support-x@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8" @@ -3131,7 +3174,7 @@ is-retry-allowed@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" -is-stream@^1.0.0: +is-stream@^1.0.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3526,6 +3569,15 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" @@ -3778,6 +3830,12 @@ media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + memory-fs@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" @@ -3861,6 +3919,10 @@ mime@1.3.4, mime@1.3.x, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + mimic-response@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" @@ -4082,6 +4144,12 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + npmlog@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f" @@ -4213,6 +4281,14 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -4336,6 +4412,10 @@ path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -4352,6 +4432,12 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + pause-stream@0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" @@ -4872,6 +4958,13 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -4880,6 +4973,14 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.1.0, readable-stream@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" @@ -5257,6 +5358,16 @@ sha.js@^2.3.6: dependencies: inherits "^2.0.1" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + shelljs@^0.7.5: version "0.7.6" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad" @@ -5549,6 +5660,10 @@ strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -5577,6 +5692,12 @@ supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-co dependencies: has-flag "^1.0.0" +supports-color@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" + dependencies: + has-flag "^2.0.0" + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -5604,9 +5725,9 @@ tapable@^0.1.8: version "0.1.10" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" -tapable@^0.2.5, tapable@~0.2.5: - version "0.2.6" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" +tapable@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c" tar-pack@~3.3.0: version "3.3.0" @@ -6005,12 +6126,12 @@ vue@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/vue/-/vue-2.2.6.tgz#451714b394dd6d4eae7b773c40c2034a59621aed" -watchpack@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87" +watchpack@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" dependencies: async "^2.1.2" - chokidar "^1.4.3" + chokidar "^1.7.0" graceful-fs "^4.1.2" wbuf@^1.1.0, wbuf@^1.4.0: @@ -6089,16 +6210,16 @@ webpack-stats-plugin@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9" -webpack@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.1.0.tgz#ac0675e500db835f9ab2369d29ba096f51ad0731" +webpack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.0.tgz#e9465b660ad79dd2d33874d968b31746ea9a8e63" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" ajv "^5.1.5" ajv-keywords "^2.0.0" async "^2.1.2" - enhanced-resolve "^3.3.0" + enhanced-resolve "^3.4.0" escope "^3.6.0" interpret "^1.0.0" json-loader "^0.5.4" @@ -6109,12 +6230,12 @@ webpack@^3.1.0: mkdirp "~0.5.0" node-libs-browser "^2.0.0" source-map "^0.5.3" - supports-color "^3.1.0" - tapable "~0.2.5" + supports-color "^4.2.1" + tapable "^0.2.7" uglifyjs-webpack-plugin "^0.4.6" - watchpack "^1.3.1" + watchpack "^1.4.0" webpack-sources "^1.0.1" - yargs "^6.0.0" + yargs "^8.0.2" websocket-driver@>=0.3.6, websocket-driver@>=0.5.1: version "0.6.5" @@ -6134,7 +6255,11 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" -which@^1.1.1, which@^1.2.1: +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which@^1.1.1, which@^1.2.1, which@^1.2.9: version "1.2.12" resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" dependencies: @@ -6233,6 +6358,12 @@ yargs-parser@^4.2.0: dependencies: camelcase "^3.0.0" +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + yargs@^6.0.0: version "6.6.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" @@ -6251,6 +6382,24 @@ yargs@^6.0.0: y18n "^3.2.1" yargs-parser "^4.2.0" +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" -- cgit v1.2.1 From 6ff1ad2f63cbbc9f9ce421d6b756d9a8bc7b72fb Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 5 Aug 2017 23:54:32 -0500 Subject: dedupe yarn dependencies --- yarn.lock | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3eda2787bca..b1cd07a8e9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -845,11 +845,7 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23 lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@^6.11.0: - version "6.15.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" - -babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1: +babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0, babylon@^6.16.1: version "6.16.1" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" @@ -1646,18 +1642,12 @@ debug@2.6.7: dependencies: ms "2.0.0" -debug@^2.1.0, debug@^2.2.0, debug@^2.6.6, debug@^2.6.8: +debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.6.6, debug@^2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: ms "2.0.0" -debug@^2.1.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" - dependencies: - ms "0.7.2" - decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -3899,22 +3889,12 @@ miller-rabin@^4.0.0: version "1.27.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" -mime-db@~1.26.0: - version "1.26.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" - -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.7: +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: version "2.1.15" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" dependencies: mime-db "~1.27.0" -mime-types@~2.1.11: - version "2.1.14" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee" - dependencies: - mime-db "~1.26.0" - mime@1.3.4, mime@1.3.x, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" -- cgit v1.2.1 From 669ff236dc5b4bfa5916c9406231f3e8cfc2f8c2 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 7 Aug 2017 00:58:12 -0500 Subject: add thunky to approved licenses (MIT) --- config/dependency_decisions.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml index 59c7050a14d..ca5b941aebf 100644 --- a/config/dependency_decisions.yml +++ b/config/dependency_decisions.yml @@ -398,3 +398,9 @@ :why: https://github.com/remy/undefsafe/blob/master/LICENSE :versions: [] :when: 2017-04-10 06:30:00.002555000 Z +- - :approve + - thunky + - :who: Mike Greiling + :why: https://github.com/mafintosh/thunky/blob/master/README.md#license + :versions: [] + :when: 2017-08-07 05:56:09.907045000 Z -- cgit v1.2.1 From ee3d8e93403ab04ccd6cc031d2b0454702322eb0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 7 Aug 2017 13:52:28 -0300 Subject: Import GitHub releases --- lib/github/import.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/github/import.rb b/lib/github/import.rb index 9c61d44e023..8c4a5e0875c 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -68,6 +68,8 @@ module Github fetch_pull_requests puts 'Fetching issues...'.color(:aqua) if verbose fetch_issues + puts 'Fetching releases...'.color(:aqua) if verbose + fetch_releases puts 'Cloning wiki repository...'.color(:aqua) if verbose fetch_wiki_repository puts 'Expiring repository cache...'.color(:aqua) if verbose -- cgit v1.2.1 From 68f568d9dc53975c44ab00cf320fa6407146d1e3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 7 Aug 2017 13:53:08 -0300 Subject: Fix small typo on Github::Import#fetch_releases --- lib/github/import.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/import.rb b/lib/github/import.rb index 8c4a5e0875c..611ea35964e 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -313,7 +313,7 @@ module Github next unless representation.valid? release = ::Release.find_or_initialize_by(project_id: project.id, tag: representation.tag) - next unless relese.new_record? + next unless release.new_record? begin release.description = representation.description -- cgit v1.2.1 From c4f55ce32d6160b43c518954219cca69077b5001 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Wed, 2 Aug 2017 20:59:44 +0200 Subject: Migrate Gitlab::Git::Repository#size to Gitaly Closes gitaly#437 --- GITALY_SERVER_VERSION | 2 +- Gemfile | 2 +- Gemfile.lock | 4 ++-- lib/gitlab/git/repository.rb | 17 ++++++++++++++++- lib/gitlab/gitaly_client/repository_service.rb | 5 +++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 1b58cc10180..ae6dd4e2032 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.27.0 +0.29.0 diff --git a/Gemfile b/Gemfile index 37a4eb5fde4..6f92326910d 100644 --- a/Gemfile +++ b/Gemfile @@ -391,7 +391,7 @@ gem 'vmstat', '~> 2.3.0' gem 'sys-filesystem', '~> 1.1.6' # Gitaly GRPC client -gem 'gitaly', '~> 0.24.0' +gem 'gitaly', '~> 0.26.0' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 04d17d54636..24ddf36fa44 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -269,7 +269,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly (0.24.0) + gitaly (0.26.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -975,7 +975,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly (~> 0.24.0) + gitaly (~> 0.26.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.5.1) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 1005a819f95..ea01bec2d5a 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -282,7 +282,14 @@ module Gitlab # Return repo size in megabytes def size - size = popen(%w(du -sk), path).first.strip.to_i + size = gitaly_migrate(:repository_size) do |is_enabled| + if is_enabled + size_by_gitaly + else + size_by_shelling_out + end + end + (size.to_f / 1024).round(2) end @@ -942,6 +949,14 @@ module Gitlab gitaly_ref_client.tags end + def size_by_shelling_out + popen(%w(du -sk), path).first.strip.to_i + end + + def size_by_gitaly + gitaly_repository_client.repository_size + end + def count_commits_by_gitaly(options) gitaly_commit_client.commit_count(options[:ref], options) end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 13e75b256a7..79ce784f2f2 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -27,6 +27,11 @@ module Gitlab request = Gitaly::RepackIncrementalRequest.new(repository: @gitaly_repo) GitalyClient.call(@storage, :repository_service, :repack_incremental, request) end + + def repository_size + request = Gitaly::RepositorySizeRequest.new(repository: @gitaly_repo) + GitalyClient.call(@storage, :repository_service, :repository_size, request).size + end end end end -- cgit v1.2.1 From c0b41064ff1eb4c5465d3c2a9efa0a22b60c3f4c Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Thu, 3 Aug 2017 10:22:01 +0200 Subject: Migrate Repository#find_commits_by_message to Gitaly Closes gitaly#443 --- GITALY_SERVER_VERSION | 2 +- Gemfile | 2 +- Gemfile.lock | 4 +-- app/models/repository.rb | 39 +++++++++++++++++++++--------- lib/gitlab/gitaly_client/commit_service.rb | 14 +++++++++++ spec/models/repository_spec.rb | 30 ++++++++++++++++------- 6 files changed, 67 insertions(+), 24 deletions(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 1b58cc10180..ae6dd4e2032 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.27.0 +0.29.0 diff --git a/Gemfile b/Gemfile index 37a4eb5fde4..6f92326910d 100644 --- a/Gemfile +++ b/Gemfile @@ -391,7 +391,7 @@ gem 'vmstat', '~> 2.3.0' gem 'sys-filesystem', '~> 1.1.6' # Gitaly GRPC client -gem 'gitaly', '~> 0.24.0' +gem 'gitaly', '~> 0.26.0' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 04d17d54636..24ddf36fa44 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -269,7 +269,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly (0.24.0) + gitaly (0.26.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -975,7 +975,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly (~> 0.24.0) + gitaly (~> 0.26.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.5.1) diff --git a/app/models/repository.rb b/app/models/repository.rb index f86a0869b01..3b5d0e00c70 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -130,17 +130,13 @@ class Repository return [] end - ref ||= root_ref - - args = %W( - log #{ref} --pretty=%H --skip #{offset} - --max-count #{limit} --grep=#{query} --regexp-ignore-case - ) - args = args.concat(%W(-- #{path})) if path.present? - - git_log_results = run_git(args).first.lines - - git_log_results.map { |c| commit(c.chomp) }.compact + raw_repository.gitaly_migrate(:commits_by_message) do |is_enabled| + if is_enabled + find_commits_by_message_by_gitaly(query, ref, path, limit, offset) + else + find_commits_by_message_by_shelling_out(query, ref, path, limit, offset) + end + end end def find_branch(name, fresh_repo: true) @@ -1184,4 +1180,25 @@ class Repository def circuit_breaker @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(project.repository_storage) end + + def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset) + ref ||= root_ref + + args = %W( + log #{ref} --pretty=%H --skip #{offset} + --max-count #{limit} --grep=#{query} --regexp-ignore-case + ) + args = args.concat(%W(-- #{path})) if path.present? + + git_log_results = run_git(args).first.lines + + git_log_results.map { |c| commit(c.chomp) }.compact + end + + def find_commits_by_message_by_gitaly(query, ref, path, limit, offset) + raw_repository + .gitaly_commit_client + .commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset) + .map { |c| commit(c) } + end end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index ac6817e6d0e..3f577ac8530 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -135,6 +135,20 @@ module Gitlab consume_commits_response(response) end + def commits_by_message(query, revision: '', path: '', limit: 1000, offset: 0) + request = Gitaly::CommitsByMessageRequest.new( + repository: @gitaly_repo, + query: query, + revision: revision.to_s.force_encoding(Encoding::ASCII_8BIT), + path: path.to_s.force_encoding(Encoding::ASCII_8BIT), + limit: limit.to_i, + offset: offset.to_i + ) + + response = GitalyClient.call(@repository.storage, :commit_service, :commits_by_message, request) + consume_commits_response(response) + end + def languages(ref = nil) request = Gitaly::CommitLanguagesRequest.new(repository: @gitaly_repo, revision: ref || '') response = GitalyClient.call(@repository.storage, :commit_service, :commit_languages, request) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4ddda5b638c..cfa77648338 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -234,19 +234,31 @@ describe Repository, models: true do 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) + shared_examples 'finding commits by message' do + it 'returns commits with messages containing a given string' do + commit_ids = repository.find_commits_by_message('submodule').map(&:id) - expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') - expect(commit_ids).to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') - expect(commit_ids).to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') - expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') + expect(commit_ids).to include( + '5937ac0a7beb003549fc5fd26fc247adbce4a52e', + '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', + 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' + ) + expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') + end + + it 'is case insensitive' do + commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id) + + expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + end end - it 'is case insensitive' do - commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id) + context 'when Gitaly commits_by_message feature is enabled' do + it_behaves_like 'finding commits by message' + end - expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + context 'when Gitaly commits_by_message feature is disabled', skip_gitaly_mock: true do + it_behaves_like 'finding commits by message' end describe 'when storage is broken', broken_storage: true do -- cgit v1.2.1 From 1e39b51a8a32ea469838fc473d41c8b04c2f935a Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 7 Aug 2017 11:58:19 -0500 Subject: remove sidebar height management from wikis sidebar --- app/assets/javascripts/dispatcher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 178e72a1127..3b36e5de95a 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -580,7 +580,6 @@ import GpgBadges from './gpg_badges'; shortcut_handler = new ShortcutsWiki(); new ZenMode(); new gl.GLForm($('.wiki-form'), true); - new Sidebar(); break; case 'snippets': shortcut_handler = new ShortcutsNavigation(); -- cgit v1.2.1 From 0e191b4ccd6ec8aa22b9d2244a3793daa254bc05 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 7 Aug 2017 14:11:38 -0300 Subject: Allow users to disable SSL verification if not connecting to github.com --- lib/github/client.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/github/client.rb b/lib/github/client.rb index 7d77f1a91bc..9c476df7d46 100644 --- a/lib/github/client.rb +++ b/lib/github/client.rb @@ -10,6 +10,7 @@ module Github faraday.options.timeout = options.fetch(:timeout, TIMEOUT) faraday.authorization 'token', options.fetch(:token) faraday.adapter :net_http + faraday.ssl.verify = verify_ssl end @rate_limit = RateLimit.new(connection) @@ -29,14 +30,24 @@ module Github end def custom_endpoint - Gitlab.config.omniauth.providers - .find { |provider| provider.name == 'github' } - .to_h - .dig('args', 'client_options', 'site') + github_omniauth_provider.dig('args', 'client_options', 'site') + end + + def verify_ssl + # If there is no config, we're connecting to github.com + # and we should verify ssl. + github_omniauth_provider.fetch('verify_ssl', true) end def github_endpoint OmniAuth::Strategies::GitHub.default_options[:client_options][:site] end + + def github_omniauth_provider + @github_omniauth_provider ||= + Gitlab.config.omniauth.providers + .find { |provider| provider.name == 'github' } + .to_h + end end end -- cgit v1.2.1 From 746f0ec367a82d83b07d8972fd7043cc10baba23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Mon, 7 Aug 2017 17:13:02 +0000 Subject: Add sidekiq metrics endpoint and add http server to sidekiq --- .../pawel-add-sidekiq-metrics-endpoint-32145.yml | 4 + config/gitlab.yml.example | 6 ++ config/initializers/1_settings.rb | 4 + config/initializers/7_prometheus_metrics.rb | 6 ++ lib/gitlab/daemon.rb | 62 +++++++++++++ lib/gitlab/metrics/base_sampler.rb | 75 +++++---------- lib/gitlab/metrics/sidekiq_metrics_exporter.rb | 39 ++++++++ spec/lib/gitlab/daemon_spec.rb | 103 +++++++++++++++++++++ spec/lib/gitlab/metrics/influx_sampler_spec.rb | 2 +- .../metrics/sidekiq_metrics_exporter_spec.rb | 101 ++++++++++++++++++++ 10 files changed, 348 insertions(+), 54 deletions(-) create mode 100644 changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml create mode 100644 lib/gitlab/daemon.rb create mode 100644 lib/gitlab/metrics/sidekiq_metrics_exporter.rb create mode 100644 spec/lib/gitlab/daemon_spec.rb create mode 100644 spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb diff --git a/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml b/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml new file mode 100644 index 00000000000..71eabdc16d2 --- /dev/null +++ b/changelogs/unreleased/pawel-add-sidekiq-metrics-endpoint-32145.yml @@ -0,0 +1,4 @@ +--- +title: Add Prometheus metrics exporter to Sidekiq +merge_request: 13082 +author: diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 45ab4e1a851..e73db08fcac 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -590,6 +590,12 @@ production: &base ip_whitelist: - 127.0.0.0/8 + # Sidekiq exporter is webserver built in to Sidekiq to expose Prometheus metrics + sidekiq_exporter: + # enabled: true + # address: localhost + # port: 3807 + # # 5. Extra customization # ========================== diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 7a43bf939ea..e24cf33adb5 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -524,6 +524,10 @@ Settings.webpack.dev_server['port'] ||= 3808 Settings['monitoring'] ||= Settingslogic.new({}) Settings.monitoring['ip_whitelist'] ||= ['127.0.0.1/8'] Settings.monitoring['unicorn_sampler_interval'] ||= 10 +Settings.monitoring['sidekiq_exporter'] ||= Settingslogic.new({}) +Settings.monitoring.sidekiq_exporter['enabled'] ||= false +Settings.monitoring.sidekiq_exporter['address'] ||= 'localhost' +Settings.monitoring.sidekiq_exporter['port'] ||= 3807 # # Testing settings diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb index a2f8421f5d7..54c797e0714 100644 --- a/config/initializers/7_prometheus_metrics.rb +++ b/config/initializers/7_prometheus_metrics.rb @@ -10,3 +10,9 @@ Prometheus::Client.configure do |config| config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir') end end + +Sidekiq.configure_server do |config| + config.on(:startup) do + Gitlab::Metrics::SidekiqMetricsExporter.instance.start + end +end diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb new file mode 100644 index 00000000000..dfd17e35707 --- /dev/null +++ b/lib/gitlab/daemon.rb @@ -0,0 +1,62 @@ +module Gitlab + class Daemon + def self.initialize_instance(*args) + raise "#{name} singleton instance already initialized" if @instance + @instance = new(*args) + Kernel.at_exit(&@instance.method(:stop)) + @instance + end + + def self.instance + @instance ||= initialize_instance + end + + attr_reader :thread + + def thread? + !thread.nil? + end + + def initialize + @mutex = Mutex.new + end + + def enabled? + true + end + + def start + return unless enabled? + + @mutex.synchronize do + return thread if thread? + + @thread = Thread.new { start_working } + end + end + + def stop + @mutex.synchronize do + return unless thread? + + stop_working + + if thread + thread.wakeup if thread.alive? + thread.join + @thread = nil + end + end + end + + private + + def start_working + raise NotImplementedError + end + + def stop_working + # no-ops + end + end +end diff --git a/lib/gitlab/metrics/base_sampler.rb b/lib/gitlab/metrics/base_sampler.rb index 219accfc029..716d20bb91a 100644 --- a/lib/gitlab/metrics/base_sampler.rb +++ b/lib/gitlab/metrics/base_sampler.rb @@ -1,20 +1,7 @@ require 'logger' module Gitlab module Metrics - class BaseSampler - def self.initialize_instance(*args) - raise "#{name} singleton instance already initialized" if @instance - @instance = new(*args) - at_exit(&@instance.method(:stop)) - @instance - end - - def self.instance - @instance - end - - attr_reader :running - + class BaseSampler < Daemon # interval - The sampling interval in seconds. def initialize(interval) interval_half = interval.to_f / 2 @@ -22,44 +9,7 @@ module Gitlab @interval = interval @interval_steps = (-interval_half..interval_half).step(0.1).to_a - @mutex = Mutex.new - end - - def enabled? - true - end - - def start - return unless enabled? - - @mutex.synchronize do - return if running - @running = true - - @thread = Thread.new do - sleep(sleep_interval) - - while running - safe_sample - - sleep(sleep_interval) - end - end - end - end - - def stop - @mutex.synchronize do - return unless running - - @running = false - - if @thread - @thread.wakeup if @thread.alive? - @thread.join - @thread = nil - end - end + super() end def safe_sample @@ -81,7 +31,7 @@ module Gitlab # potentially missing anything that happens in between samples). # 2. Don't sample data at the same interval two times in a row. def sleep_interval - while step = @interval_steps.sample + while (step = @interval_steps.sample) if step != @last_step @last_step = step @@ -89,6 +39,25 @@ module Gitlab end end end + + private + + attr_reader :running + + def start_working + @running = true + sleep(sleep_interval) + + while running + safe_sample + + sleep(sleep_interval) + end + end + + def stop_working + @running = false + end end end end diff --git a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb new file mode 100644 index 00000000000..5980a4ded2b --- /dev/null +++ b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb @@ -0,0 +1,39 @@ +require 'webrick' +require 'prometheus/client/rack/exporter' + +module Gitlab + module Metrics + class SidekiqMetricsExporter < Daemon + def enabled? + Gitlab::Metrics.metrics_folder_present? && settings.enabled + end + + def settings + Settings.monitoring.sidekiq_exporter + end + + private + + attr_reader :server + + def start_working + @server = ::WEBrick::HTTPServer.new(Port: settings.port, BindAddress: settings.address) + server.mount "/", Rack::Handler::WEBrick, rack_app + server.start + end + + def stop_working + server.shutdown + @server = nil + end + + def rack_app + Rack::Builder.app do + use Rack::Deflater + use ::Prometheus::Client::Rack::Exporter + run -> (env) { [404, {}, ['']] } + end + end + end + end +end diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb new file mode 100644 index 00000000000..c519984a267 --- /dev/null +++ b/spec/lib/gitlab/daemon_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe Gitlab::Daemon do + subject { described_class.new } + + before do + allow(subject).to receive(:start_working) + allow(subject).to receive(:stop_working) + end + + describe '.instance' do + before do + allow(Kernel).to receive(:at_exit) + end + + after(:each) do + described_class.instance_variable_set(:@instance, nil) + end + + it 'provides instance of Daemon' do + expect(described_class.instance).to be_instance_of(described_class) + end + + it 'subsequent invocations provide the same instance' do + expect(described_class.instance).to eq(described_class.instance) + end + + it 'creates at_exit hook when instance is created' do + expect(described_class.instance).not_to be_nil + + expect(Kernel).to have_received(:at_exit) + end + end + + describe 'when Daemon is enabled' do + before do + allow(subject).to receive(:enabled?).and_return(true) + end + + describe 'when Daemon is stopped' do + describe '#start' do + it 'starts the Daemon' do + expect { subject.start.join }.to change { subject.thread? }.from(false).to(true) + + expect(subject).to have_received(:start_working) + end + end + + describe '#stop' do + it "doesn't shutdown stopped Daemon" do + expect { subject.stop }.not_to change { subject.thread? } + + expect(subject).not_to have_received(:start_working) + end + end + end + + describe 'when Daemon is running' do + before do + subject.start.join + end + + describe '#start' do + it "doesn't start running Daemon" do + expect { subject.start.join }.not_to change { subject.thread? } + + expect(subject).to have_received(:start_working).once + end + end + + describe '#stop' do + it 'shutdowns Daemon' do + expect { subject.stop }.to change { subject.thread? }.from(true).to(false) + + expect(subject).to have_received(:stop_working) + end + end + end + end + + describe 'when Daemon is disabled' do + before do + allow(subject).to receive(:enabled?).and_return(false) + end + + describe '#start' do + it "doesn't start working" do + expect(subject.start).to be_nil + expect { subject.start }.not_to change { subject.thread? } + + expect(subject).not_to have_received(:start_working) + end + end + + describe '#stop' do + it "doesn't stop working" do + expect { subject.stop }.not_to change { subject.thread? } + + expect(subject).not_to have_received(:stop_working) + end + end + end +end diff --git a/spec/lib/gitlab/metrics/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/influx_sampler_spec.rb index 0bc68d64276..999a9536d82 100644 --- a/spec/lib/gitlab/metrics/influx_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/influx_sampler_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::Metrics::InfluxSampler do it 'runs once and gathers a sample at a given interval' do expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice expect(sampler).to receive(:sample).once - expect(sampler).to receive(:running).and_return(false, true, false) + expect(sampler).to receive(:running).and_return(true, false) sampler.start.join end diff --git a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb new file mode 100644 index 00000000000..6721e02fb85 --- /dev/null +++ b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe Gitlab::Metrics::SidekiqMetricsExporter do + let(:exporter) { described_class.new } + let(:server) { double('server') } + + before do + allow(::WEBrick::HTTPServer).to receive(:new).and_return(server) + allow(server).to receive(:mount) + allow(server).to receive(:start) + allow(server).to receive(:shutdown) + end + + describe 'when exporter is enabled' do + before do + allow(Settings.monitoring.sidekiq_exporter).to receive(:enabled).and_return(true) + end + + describe 'when exporter is stopped' do + describe '#start' do + it 'starts the exporter' do + expect { exporter.start.join }.to change { exporter.thread? }.from(false).to(true) + + expect(server).to have_received(:start) + end + + describe 'with custom settings' do + let(:port) { 99999 } + let(:address) { 'sidekiq_exporter_address' } + + before do + allow(Settings.monitoring.sidekiq_exporter).to receive(:port).and_return(port) + allow(Settings.monitoring.sidekiq_exporter).to receive(:address).and_return(address) + end + + it 'starts server with port and address from settings' do + exporter.start.join + + expect(::WEBrick::HTTPServer).to have_received(:new).with( + Port: port, + BindAddress: address + ) + end + end + end + + describe '#stop' do + it "doesn't shutdown stopped server" do + expect { exporter.stop }.not_to change { exporter.thread? } + + expect(server).not_to have_received(:shutdown) + end + end + end + + describe 'when exporter is running' do + before do + exporter.start.join + end + + describe '#start' do + it "doesn't start running server" do + expect { exporter.start.join }.not_to change { exporter.thread? } + + expect(server).to have_received(:start).once + end + end + + describe '#stop' do + it 'shutdowns server' do + expect { exporter.stop }.to change { exporter.thread? }.from(true).to(false) + + expect(server).to have_received(:shutdown) + end + end + end + end + + describe 'when exporter is disabled' do + before do + allow(Settings.monitoring.sidekiq_exporter).to receive(:enabled).and_return(false) + end + + describe '#start' do + it "doesn't start" do + expect(exporter.start).to be_nil + expect { exporter.start }.not_to change { exporter.thread? } + + expect(server).not_to have_received(:start) + end + end + + describe '#stop' do + it "doesn't shutdown" do + expect { exporter.stop }.not_to change { exporter.thread? } + + expect(server).not_to have_received(:shutdown) + end + end + end +end -- cgit v1.2.1 From e198eea90fe8de5770a8baf4028c2b1d30278324 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 7 Aug 2017 14:18:05 -0300 Subject: Change `project.path_with_namespace` to `project.disk_path` --- lib/github/import.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/import.rb b/lib/github/import.rb index 611ea35964e..4cc01593ef4 100644 --- a/lib/github/import.rb +++ b/lib/github/import.rb @@ -100,7 +100,7 @@ module Github def fetch_wiki_repository return if project.wiki.repository_exists? - wiki_path = "#{project.path_with_namespace}.wiki" + wiki_path = "#{project.disk_path}.wiki" gitlab_shell.import_repository(project.repository_storage_path, wiki_path, wiki_url) rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, -- cgit v1.2.1 From bfc529bbd7b40f43f7069b6459ace61323faee4f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 7 Aug 2017 14:21:21 -0300 Subject: Add explanation why we should return early for GitHub importer --- app/services/projects/import_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index cd661166c3b..c3bf0031409 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -34,6 +34,8 @@ module Projects def import_repository raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url) + # We should return early for a GitHub import because the new GitHub + # importer fetch the project repositories for us. return if project.github_import? begin -- cgit v1.2.1 From 625b0bcfba0a3cb1bf4f4a0d5f224c958e73239c Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 7 Aug 2017 12:24:52 -0500 Subject: Remove JS flicker on page load; UI tweaks --- app/assets/javascripts/new_sidebar.js | 6 ++++-- app/assets/stylesheets/new_sidebar.scss | 1 + app/helpers/application_helper.rb | 4 ++++ app/helpers/nav_helper.rb | 1 + app/views/layouts/nav/_new_project_sidebar.html.haml | 4 ++-- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js index 3a3e6b14ec4..930218dd1f5 100644 --- a/app/assets/javascripts/new_sidebar.js +++ b/app/assets/javascripts/new_sidebar.js @@ -45,8 +45,10 @@ export default class NewNavSidebar { toggleCollapsedSidebar(collapsed) { this.$sidebar.toggleClass('sidebar-icons-only', collapsed); - this.$page.toggleClass('page-with-new-sidebar', !collapsed); - this.$page.toggleClass('page-with-icon-sidebar', collapsed); + if (this.$sidebar.length) { + this.$page.toggleClass('page-with-new-sidebar', !collapsed); + this.$page.toggleClass('page-with-icon-sidebar', collapsed); + } NewNavSidebar.setCollapsedCookie(collapsed); } diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 10c9c97df0e..30029a94d9d 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -400,6 +400,7 @@ $new-sidebar-collapsed-width: 50px; .toggle-sidebar-button { width: $new-sidebar-collapsed-width - 2px; + padding: 16px 18px; .collapse-text, .fa-angle-double-left { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 14dc9bd9d62..0cfd7822d05 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -305,4 +305,8 @@ module ApplicationHelper def show_new_nav? cookies["new_nav"] == "true" end + + def collapsed_sidebar? + cookies["sidebar_collapsed"] == "true" + end end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index b1205b8529b..e0f1948ce20 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -2,6 +2,7 @@ module NavHelper def page_with_sidebar_class class_name = page_gutter_class class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar + class_name << 'page-with-icon-sidebar' if collapsed_sidebar? class_name end diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index 4b7209fa69e..df8dfe0c2f7 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar +.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } - can_edit = can?(current_user, :admin_project, @project) .context-header = link_to project_path(@project), title: @project.name do @@ -219,7 +219,7 @@ = link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do .nav-icon-container = custom_icon('members') - %span + %span.nav-item-name Members = render 'shared/sidebar_toggle_button' -- cgit v1.2.1 From 34ca73cae4df069e937cd6c9dfbc445240a9b73a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 7 Aug 2017 12:28:24 -0500 Subject: Check if sidebar exists --- app/helpers/nav_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index e0f1948ce20..b63b3b70903 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -2,7 +2,7 @@ module NavHelper def page_with_sidebar_class class_name = page_gutter_class class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar - class_name << 'page-with-icon-sidebar' if collapsed_sidebar? + class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar class_name end -- cgit v1.2.1 From 036ee515d418b99181d5e610e40a7ab527a8a9ce Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 7 Aug 2017 19:24:40 +0200 Subject: Port form back to use form_tag --- .../import/gitlab_projects_controller.rb | 2 +- app/views/import/gitlab_projects/new.html.haml | 24 ++++++++-------------- .../projects/import_export/import_file_spec.rb | 4 ++-- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 6463d2dfd5b..510813846a4 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -12,7 +12,7 @@ class Import::GitlabProjectsController < Import::BaseController return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) end - @project = ::Projects::GitlabProjectsImporterService.new(current_user, project_params).execute + @project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute if @project.saved? redirect_to( diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index 772625867df..d99da464ad1 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -8,29 +8,25 @@ Import an exported GitLab project %hr -= form_for import_gitlab_project_path, html: { class: 'new_project' }, multipart: true do |f| += form_tag import_gitlab_project_path, html: { class: 'new_project' }, multipart: true do .row .form-group.col-xs-12.col-sm-6 - = f.label :namespace_id, class: 'label-light' do - %span - Project path + .span + = label_tag :namespace_id, 'Project path', class: 'label-light' .form-group .input-group - if current_user.can_select_namespace? .input-group-addon = root_url - = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1} + = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1 - else .input-group-addon.static-namespace #{root_url}#{current_user.username}/ - = f.hidden_field :namespace_id, value: current_user.namespace_id + = hidden_field_tag :namespace_id, value: current_user.namespace_id .form-group.col-xs-12.col-sm-6.project-path - = f.label :path, class: 'label-light' do - %span - Project name - = f.text_field :path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true - + = label_tag :path, 'Project name', class: 'label-light' + = text_field_tag :path, nil, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true .row .form-group.col-md-12 @@ -39,12 +35,10 @@ .form-group.col-sm-12 = hidden_field_tag :namespace_id, @namespace.id = hidden_field_tag :path, @path - = f.label :file, class: 'label-light' do - %span - GitLab project export + = label_tag :file, 'GitLab project export', class: 'label-light' .form-group = file_field_tag :file, class: '' .row .form-actions - = f.submit 'Import project', class: 'btn btn-create' + = submit_tag 'Import project', class: 'btn btn-create' = link_to 'Cancel', new_project_path, class: 'btn btn-cancel' diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 9b4235d9186..83d924382be 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -29,9 +29,9 @@ feature 'Import/Export - project import integration test', js: true do fill_in :project_path, with: 'test-project-path', visible: true click_link 'GitLab export' - expect(page).to have_content('GitLab project export') + expect(page).to have_content('Import an exported GitLab project') expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=test-project-path") - expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A[0-9a-f]{32}_test_project_export\.tar\.gz\z/).and_call_original + expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\z/).and_call_original attach_file('file', file) -- cgit v1.2.1 From ae5003a35ef47bbdafc51e3e9d0f27039f9d20a2 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 7 Aug 2017 18:51:30 +0100 Subject: Fix html structure Removes test for removed behavior --- app/views/import/gitlab_projects/new.html.haml | 5 ++--- spec/features/projects/import_export/import_file_spec.rb | 11 ----------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index d99da464ad1..008e8287aa3 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -8,11 +8,10 @@ Import an exported GitLab project %hr -= form_tag import_gitlab_project_path, html: { class: 'new_project' }, multipart: true do += form_tag import_gitlab_project_path, class: 'new_project', multipart: true do .row .form-group.col-xs-12.col-sm-6 - .span - = label_tag :namespace_id, 'Project path', class: 'label-light' + = label_tag :namespace_id, 'Project path', class: 'label-light' .form-group .input-group - if current_user.can_select_namespace? diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 83d924382be..24e7843db63 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -61,17 +61,6 @@ feature 'Import/Export - project import integration test', js: true do expect(page).to have_content('Project could not be imported') end end - - scenario 'project with no name' do - create(:project, namespace: namespace) - - visit new_project_path - - select2(namespace.id, from: '#project_namespace_id') - - # Check for tooltip disabled import button - expect(find('.import_gitlab_project')['title']).to eq('Please enter a valid project name.') - end end context 'when limited to the default user namespace' do -- cgit v1.2.1 From cc56bfd1cf3c17f2b03fb82e9f25cabc265118ef Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 7 Aug 2017 15:07:13 -0300 Subject: Add CHANGELOG entry --- changelogs/unreleased/github.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/github.yml diff --git a/changelogs/unreleased/github.yml b/changelogs/unreleased/github.yml new file mode 100644 index 00000000000..7f48680f164 --- /dev/null +++ b/changelogs/unreleased/github.yml @@ -0,0 +1,4 @@ +--- +title: Reduce memory usage while importing GitHub projects +merge_request: 12886 +author: -- cgit v1.2.1 From 38704e4247616a3122108b01b61798eed21461c9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 7 Aug 2017 15:11:01 -0300 Subject: [ci skip] Update CHANGELOG --- changelogs/unreleased/github.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/github.yml b/changelogs/unreleased/github.yml index 7f48680f164..585b9b13b65 100644 --- a/changelogs/unreleased/github.yml +++ b/changelogs/unreleased/github.yml @@ -1,4 +1,4 @@ --- -title: Reduce memory usage while importing GitHub projects +title: Reduce memory usage of the GitHub importer merge_request: 12886 author: -- cgit v1.2.1 From df00ebded67e871dfab226db3ffadc3dd6807d79 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 7 Aug 2017 11:54:18 +0100 Subject: DRY up caching in AbstractReferenceFilter We had a lot of copied and pasted code, when the different elements were very small and easy to get wrong. --- lib/banzai/filter/abstract_reference_filter.rb | 60 +++++++++----------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 5db4fe77885..ef4578aabd6 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -54,9 +54,9 @@ module Banzai self.class.references_in(*args, &block) end + # Implement in child class + # Example: project.merge_requests.find def find_object(project, id) - # Implement in child class - # Example: project.merge_requests.find end # Override if the link reference pattern produces a different ID (global @@ -65,47 +65,31 @@ module Banzai find_object(project, id) end - def find_object_cached(project, id) - if RequestStore.active? - cache = find_objects_cache[object_class][project.id] + # Implement in child class + # Example: project_merge_request_url + def url_for_object(object, project) + end - get_or_set_cache(cache, id) { find_object(project, id) } - else + def find_object_cached(project, id) + cached_call(:banzai_find_object, id, path: [object_class, project.id]) do find_object(project, id) end end def find_object_from_link_cached(project, id) - if RequestStore.active? - cache = find_objects_from_link_cache[object_class][project.id] - - get_or_set_cache(cache, id) { find_object_from_link(project, id) } - else + cached_call(:banzai_find_object_from_link, id, path: [object_class, project.id]) do find_object_from_link(project, id) end end def project_from_ref_cached(ref) - if RequestStore.active? - cache = project_refs_cache - - get_or_set_cache(cache, ref) { project_from_ref(ref) } - else + cached_call(:banzai_project_refs, ref) do project_from_ref(ref) end end - def url_for_object(object, project) - # Implement in child class - # Example: project_merge_request_url - end - def url_for_object_cached(object, project) - if RequestStore.active? - cache = url_for_object_cache[object_class][project.id] - - get_or_set_cache(cache, object) { url_for_object(object, project) } - else + cached_call(:banzai_url_for_object, object, path: [object_class, project.id]) do url_for_object(object, project) end end @@ -324,21 +308,17 @@ module Banzai RequestStore[:banzai_project_refs] ||= {} end - def find_objects_cache - RequestStore[:banzai_find_objects_cache] ||= Hash.new do |hash, key| - hash[key] = Hash.new { |h, k| h[k] = {} } - end - end + def cached_call(request_store_key, cache_key, path: []) + if RequestStore.active? + cache = RequestStore[request_store_key] ||= Hash.new do |hash, key| + hash[key] = Hash.new { |h, k| h[k] = {} } + end - def find_objects_from_link_cache - RequestStore[:banzai_find_objects_from_link_cache] ||= Hash.new do |hash, key| - hash[key] = Hash.new { |h, k| h[k] = {} } - end - end + cache = cache.dig(*path) if path.any? - def url_for_object_cache - RequestStore[:banzai_url_for_object] ||= Hash.new do |hash, key| - hash[key] = Hash.new { |h, k| h[k] = {} } + get_or_set_cache(cache, cache_key) { yield } + else + yield end end -- cgit v1.2.1 From da5262f4e6d9524b2af2ad8f6c8ebe10edf17169 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 3 Aug 2017 13:19:11 +0100 Subject: Backport changes in https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551 to CE --- Gemfile | 10 +++ Gemfile.lock | 10 +++ app/models/project.rb | 5 +- app/models/project_import_data.rb | 2 +- lib/gitlab/key_fingerprint.rb | 71 ++++++++---------- lib/gitlab/shell.rb | 24 ++++-- spec/lib/gitlab/bitbucket_import/importer_spec.rb | 2 +- spec/lib/gitlab/key_fingerprint_spec.rb | 84 +++++++++++++++++++-- spec/lib/gitlab/shell_spec.rb | 90 +++++++++++++++++++++-- spec/models/key_spec.rb | 10 --- 10 files changed, 233 insertions(+), 75 deletions(-) diff --git a/Gemfile b/Gemfile index 37a4eb5fde4..f27ba905b4f 100644 --- a/Gemfile +++ b/Gemfile @@ -390,6 +390,16 @@ gem 'health_check', '~> 2.6.0' gem 'vmstat', '~> 2.3.0' gem 'sys-filesystem', '~> 1.1.6' +# SSH host key support +gem 'net-ssh', '~> 4.1.0' + +# Required for ED25519 SSH host key support +group :ed25519 do + gem 'rbnacl-libsodium' + gem 'rbnacl', '~> 3.2' + gem 'bcrypt_pbkdf', '~> 1.0' +end + # Gitaly GRPC client gem 'gitaly', '~> 0.24.0' diff --git a/Gemfile.lock b/Gemfile.lock index 04d17d54636..8bbd8cce322 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,6 +75,7 @@ GEM babosa (1.0.2) base32 (0.3.2) bcrypt (3.1.11) + bcrypt_pbkdf (1.0.0) benchmark-ips (2.3.0) better_errors (2.1.1) coderay (>= 1.0.0) @@ -475,6 +476,7 @@ GEM mustermann (~> 1.0.0) mysql2 (0.4.5) net-ldap (0.16.0) + net-ssh (4.1.0) netrc (0.11.0) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) @@ -662,6 +664,10 @@ GEM rake (12.0.0) rblineprof (0.3.6) debugger-ruby_core_source (~> 1.3) + rbnacl (3.4.0) + ffi + rbnacl-libsodium (1.0.11) + rbnacl (>= 3.0.1) rdoc (4.2.2) json (~> 1.4) re2 (1.1.1) @@ -924,6 +930,7 @@ DEPENDENCIES awesome_print (~> 1.2.0) babosa (~> 1.0.2) base32 (~> 0.3.0) + bcrypt_pbkdf (~> 1.0) benchmark-ips (~> 2.3.0) better_errors (~> 2.1.0) binding_of_caller (~> 0.7.2) @@ -1016,6 +1023,7 @@ DEPENDENCIES mousetrap-rails (~> 1.4.6) mysql2 (~> 0.4.5) net-ldap + net-ssh (~> 4.1.0) nokogiri (~> 1.6.7, >= 1.6.7.2) oauth2 (~> 1.4) octokit (~> 4.6.2) @@ -1062,6 +1070,8 @@ DEPENDENCIES rainbow (~> 2.2) raindrops (~> 0.18) rblineprof (~> 0.3.6) + rbnacl (~> 3.2) + rbnacl-libsodium rdoc (~> 4.2) re2 (~> 1.1.1) recaptcha (~> 3.0) diff --git a/app/models/project.rb b/app/models/project.rb index 09b1305739c..36b5d644ca3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -163,7 +163,7 @@ class Project < ActiveRecord::Base has_many :todos has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent - has_one :import_data, class_name: 'ProjectImportData' + has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true has_one :project_feature has_one :statistics, class_name: 'ProjectStatistics' @@ -192,6 +192,7 @@ class Project < ActiveRecord::Base accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature + accepts_nested_attributes_for :import_data delegate :name, to: :owner, allow_nil: true, prefix: true delegate :count, to: :forks, prefix: true @@ -588,8 +589,6 @@ class Project < ActiveRecord::Base project_import_data.credentials ||= {} project_import_data.credentials = project_import_data.credentials.merge(credentials) end - - project_import_data.save end def import? diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 37730474324..6da6632f4f2 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -1,7 +1,7 @@ require 'carrierwave/orm/activerecord' class ProjectImportData < ActiveRecord::Base - belongs_to :project + belongs_to :project, inverse_of: :import_data attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, diff --git a/lib/gitlab/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb index b75ae512d92..d9a79f7c291 100644 --- a/lib/gitlab/key_fingerprint.rb +++ b/lib/gitlab/key_fingerprint.rb @@ -1,55 +1,48 @@ module Gitlab class KeyFingerprint - include Gitlab::Popen + attr_reader :key, :ssh_key - attr_accessor :key + # Unqualified MD5 fingerprint for compatibility + delegate :fingerprint, to: :ssh_key, allow_nil: true def initialize(key) @key = key - end - - def fingerprint - cmd_status = 0 - cmd_output = '' - - Tempfile.open('gitlab_key_file') do |file| - file.puts key - file.rewind - - cmd = [] - cmd.push('ssh-keygen') - cmd.push('-E', 'md5') if explicit_fingerprint_algorithm? - cmd.push('-lf', file.path) - - cmd_output, cmd_status = popen(cmd, '/tmp') - end - - return nil unless cmd_status.zero? - # 16 hex bytes separated by ':', optionally starting with "MD5:" - fingerprint_matches = cmd_output.match(/(MD5:)?(?(\h{2}:){15}\h{2})/) - return nil unless fingerprint_matches - - fingerprint_matches[:fingerprint] + @ssh_key = + begin + Net::SSH::KeyFactory.load_data_public_key(key) + rescue Net::SSH::Exception, NotImplementedError + end end - private - - def explicit_fingerprint_algorithm? - # OpenSSH 6.8 introduces a new default output format for fingerprints. - # Check the version and decide which command to use. - - version_output, version_status = popen(%w(ssh -V)) - return false unless version_status.zero? + def valid? + ssh_key.present? + end - version_matches = version_output.match(/OpenSSH_(?\d+)\.(?\d+)/) - return false unless version_matches + def type + return unless valid? - version_info = Gitlab::VersionInfo.new(version_matches[:major].to_i, version_matches[:minor].to_i) + parts = ssh_key.ssh_type.split('-') + parts.shift if parts[0] == 'ssh' - required_version_info = Gitlab::VersionInfo.new(6, 8) + parts[0].upcase + end - version_info >= required_version_info + def bits + return unless valid? + + case type + when 'RSA' + ssh_key.n.num_bits + when 'DSS', 'DSA' + ssh_key.p.num_bits + when 'ECDSA' + ssh_key.group.order.num_bits + when 'ED25519' + 256 + else + raise "Unsupported key type: #{type}" + end end end end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 4366ff336ef..0cb28732402 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -105,12 +105,24 @@ module Gitlab # fetch_remote("gitlab/gitlab-ci", "upstream") # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 - def fetch_remote(storage, name, remote, forced: false, no_tags: false) + def fetch_remote(storage, name, remote, ssh_auth: nil, forced: false, no_tags: false) args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, "#{Gitlab.config.gitlab_shell.git_timeout}"] args << '--force' if forced args << '--no-tags' if no_tags - gitlab_shell_fast_execute_raise_error(args) + vars = {} + + if ssh_auth&.ssh_import? + if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present? + vars['GITLAB_SHELL_SSH_KEY'] = ssh_auth.ssh_private_key + end + + if ssh_auth.ssh_known_hosts.present? + vars['GITLAB_SHELL_KNOWN_HOSTS'] = ssh_auth.ssh_known_hosts + end + end + + gitlab_shell_fast_execute_raise_error(args, vars) end # Move repository @@ -293,15 +305,15 @@ module Gitlab false end - def gitlab_shell_fast_execute_raise_error(cmd) - output, status = gitlab_shell_fast_execute_helper(cmd) + def gitlab_shell_fast_execute_raise_error(cmd, vars = {}) + output, status = gitlab_shell_fast_execute_helper(cmd, vars) raise Error, output unless status.zero? true end - def gitlab_shell_fast_execute_helper(cmd) - vars = ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS) + def gitlab_shell_fast_execute_helper(cmd, vars = {}) + vars.merge!(ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS)) # Don't pass along the entire parent environment to prevent gitlab-shell # from wasting I/O by searching through GEM_PATH diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index d7d6a37f7cf..a66347ead76 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -54,7 +54,7 @@ describe Gitlab::BitbucketImport::Importer do create( :project, import_source: project_identifier, - import_data: ProjectImportData.new(credentials: data) + import_data_attributes: { credentials: data } ) end diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb index d7bebaca675..f5fd5a96bc9 100644 --- a/spec/lib/gitlab/key_fingerprint_spec.rb +++ b/spec/lib/gitlab/key_fingerprint_spec.rb @@ -1,12 +1,82 @@ -require "spec_helper" +require 'spec_helper' -describe Gitlab::KeyFingerprint do - let(:key) { "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" } - let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" } +describe Gitlab::KeyFingerprint, lib: true do + KEYS = { + rsa: + 'example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5z65PwQ1GE6foJgwk' \ + '9rmQi/glaXbUeVa5uvQpnZ3Z5+forcI7aTngh3aZ/H2UDP2L70TGy7kKNyp0J3a8/OdG' \ + 'Z08y5yi3JlbjFARO1NyoFEjw2H1SJxeJ43L6zmvTlu+hlK1jSAlidl7enS0ufTlzEEj4' \ + 'iJcuTPKdVzKRgZuTRVm9woWNVKqIrdRC0rJiTinERnfSAp/vNYERMuaoN4oJt8p/NEek' \ + 'rmFoDsQOsyDW5RAnCnjWUU+jFBKDpfkJQ1U2n6BjJewC9dl6ODK639l3yN4WOLZEk4tN' \ + 'UysfbGeF3rmMeflaD6O1Jplpv3YhwVGFNKa7fMq6k3Z0tszTJPYh', + ecdsa: + 'example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAI' \ + 'bmlzdHAyNTYAAABBBKTJy43NZzJSfNxpv/e2E6Zy3qoHoTQbmOsU5FEfpWfWa1MdTeXQ' \ + 'YvKOi+qz/1AaNx6BK421jGu74JCDJtiZWT8=', + ed25519: + '@revoked example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjq' \ + 'uxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf', + dss: + 'example.com ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4EddRIpUt9KnC7s5Of2EbdS' \ + 'PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f' \ + '6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iID' \ + 'GZ3RSAHHAAAAFQCXYFCPFSMLzLKSuYKi64QL8Fgc9QAAAIEA9+GghdabPd7LvKtcNrhX' \ + 'uXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwW' \ + 'eotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6A' \ + 'e1UlZAFMO/7PSSoAAACBAJcQ4JODqhuGbXIEpqxetm7PWbdbCcr3y/GzIZ066pRovpL6' \ + 'qm3qCVIym4cyChxWwb8qlyCIi+YRUUWm1z/wiBYT2Vf3S4FXBnyymCkKEaV/EY7+jd4X' \ + '1bXI58OD2u+bLCB/sInM4fGB8CZUIWT9nJH0Ve9jJUge2ms348/QOJ1+' + }.freeze - describe "#fingerprint" do - it "generates the key's fingerprint" do - expect(described_class.new(key).fingerprint).to eq(fingerprint) + MD5_FINGERPRINTS = { + rsa: '06:b2:8a:92:df:0e:11:2c:ca:7b:8f:a4:ba:6e:4b:fd', + ecdsa: '45:ff:5b:98:9a:b6:8a:41:13:c1:30:8b:09:5e:7b:4e', + ed25519: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16', + dss: '57:98:86:02:5f:9c:f4:9b:ad:5a:1e:51:92:0e:fd:2b' + }.freeze + + BIT_COUNTS = { + rsa: 2048, + ecdsa: 256, + ed25519: 256, + dss: 1024 + }.freeze + + describe '#type' do + KEYS.each do |type, key| + it "calculates the type of #{type} keys" do + calculated_type = described_class.new(key).type + + expect(calculated_type).to eq(type.to_s.upcase) + end + end + end + + describe '#fingerprint' do + KEYS.each do |type, key| + it "calculates the MD5 fingerprint for #{type} keys" do + fp = described_class.new(key).fingerprint + + expect(fp).to eq(MD5_FINGERPRINTS[type]) + end + end + end + + describe '#bits' do + KEYS.each do |type, key| + it "calculates the number of bits in #{type} keys" do + bits = described_class.new(key).bits + + expect(bits).to eq(BIT_COUNTS[type]) + end + end + end + + describe '#key' do + it 'carries the unmodified key data' do + key = described_class.new(KEYS[:rsa]).key + + expect(key).to eq(KEYS[:rsa]) end end end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index b90d8dede0f..2345874cf10 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -174,20 +174,94 @@ describe Gitlab::Shell do end describe '#fetch_remote' do + def fetch_remote(ssh_auth = nil) + gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage', ssh_auth: ssh_auth) + end + + def expect_popen(vars = {}) + popen_args = [ + projects_path, + 'fetch-remote', + 'current/storage', + 'project/path.git', + 'new/storage', + Gitlab.config.gitlab_shell.git_timeout.to_s + ] + + expect(Gitlab::Popen).to receive(:popen).with(popen_args, nil, popen_vars.merge(vars)) + end + + def build_ssh_auth(opts = {}) + defaults = { + ssh_import?: true, + ssh_key_auth?: false, + ssh_known_hosts: nil, + ssh_private_key: nil + } + + double(:ssh_auth, defaults.merge(opts)) + end + it 'returns true when the command succeeds' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'], - nil, popen_vars).and_return([nil, 0]) + expect_popen.and_return([nil, 0]) - expect(gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage')).to be true + expect(fetch_remote).to be_truthy end it 'raises an exception when the command fails' do - expect(Gitlab::Popen).to receive(:popen) - .with([projects_path, 'fetch-remote', 'current/storage', 'project/path.git', 'new/storage', '800'], - nil, popen_vars).and_return(["error", 1]) + expect_popen.and_return(["error", 1]) + + expect { fetch_remote }.to raise_error(Gitlab::Shell::Error, "error") + end + + context 'SSH auth' do + it 'passes the SSH key if specified' do + expect_popen('GITLAB_SHELL_SSH_KEY' => 'foo').and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'does not pass an empty SSH key' do + expect_popen.and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'does not pass the key unless SSH key auth is to be used' do + expect_popen.and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'passes the known_hosts data if specified' do + expect_popen('GITLAB_SHELL_KNOWN_HOSTS' => 'foo').and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'does not pass empty known_hosts data' do + expect_popen.and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_known_hosts: '') + + expect(fetch_remote(ssh_auth)).to be_truthy + end + + it 'does not pass known_hosts data unless SSH is to be used' do + expect_popen(popen_vars).and_return([nil, 0]) + + ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo') - expect { gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage') }.to raise_error(Gitlab::Shell::Error, "error") + expect(fetch_remote(ssh_auth)).to be_truthy + end end end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 0daeb337168..3508391c721 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -83,15 +83,6 @@ describe Key, :mailer do expect(build(:key)).to be_valid end - it 'rejects an unfingerprintable key that contains a space' do - key = build(:key) - - # Not always the middle, but close enough - key.key = key.key[0..100] + ' ' + key.key[101..-1] - - expect(key).not_to be_valid - end - it 'accepts a key with newline charecters after stripping them' do key = build(:key) key.key = key.key.insert(100, "\n") @@ -102,7 +93,6 @@ describe Key, :mailer do it 'rejects the unfingerprintable key (not a key)' do expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid end - end context 'callbacks' do -- cgit v1.2.1 From 0640b3d1d89b7a4eda343eb23b0518a835ac9106 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 31 Jul 2017 19:01:36 -0300 Subject: Store MergeWorker JID on merge request, and clean up stuck merges --- .flayignore | 1 + .../components/states/mr_widget_locked.js | 29 ------------- .../components/states/mr_widget_merging.js | 29 +++++++++++++ .../vue_merge_request_widget/dependencies.js | 2 +- .../vue_merge_request_widget/mr_widget_options.js | 4 +- .../stores/mr_widget_store.js | 9 ++-- .../vue_merge_request_widget/stores/state_maps.js | 4 +- .../projects/merge_requests_controller.rb | 5 --- app/models/merge_request.rb | 22 +++------- app/serializers/merge_request_entity.rb | 2 +- app/workers/merge_worker.rb | 2 + app/workers/stuck_merge_jobs_worker.rb | 34 +++++++++++++++ .../31207-clean-locked-merge-requests.yml | 4 ++ config/initializers/1_settings.rb | 4 ++ ...170731183033_add_merge_jid_to_merge_requests.rb | 7 +++ ..._remove_locked_at_column_from_merge_requests.rb | 11 +++++ db/schema.rb | 2 +- doc/user/project/integrations/webhooks.md | 1 - spec/features/merge_requests/widget_spec.rb | 13 ++++++ .../api/schemas/entities/merge_request.json | 4 +- .../components/states/mr_widget_locked_spec.js | 10 ++--- spec/javascripts/vue_mr_widget/mock_data.js | 1 - .../vue_mr_widget/mr_widget_options_spec.js | 2 +- spec/lib/gitlab/import_export/project.json | 9 ---- .../gitlab/import_export/safe_model_attributes.yml | 1 - spec/models/merge_request_spec.rb | 26 +++++++++++ spec/serializers/merge_request_entity_spec.rb | 2 +- spec/workers/merge_worker_spec.rb | 11 +++++ spec/workers/stuck_merge_jobs_worker_spec.rb | 50 ++++++++++++++++++++++ 29 files changed, 220 insertions(+), 81 deletions(-) delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js create mode 100644 app/workers/stuck_merge_jobs_worker.rb create mode 100644 changelogs/unreleased/31207-clean-locked-merge-requests.yml create mode 100644 db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb create mode 100644 db/migrate/20170801000000_remove_locked_at_column_from_merge_requests.rb create mode 100644 spec/workers/stuck_merge_jobs_worker_spec.rb diff --git a/.flayignore b/.flayignore index e2d0a2e50c5..b63ce4c4df0 100644 --- a/.flayignore +++ b/.flayignore @@ -3,4 +3,5 @@ lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/diff/position_tracer.rb app/policies/project_policy.rb app/models/concerns/relative_positioning.rb +app/workers/stuck_merge_jobs_worker.rb lib/gitlab/redis/*.rb diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js deleted file mode 100644 index a12f418e1af..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_locked.js +++ /dev/null @@ -1,29 +0,0 @@ -import statusIcon from '../mr_widget_status_icon'; - -export default { - name: 'MRWidgetLocked', - props: { - mr: { type: Object, required: true }, - }, - components: { - statusIcon, - }, - template: ` - - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js new file mode 100644 index 00000000000..f6d1a4feeb2 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js @@ -0,0 +1,29 @@ +import statusIcon from '../mr_widget_status_icon'; + +export default { + name: 'MRWidgetMerging', + props: { + mr: { type: Object, required: true }, + }, + components: { + statusIcon, + }, + template: ` +
+ +
+

+ This merge request is in the process of being merged +

+
+

+ The changes will be merged into + + {{mr.targetBranch}} + +

+
+
+
+ `, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 546a3f625c7..49340c232c8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -19,7 +19,7 @@ export { default as WidgetRelatedLinks } from './components/mr_widget_related_li export { default as MergedState } from './components/states/mr_widget_merged'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge'; export { default as ClosedState } from './components/states/mr_widget_closed'; -export { default as LockedState } from './components/states/mr_widget_locked'; +export { default as MergingState } from './components/states/mr_widget_merging'; export { default as WipState } from './components/states/mr_widget_wip'; export { default as ArchivedState } from './components/states/mr_widget_archived'; export { default as ConflictsState } from './components/states/mr_widget_conflicts'; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 577d77f09a6..0042c48816f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -8,7 +8,7 @@ import { WidgetRelatedLinks, MergedState, ClosedState, - LockedState, + MergingState, WipState, ArchivedState, ConflictsState, @@ -212,7 +212,7 @@ export default { 'mr-widget-related-links': WidgetRelatedLinks, 'mr-widget-merged': MergedState, 'mr-widget-closed': ClosedState, - 'mr-widget-locked': LockedState, + 'mr-widget-merging': MergingState, 'mr-widget-failed-to-merge': FailedToMerge, 'mr-widget-wip': WipState, 'mr-widget-archived': ArchivedState, diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index fddafb0ddfa..fbea764b739 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -73,6 +73,7 @@ export default class MergeRequestStore { this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path; this.hasSHAChanged = this.sha !== data.diff_head_sha; this.canBeMerged = data.can_be_merged || false; + this.mergeOngoing = data.merge_ongoing; // Cherry-pick and Revert actions related this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; @@ -94,6 +95,11 @@ export default class MergeRequestStore { } setState(data) { + if (this.mergeOngoing) { + this.state = 'merging'; + return; + } + if (this.isOpen) { this.state = getStateKey.call(this, data); } else { @@ -104,9 +110,6 @@ export default class MergeRequestStore { case 'closed': this.state = 'closed'; break; - case 'locked': - this.state = 'locked'; - break; default: this.state = null; } diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js index 605dd3a1ff4..9074a064a6d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js @@ -1,7 +1,7 @@ const stateToComponentMap = { merged: 'mr-widget-merged', closed: 'mr-widget-closed', - locked: 'mr-widget-locked', + merging: 'mr-widget-merging', conflicts: 'mr-widget-conflicts', missingBranch: 'mr-widget-missing-branch', workInProgress: 'mr-widget-wip', @@ -20,7 +20,7 @@ const stateToComponentMap = { }; const statesToShowHelpWidget = [ - 'locked', + 'merging', 'conflicts', 'workInProgress', 'readyToMerge', diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index d361e661d0e..4de814d0ca8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -67,11 +67,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @noteable = @merge_request @commits_count = @merge_request.commits_count - if @merge_request.locked_long_ago? - @merge_request.unlock_mr - @merge_request.close - end - labels set_pipeline_variables diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 8ca850b6d96..e0c779b4fbc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -61,16 +61,6 @@ class MergeRequest < ActiveRecord::Base transition locked: :opened end - after_transition any => :locked do |merge_request, transition| - merge_request.locked_at = Time.now - merge_request.save - end - - after_transition locked: (any - :locked) do |merge_request, transition| - merge_request.locked_at = nil - merge_request.save - end - state :opened state :closed state :merged @@ -392,6 +382,12 @@ class MergeRequest < ActiveRecord::Base 'Source project is not a fork of the target project' end + def merge_ongoing? + return false unless merge_jid + + Gitlab::SidekiqStatus.num_running([merge_jid]) > 0 + end + def closed_without_fork? closed? && source_project_missing? end @@ -725,12 +721,6 @@ class MergeRequest < ActiveRecord::Base end end - def locked_long_ago? - return false unless locked? - - locked_at.nil? || locked_at < (Time.now - 1.day) - end - def has_ci? has_ci_integration = source_project.try(:ci_service) uses_gitlab_ci = all_pipelines.any? diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb index 7f17f2bf604..07650ce6f20 100644 --- a/app/serializers/merge_request_entity.rb +++ b/app/serializers/merge_request_entity.rb @@ -2,7 +2,6 @@ class MergeRequestEntity < IssuableEntity include RequestAwareEntity expose :in_progress_merge_commit_sha - expose :locked_at expose :merge_commit_sha expose :merge_error expose :merge_params @@ -32,6 +31,7 @@ class MergeRequestEntity < IssuableEntity expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline # Booleans + expose :merge_ongoing?, as: :merge_ongoing expose :work_in_progress?, as: :work_in_progress expose :source_branch_exists?, as: :source_branch_exists expose :mergeable_discussions_state?, as: :mergeable_discussions_state diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb index 48e2da338f6..c3b58df92c1 100644 --- a/app/workers/merge_worker.rb +++ b/app/workers/merge_worker.rb @@ -7,6 +7,8 @@ class MergeWorker current_user = User.find(current_user_id) merge_request = MergeRequest.find(merge_request_id) + merge_request.update_column(:merge_jid, jid) + MergeRequests::MergeService.new(merge_request.target_project, current_user, params) .execute(merge_request) end diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb new file mode 100644 index 00000000000..7843179d77c --- /dev/null +++ b/app/workers/stuck_merge_jobs_worker.rb @@ -0,0 +1,34 @@ +class StuckMergeJobsWorker + include Sidekiq::Worker + include CronjobQueue + + def perform + stuck_merge_requests.find_in_batches(batch_size: 100) do |group| + jids = group.map(&:merge_jid) + + # Find the jobs that aren't currently running or that exceeded the threshold. + completed_jids = Gitlab::SidekiqStatus.completed_jids(jids) + + if completed_jids.any? + completed_ids = group.select { |merge_request| completed_jids.include?(merge_request.merge_jid) }.map(&:id) + + apply_current_state!(completed_jids, completed_ids) + end + end + end + + private + + def apply_current_state!(completed_jids, completed_ids) + merge_requests = MergeRequest.where(id: completed_ids) + + merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged) + merge_requests.where(merge_commit_sha: nil).update_all(state: :opened) + + Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}") + end + + def stuck_merge_requests + MergeRequest.select('id, merge_jid').with_state(:locked).where.not(merge_jid: nil).reorder(nil) + end +end diff --git a/changelogs/unreleased/31207-clean-locked-merge-requests.yml b/changelogs/unreleased/31207-clean-locked-merge-requests.yml new file mode 100644 index 00000000000..1f52987baef --- /dev/null +++ b/changelogs/unreleased/31207-clean-locked-merge-requests.yml @@ -0,0 +1,4 @@ +--- +title: Unlock stuck merge request and set the proper state +merge_request: 13207 +author: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 7a43bf939ea..396bc57bd64 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -395,6 +395,10 @@ Settings.cron_jobs['remove_old_web_hook_logs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['remove_old_web_hook_logs_worker']['cron'] ||= '40 0 * * *' Settings.cron_jobs['remove_old_web_hook_logs_worker']['job_class'] = 'RemoveOldWebHookLogsWorker' +Settings.cron_jobs['stuck_merge_jobs_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['stuck_merge_jobs_worker']['cron'] ||= '0 */2 * * *' +Settings.cron_jobs['stuck_merge_jobs_worker']['job_class'] = 'StuckMergeJobsWorker' + # # GitLab Shell # diff --git a/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb b/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb new file mode 100644 index 00000000000..a7d8f2f3604 --- /dev/null +++ b/db/migrate/20170731183033_add_merge_jid_to_merge_requests.rb @@ -0,0 +1,7 @@ +class AddMergeJidToMergeRequests < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column :merge_requests, :merge_jid, :string + end +end diff --git a/db/migrate/20170801000000_remove_locked_at_column_from_merge_requests.rb b/db/migrate/20170801000000_remove_locked_at_column_from_merge_requests.rb new file mode 100644 index 00000000000..ea3d1fb3e02 --- /dev/null +++ b/db/migrate/20170801000000_remove_locked_at_column_from_merge_requests.rb @@ -0,0 +1,11 @@ +class RemoveLockedAtColumnFromMergeRequests < ActiveRecord::Migration + DOWNTIME = false + + def up + remove_column :merge_requests, :locked_at + end + + def down + add_column :merge_requests, :locked_at, :datetime_with_timezone + end +end diff --git a/db/schema.rb b/db/schema.rb index f2f35acef95..4816e1aefab 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -840,7 +840,6 @@ ActiveRecord::Schema.define(version: 20170803130232) do t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.datetime "locked_at" t.integer "updated_by_id" t.text "merge_error" t.text "merge_params" @@ -858,6 +857,7 @@ ActiveRecord::Schema.define(version: 20170803130232) do t.integer "last_edited_by_id" t.integer "head_pipeline_id" t.boolean "ref_fetched" + t.string "merge_jid" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index c03a2df9a72..47eb0b34f66 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -438,7 +438,6 @@ X-Gitlab-Event: Note Hook "iid": 1, "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.", "position": 0, - "locked_at": null, "source":{ "name":"Gitlab Test", "description":"Aut reprehenderit ut est.", diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 69e31c7481f..fd991293ee9 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -219,4 +219,17 @@ describe 'Merge request', :js do expect(page).to have_field('remove-source-branch-input', disabled: true) end end + + context 'ongoing merge process' do + it 'shows Merging state' do + allow_any_instance_of(MergeRequest).to receive(:merge_ongoing?).and_return(true) + + visit project_merge_request_path(project, merge_request) + + wait_for_requests + + expect(page).not_to have_button('Merge') + expect(page).to have_content('This merge request is in the process of being merged') + end + end end diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json index 7ffa82fc4bd..2f12b671dec 100644 --- a/spec/fixtures/api/schemas/entities/merge_request.json +++ b/spec/fixtures/api/schemas/entities/merge_request.json @@ -19,7 +19,6 @@ "human_time_estimate": { "type": ["integer", "null"] }, "human_total_time_spent": { "type": ["integer", "null"] }, "in_progress_merge_commit_sha": { "type": ["string", "null"] }, - "locked_at": { "type": ["string", "null"] }, "merge_error": { "type": ["string", "null"] }, "merge_commit_sha": { "type": ["string", "null"] }, "merge_params": { "type": ["object", "null"] }, @@ -94,7 +93,8 @@ "commit_change_content_path": { "type": "string" }, "remove_wip_path": { "type": "string" }, "commits_count": { "type": "integer" }, - "remove_source_branch": { "type": ["boolean", "null"] } + "remove_source_branch": { "type": ["boolean", "null"] }, + "merge_ongoing": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js index fb2ef606604..237035648cf 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js @@ -1,10 +1,10 @@ import Vue from 'vue'; -import lockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_locked'; +import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging'; -describe('MRWidgetLocked', () => { +describe('MRWidgetMerging', () => { describe('props', () => { it('should have props', () => { - const { mr } = lockedComponent.props; + const { mr } = mergingComponent.props; expect(mr.type instanceof Object).toBeTruthy(); expect(mr.required).toBeTruthy(); @@ -13,7 +13,7 @@ describe('MRWidgetLocked', () => { describe('template', () => { it('should have correct elements', () => { - const Component = Vue.extend(lockedComponent); + const Component = Vue.extend(mergingComponent); const mr = { targetBranchPath: '/branch-path', targetBranch: 'branch', @@ -24,7 +24,7 @@ describe('MRWidgetLocked', () => { }).$el; expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.innerText).toContain('it is locked'); + expect(el.innerText).toContain('This merge request is in the process of being merged'); expect(el.innerText).toContain('changes will be merged into'); expect(el.querySelector('.label-branch a').getAttribute('href')).toEqual(mr.targetBranchPath); expect(el.querySelector('.label-branch a').textContent).toContain(mr.targetBranch); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index ad2f28b24f0..0795d0aaa82 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -20,7 +20,6 @@ export default { "human_time_estimate": null, "human_total_time_spent": null, "in_progress_merge_commit_sha": null, - "locked_at": null, "merge_commit_sha": "53027d060246c8f47e4a9310fb332aa52f221775", "merge_error": null, "merge_params": { diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 3a0c50b750f..669ee248bf1 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -342,7 +342,7 @@ describe('mrWidgetOptions', () => { expect(comps['mr-widget-related-links']).toBeDefined(); expect(comps['mr-widget-merged']).toBeDefined(); expect(comps['mr-widget-closed']).toBeDefined(); - expect(comps['mr-widget-locked']).toBeDefined(); + expect(comps['mr-widget-merging']).toBeDefined(); expect(comps['mr-widget-failed-to-merge']).toBeDefined(); expect(comps['mr-widget-wip']).toBeDefined(); expect(comps['mr-widget-archived']).toBeDefined(); diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 469a014e4d2..4e631e13410 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2534,7 +2534,6 @@ "iid": 9, "description": null, "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -2983,7 +2982,6 @@ "iid": 8, "description": null, "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -3267,7 +3265,6 @@ "iid": 7, "description": "Et commodi deserunt aspernatur vero rerum. Ut non dolorum alias in odit est libero. Voluptatibus eos in et vitae repudiandae facilis ex mollitia.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -3551,7 +3548,6 @@ "iid": 6, "description": "Dicta magnam non voluptates nam dignissimos nostrum deserunt. Dolorum et suscipit iure quae doloremque. Necessitatibus saepe aut labore sed.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -4241,7 +4237,6 @@ "iid": 5, "description": "Est eaque quasi qui qui. Similique voluptatem impedit iusto ratione reprehenderit. Itaque est illum ut nulla aut.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -4789,7 +4784,6 @@ "iid": 4, "description": "Nam magnam odit velit rerum. Sapiente dolore sunt saepe debitis. Culpa maiores ut ad dolores dolorem et.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -5288,7 +5282,6 @@ "iid": 3, "description": "Libero nesciunt mollitia quis odit eos vero quasi. Iure voluptatem ut sint pariatur voluptates ut aut. Laborum possimus unde illum ipsum eum.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -5548,7 +5541,6 @@ "iid": 2, "description": "Ut dolor quia aliquid dolore et nisi. Est minus suscipit enim quaerat sapiente consequatur rerum. Eveniet provident consequatur dolor accusantium reiciendis.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { @@ -6238,7 +6230,6 @@ "iid": 1, "description": "Eveniet nihil ratione veniam similique qui aut sapiente tempora. Sed praesentium iusto dignissimos possimus id repudiandae quo nihil. Qui doloremque autem et iure fugit.", "position": 0, - "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 11f4c16ff96..4dce48f8079 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -145,7 +145,6 @@ MergeRequest: - iid - description - position -- locked_at - updated_by_id - merge_error - merge_params diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 3402c260f27..a1a3e70a7d2 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1369,6 +1369,32 @@ describe MergeRequest do end end + describe '#merge_ongoing?' do + it 'returns true when merge process is ongoing for merge_jid' do + merge_request = create(:merge_request, merge_jid: 'foo') + + allow(Gitlab::SidekiqStatus).to receive(:num_running).with(['foo']).and_return(1) + + expect(merge_request.merge_ongoing?).to be(true) + end + + it 'returns false when no merge process running for merge_jid' do + merge_request = build(:merge_request, merge_jid: 'foo') + + allow(Gitlab::SidekiqStatus).to receive(:num_running).with(['foo']).and_return(0) + + expect(merge_request.merge_ongoing?).to be(false) + end + + it 'returns false when merge_jid is nil' do + merge_request = build(:merge_request, merge_jid: nil) + + expect(Gitlab::SidekiqStatus).not_to receive(:num_running) + + expect(merge_request.merge_ongoing?).to be(false) + end + end + describe "#closed_without_fork?" do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb index 18cd9e9c006..a2fd5b7daae 100644 --- a/spec/serializers/merge_request_entity_spec.rb +++ b/spec/serializers/merge_request_entity_spec.rb @@ -47,7 +47,7 @@ describe MergeRequestEntity do :cancel_merge_when_pipeline_succeeds_path, :create_issue_to_resolve_discussions_path, :source_branch_path, :target_branch_commits_path, - :target_branch_tree_path, :commits_count) + :target_branch_tree_path, :commits_count, :merge_ongoing) end it 'has email_patches_path' do diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index 303193bab9b..ee51000161a 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -27,4 +27,15 @@ describe MergeWorker do expect(source_project.repository.branch_names).not_to include('markdown') end end + + it 'persists merge_jid' do + merge_request = create(:merge_request, merge_jid: nil) + user = create(:user) + worker = described_class.new + + allow(worker).to receive(:jid) { '999' } + + expect { worker.perform(merge_request.id, user.id, {}) } + .to change { merge_request.reload.merge_jid }.from(nil).to('999') + end end diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb new file mode 100644 index 00000000000..a5ad78393c9 --- /dev/null +++ b/spec/workers/stuck_merge_jobs_worker_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe StuckMergeJobsWorker do + describe 'perform' do + let(:worker) { described_class.new } + + context 'merge job identified as completed' do + it 'updates merge request to merged when locked but has merge_commit_sha' do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123 456)) + mr_with_sha = create(:merge_request, :locked, merge_jid: '123', state: :locked, merge_commit_sha: 'foo-bar-baz') + mr_without_sha = create(:merge_request, :locked, merge_jid: '123', state: :locked, merge_commit_sha: nil) + + worker.perform + + expect(mr_with_sha.reload).to be_merged + expect(mr_without_sha.reload).to be_opened + end + + it 'updates merge request to opened when locked but has not been merged' do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123)) + merge_request = create(:merge_request, :locked, merge_jid: '123', state: :locked) + + worker.perform + + expect(merge_request.reload).to be_opened + end + + it 'logs updated stuck merge job ids' do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123 456)) + + create(:merge_request, :locked, merge_jid: '123') + create(:merge_request, :locked, merge_jid: '456') + + expect(Rails).to receive_message_chain(:logger, :info).with('Updated state of locked merge jobs. JIDs: 123, 456') + + worker.perform + end + end + + context 'merge job not identified as completed' do + it 'does not change merge request state when job is not completed yet' do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([]) + + merge_request = create(:merge_request, :locked, merge_jid: '123') + + expect { worker.perform }.not_to change { merge_request.reload.state }.from('locked') + end + end + end +end -- cgit v1.2.1 From 0f9bde41fc5d51ef227021444e2185fb5e5162f1 Mon Sep 17 00:00:00 2001 From: Jarka Kadlecova Date: Tue, 1 Aug 2017 08:46:43 +0200 Subject: Store & use ConvDev percentages returned by Version app --- .../conversational_development_index/metric.rb | 4 +-- app/services/submit_usage_ping_service.rb | 20 ++++++----- changelogs/unreleased/35761-convdev-perc.yml | 4 +++ .../20170731175128_add_percentages_to_conv_dev.rb | 32 +++++++++++++++++ ...3090603_calculate_conv_dev_index_percentages.rb | 30 ++++++++++++++++ db/schema.rb | 10 ++++++ .../conversational_development_index_metrics.rb | 10 ++++++ .../calculate_conv_dev_index_percentages_spec.rb | 41 ++++++++++++++++++++++ .../metric_spec.rb | 11 ++++++ .../metric_presenter_spec.rb | 6 ++-- spec/services/submit_usage_ping_service_spec.rb | 7 +++- 11 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 changelogs/unreleased/35761-convdev-perc.yml create mode 100644 db/migrate/20170731175128_add_percentages_to_conv_dev.rb create mode 100644 db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb create mode 100644 spec/migrations/calculate_conv_dev_index_percentages_spec.rb create mode 100644 spec/models/conversational_development_index/metric_spec.rb diff --git a/app/models/conversational_development_index/metric.rb b/app/models/conversational_development_index/metric.rb index f42f516f99a..0bee62f954f 100644 --- a/app/models/conversational_development_index/metric.rb +++ b/app/models/conversational_development_index/metric.rb @@ -13,9 +13,7 @@ module ConversationalDevelopmentIndex end def percentage_score(feature) - return 100 if leader_score(feature).zero? - - 100 * instance_score(feature) / leader_score(feature) + self["percentage_#{feature}"] end end end diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb index 17857ca62f2..14171bce782 100644 --- a/app/services/submit_usage_ping_service.rb +++ b/app/services/submit_usage_ping_service.rb @@ -1,6 +1,16 @@ class SubmitUsagePingService URL = 'https://version.gitlab.com/usage_data'.freeze + METRICS = %w[leader_issues instance_issues percentage_issues leader_notes instance_notes + percentage_notes leader_milestones instance_milestones percentage_milestones + leader_boards instance_boards percentage_boards leader_merge_requests + instance_merge_requests percentage_merge_requests leader_ci_pipelines + instance_ci_pipelines percentage_ci_pipelines leader_environments instance_environments + percentage_environments leader_deployments instance_deployments percentage_deployments + leader_projects_prometheus_active instance_projects_prometheus_active + percentage_projects_prometheus_active leader_service_desk_issues instance_service_desk_issues + percentage_service_desk_issues].freeze + include Gitlab::CurrentSettings def execute @@ -27,15 +37,7 @@ class SubmitUsagePingService return unless response['conv_index'].present? ConversationalDevelopmentIndex::Metric.create!( - response['conv_index'].slice( - 'leader_issues', 'instance_issues', 'leader_notes', 'instance_notes', - 'leader_milestones', 'instance_milestones', 'leader_boards', 'instance_boards', - 'leader_merge_requests', 'instance_merge_requests', 'leader_ci_pipelines', - 'instance_ci_pipelines', 'leader_environments', 'instance_environments', - 'leader_deployments', 'instance_deployments', 'leader_projects_prometheus_active', - 'instance_projects_prometheus_active', 'leader_service_desk_issues', - 'instance_service_desk_issues' - ) + response['conv_index'].slice(*METRICS) ) end end diff --git a/changelogs/unreleased/35761-convdev-perc.yml b/changelogs/unreleased/35761-convdev-perc.yml new file mode 100644 index 00000000000..319c4d18219 --- /dev/null +++ b/changelogs/unreleased/35761-convdev-perc.yml @@ -0,0 +1,4 @@ +--- +title: Store & use ConvDev percentages returned by the Version app +merge_request: +author: diff --git a/db/migrate/20170731175128_add_percentages_to_conv_dev.rb b/db/migrate/20170731175128_add_percentages_to_conv_dev.rb new file mode 100644 index 00000000000..1819bfc96bb --- /dev/null +++ b/db/migrate/20170731175128_add_percentages_to_conv_dev.rb @@ -0,0 +1,32 @@ +class AddPercentagesToConvDev < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + add_column_with_default :conversational_development_index_metrics, :percentage_boards, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_ci_pipelines, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_deployments, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_environments, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_issues, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_merge_requests, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_milestones, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_notes, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_projects_prometheus_active, :float, allow_null: false, default: 0 + add_column_with_default :conversational_development_index_metrics, :percentage_service_desk_issues, :float, allow_null: false, default: 0 + end + + def down + remove_column :conversational_development_index_metrics, :percentage_boards + remove_column :conversational_development_index_metrics, :percentage_ci_pipelines + remove_column :conversational_development_index_metrics, :percentage_deployments + remove_column :conversational_development_index_metrics, :percentage_environments + remove_column :conversational_development_index_metrics, :percentage_issues + remove_column :conversational_development_index_metrics, :percentage_merge_requests + remove_column :conversational_development_index_metrics, :percentage_milestones + remove_column :conversational_development_index_metrics, :percentage_notes + remove_column :conversational_development_index_metrics, :percentage_projects_prometheus_active + remove_column :conversational_development_index_metrics, :percentage_service_desk_issues + end +end diff --git a/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb b/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb new file mode 100644 index 00000000000..9af76c94bf3 --- /dev/null +++ b/db/post_migrate/20170803090603_calculate_conv_dev_index_percentages.rb @@ -0,0 +1,30 @@ +class CalculateConvDevIndexPercentages < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + DOWNTIME = false + + class ConversationalDevelopmentIndexMetric < ActiveRecord::Base + self.table_name = 'conversational_development_index_metrics' + + METRICS = %w[boards ci_pipelines deployments environments issues merge_requests milestones notes + projects_prometheus_active service_desk_issues] + end + + def up + ConversationalDevelopmentIndexMetric.find_each do |conv_dev_index| + update = [] + + ConversationalDevelopmentIndexMetric::METRICS.each do |metric| + instance_score = conv_dev_index["instance_#{metric}"].to_f + leader_score = conv_dev_index["leader_#{metric}"].to_f + + percentage = leader_score.zero? ? 0.0 : (instance_score / leader_score) * 100 + update << "percentage_#{metric} = '#{percentage}'" + end + + execute("UPDATE conversational_development_index_metrics SET #{update.join(',')} WHERE id = #{conv_dev_index.id}") + end + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index f2f35acef95..dc5640ad2ca 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -451,6 +451,16 @@ ActiveRecord::Schema.define(version: 20170803130232) do t.float "instance_service_desk_issues", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.float "percentage_boards", default: 0.0, null: false + t.float "percentage_ci_pipelines", default: 0.0, null: false + t.float "percentage_deployments", default: 0.0, null: false + t.float "percentage_environments", default: 0.0, null: false + t.float "percentage_issues", default: 0.0, null: false + t.float "percentage_merge_requests", default: 0.0, null: false + t.float "percentage_milestones", default: 0.0, null: false + t.float "percentage_notes", default: 0.0, null: false + t.float "percentage_projects_prometheus_active", default: 0.0, null: false + t.float "percentage_service_desk_issues", default: 0.0, null: false end create_table "deploy_keys_projects", force: :cascade do |t| diff --git a/spec/factories/conversational_development_index_metrics.rb b/spec/factories/conversational_development_index_metrics.rb index a5412629195..3806c43ba15 100644 --- a/spec/factories/conversational_development_index_metrics.rb +++ b/spec/factories/conversational_development_index_metrics.rb @@ -2,32 +2,42 @@ FactoryGirl.define do factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do leader_issues 9.256 instance_issues 1.234 + percentage_issues 13.331 leader_notes 30.33333 instance_notes 28.123 + percentage_notes 92.713 leader_milestones 16.2456 instance_milestones 1.234 + percentage_milestones 7.595 leader_boards 5.2123 instance_boards 3.254 + percentage_boards 62.429 leader_merge_requests 1.2 instance_merge_requests 0.6 + percentage_merge_requests 50.0 leader_ci_pipelines 12.1234 instance_ci_pipelines 2.344 + percentage_ci_pipelines 19.334 leader_environments 3.3333 instance_environments 2.2222 + percentage_environments 66.672 leader_deployments 1.200 instance_deployments 0.771 + percentage_deployments 64.25 leader_projects_prometheus_active 0.111 instance_projects_prometheus_active 0.109 + percentage_projects_prometheus_active 98.198 leader_service_desk_issues 15.891 instance_service_desk_issues 13.345 + percentage_service_desk_issues 83.978 end end diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb new file mode 100644 index 00000000000..597d8eab51c --- /dev/null +++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb @@ -0,0 +1,41 @@ +# encoding: utf-8 + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb') + +describe CalculateConvDevIndexPercentages, truncate: true do + let(:migration) { described_class.new } + let!(:conv_dev_index) do + create(:conversational_development_index_metric, + leader_notes: 0, + instance_milestones: 0, + percentage_issues: 0, + percentage_notes: 0, + percentage_milestones: 0, + percentage_boards: 0, + percentage_merge_requests: 0, + percentage_ci_pipelines: 0, + percentage_environments: 0, + percentage_deployments: 0, + percentage_projects_prometheus_active: 0, + percentage_service_desk_issues: 0) + end + + describe '#up' do + it 'calculates percentages correctly' do + migration.up + conv_dev_index.reload + + expect(conv_dev_index.percentage_issues).to be_within(0.1).of(13.3) + expect(conv_dev_index.percentage_notes).to be_zero # leader 0 + expect(conv_dev_index.percentage_milestones).to be_zero # instance 0 + expect(conv_dev_index.percentage_boards).to be_within(0.1).of(62.4) + expect(conv_dev_index.percentage_merge_requests).to eq(50.0) + expect(conv_dev_index.percentage_ci_pipelines).to be_within(0.1).of(19.3) + expect(conv_dev_index.percentage_environments).to be_within(0.1).of(66.7) + expect(conv_dev_index.percentage_deployments).to be_within(0.1).of(64.2) + expect(conv_dev_index.percentage_projects_prometheus_active).to be_within(0.1).of(98.2) + expect(conv_dev_index.percentage_service_desk_issues).to be_within(0.1).of(84.0) + end + end +end diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb new file mode 100644 index 00000000000..b3193619503 --- /dev/null +++ b/spec/models/conversational_development_index/metric_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +describe ConversationalDevelopmentIndex::Metric do + let(:conv_dev_index) { create(:conversational_development_index_metric) } + + describe '#percentage_score' do + it 'returns stored percentage score' do + expect(conv_dev_index.percentage_score('issues')).to eq(13.331) + end + end +end diff --git a/spec/presenters/conversational_development_index/metric_presenter_spec.rb b/spec/presenters/conversational_development_index/metric_presenter_spec.rb index 1e015c71f5b..81eb05a9a6b 100644 --- a/spec/presenters/conversational_development_index/metric_presenter_spec.rb +++ b/spec/presenters/conversational_development_index/metric_presenter_spec.rb @@ -8,9 +8,9 @@ describe ConversationalDevelopmentIndex::MetricPresenter do it 'includes instance score, leader score and percentage score' do issues_card = subject.cards.first - expect(issues_card.instance_score).to eq 1.234 - expect(issues_card.leader_score).to eq 9.256 - expect(issues_card.percentage_score).to be_within(0.1).of(13.3) + expect(issues_card.instance_score).to eq(1.234) + expect(issues_card.leader_score).to eq(9.256) + expect(issues_card.percentage_score).to eq(13.331) end end diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb index 817fa4262d5..c8a6fc1a99b 100644 --- a/spec/services/submit_usage_ping_service_spec.rb +++ b/spec/services/submit_usage_ping_service_spec.rb @@ -46,6 +46,8 @@ describe SubmitUsagePingService do .by(1) expect(ConversationalDevelopmentIndex::Metric.last.leader_issues).to eq 10.2 + expect(ConversationalDevelopmentIndex::Metric.last.instance_issues).to eq 3.2 + expect(ConversationalDevelopmentIndex::Metric.last.percentage_issues).to eq 31.37 end end @@ -60,6 +62,7 @@ describe SubmitUsagePingService do conv_index: { leader_issues: 10.2, instance_issues: 3.2, + percentage_issues: 31.37, leader_notes: 25.3, instance_notes: 23.2, @@ -86,7 +89,9 @@ describe SubmitUsagePingService do instance_projects_prometheus_active: 0.30, leader_service_desk_issues: 15.8, - instance_service_desk_issues: 15.1 + instance_service_desk_issues: 15.1, + + non_existing_column: 'value' } } end -- cgit v1.2.1 From 8a167554a9dae4ab10f5a56124a598227bf5e3ed Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 7 Aug 2017 12:36:13 -0700 Subject: reduce iterations by keeping a count of remaining enablers rather than iterating the whole remaining step set with .all?(&:prevent?) --- lib/declarative_policy/runner.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb index b5c615da4e3..1122b9e42ec 100644 --- a/lib/declarative_policy/runner.rb +++ b/lib/declarative_policy/runner.rb @@ -141,13 +141,14 @@ module DeclarativePolicy end steps = Set.new(@steps) + remaining_enablers = steps.count { |s| s.enable? } loop do return if steps.empty? # if the permission hasn't yet been enabled and we only have # prevent steps left, we short-circuit the state here - @state.prevent! if !@state.enabled? && steps.all?(&:prevent?) + @state.prevent! if !@state.enabled? && remaining_enablers == 0 lowest_score = Float::INFINITY next_step = nil @@ -162,6 +163,8 @@ module DeclarativePolicy steps.delete(next_step) + remaining_enablers -= 1 if next_step.enable? + yield next_step, lowest_score end end -- cgit v1.2.1 From 7fcd4ae9dd465206eab131c7aeed8af1562117dc Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 7 Aug 2017 19:41:33 +0000 Subject: Improve mobile sidebar --- app/assets/stylesheets/new_sidebar.scss | 53 ++++++++++------------ app/views/layouts/nav/_new_admin_sidebar.html.haml | 3 -- app/views/layouts/nav/_new_group_sidebar.html.haml | 3 -- .../layouts/nav/_new_profile_sidebar.html.haml | 3 -- .../layouts/nav/_new_project_sidebar.html.haml | 3 -- app/views/shared/_sidebar_toggle_button.html.haml | 4 ++ .../unreleased/35483-improve-mobile-sidebar.yml | 4 ++ 7 files changed, 31 insertions(+), 42 deletions(-) create mode 100644 changelogs/unreleased/35483-improve-mobile-sidebar.yml diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index c82c8a00530..0b09fa8888c 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -15,7 +15,9 @@ $new-sidebar-width: 220px; $new-sidebar-collapsed-width: 50px; .page-with-new-sidebar { - padding-left: $new-sidebar-collapsed-width; + @media (min-width: $screen-md-min) { + padding-left: $new-sidebar-collapsed-width; + } @media (min-width: $screen-lg-min) { padding-left: $new-sidebar-width; @@ -49,10 +51,6 @@ $new-sidebar-collapsed-width: 50px; align-items: center; padding: 10px 16px 10px 10px; color: $gl-text-color; - - @media (max-width: $screen-xs-max) { - padding-right: 30px; - } } &:hover, @@ -77,26 +75,6 @@ $new-sidebar-collapsed-width: 50px; overflow: hidden; text-overflow: ellipsis; } - - .close-nav-button { - display: none; - position: absolute; - top: 0; - right: 0; - height: 100%; - background-color: transparent; - border: 0; - padding: 0 10px; - color: $gl-text-color-secondary; - - @media (max-width: $screen-xs-max) { - display: block; - } - - &:hover { - color: $gl-text-color; - } - } } .settings-avatar { @@ -339,21 +317,19 @@ $new-sidebar-collapsed-width: 50px; // Collapsed nav -.toggle-sidebar-button { +.toggle-sidebar-button, +.close-nav-button { width: $new-sidebar-width - 2px; position: fixed; bottom: 0; padding: 16px; background-color: $gray-normal; + border: 0; border-top: 2px solid $border-color; color: $gl-text-color-secondary; display: flex; align-items: center; - @media (max-width: $screen-xs-max) { - display: none; - } - i { font-size: 20px; margin-right: 8px; @@ -369,6 +345,13 @@ $new-sidebar-collapsed-width: 50px; } } +.toggle-sidebar-button { + @media (max-width: $screen-xs-max) { + display: none; + } +} + + .sidebar-icons-only { .context-header { height: 60px; @@ -415,6 +398,10 @@ $new-sidebar-collapsed-width: 50px; // Mobile nav +.close-nav-button { + display: none; +} + .toggle-mobile-nav { display: none; background-color: transparent; @@ -434,6 +421,12 @@ $new-sidebar-collapsed-width: 50px; } } +@media (max-width: $screen-xs-max) { + .close-nav-button { + display: flex; + } +} + .mobile-overlay { display: none; diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index 06cfa509ebf..afa35b877ff 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -4,9 +4,6 @@ .avatar-container.s40.settings-avatar = icon('wrench') .project-title Admin Area - = button_tag class: 'close-nav-button', type: 'button' do - %span.sr-only Close sidebar - = icon ('times') %ul.sidebar-top-level-items = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index 4a04b27b3d9..d0224cf8714 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -5,9 +5,6 @@ = image_tag group_icon(@group), class: "avatar s40 avatar-tile" .group-title = @group.name - = button_tag class: 'close-nav-button', type: 'button' do - %span.sr-only Close sidebar - = icon ('times') %ul.sidebar-top-level-items = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do = link_to group_path(@group), title: 'Group overview' do diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml index df869fef604..c39db9a906b 100644 --- a/app/views/layouts/nav/_new_profile_sidebar.html.haml +++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml @@ -4,9 +4,6 @@ .avatar-container.s40.settings-avatar = icon('user') .project-title User Settings - = button_tag class: 'close-nav-button', type: 'button' do - %span.sr-only Close sidebar - = icon ('times') %ul.sidebar-top-level-items = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml index 4b7209fa69e..cdbc79e8adc 100644 --- a/app/views/layouts/nav/_new_project_sidebar.html.haml +++ b/app/views/layouts/nav/_new_project_sidebar.html.haml @@ -6,9 +6,6 @@ = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile') .project-title = @project.name - = button_tag class: 'close-nav-button', type: 'button' do - %span.sr-only Close sidebar - = icon ('times') %ul.sidebar-top-level-items = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do = link_to project_path(@project), title: 'Project overview', class: 'shortcuts-project' do diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml index e70faad4894..eb5ddb0dde4 100644 --- a/app/views/shared/_sidebar_toggle_button.html.haml +++ b/app/views/shared/_sidebar_toggle_button.html.haml @@ -2,3 +2,7 @@ = icon('angle-double-left') = icon('angle-double-right') %span.collapse-text Collapse sidebar + += button_tag class: 'close-nav-button', type: 'button' do + = icon ('times') + %span.collapse-text Close sidebar diff --git a/changelogs/unreleased/35483-improve-mobile-sidebar.yml b/changelogs/unreleased/35483-improve-mobile-sidebar.yml new file mode 100644 index 00000000000..eb3dab1da9e --- /dev/null +++ b/changelogs/unreleased/35483-improve-mobile-sidebar.yml @@ -0,0 +1,4 @@ +--- +title: Improve mobile sidebar +merge_request: +author: -- cgit v1.2.1 From 9127f32fc276bff1e38233d0b151386679fbac37 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 7 Aug 2017 14:45:44 -0500 Subject: Add class to other sidebars --- app/views/layouts/nav/_new_admin_sidebar.html.haml | 2 +- app/views/layouts/nav/_new_group_sidebar.html.haml | 2 +- app/views/layouts/nav/_new_profile_sidebar.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml index 06cfa509ebf..a30243c0a39 100644 --- a/app/views/layouts/nav/_new_admin_sidebar.html.haml +++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar +.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .context-header = link_to admin_root_path, title: 'Admin Overview' do .avatar-container.s40.settings-avatar diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml index 4a04b27b3d9..48e3d1e2f6d 100644 --- a/app/views/layouts/nav/_new_group_sidebar.html.haml +++ b/app/views/layouts/nav/_new_group_sidebar.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar +.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .context-header = link_to group_path(@group), title: @group.name do .avatar-container.s40.group-avatar diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml index df869fef604..d339619a3e8 100644 --- a/app/views/layouts/nav/_new_profile_sidebar.html.haml +++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar +.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) } .context-header = link_to profile_path, title: 'Profile Settings' do .avatar-container.s40.settings-avatar -- cgit v1.2.1 From 16cffa97f64f55a16a50cf861e133021f31c828f Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 7 Aug 2017 16:34:57 -0300 Subject: Move locked_at removal to post-deployment migration --- app/models/merge_request.rb | 1 + ...70801000000_remove_locked_at_column_from_merge_requests.rb | 11 ----------- ...70807160457_remove_locked_at_column_from_merge_requests.rb | 11 +++++++++++ db/schema.rb | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 db/migrate/20170801000000_remove_locked_at_column_from_merge_requests.rb create mode 100644 db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e0c779b4fbc..e83b11f7668 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base include CreatedAtFilterable ignore_column :position + ignore_column :locked_at belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" diff --git a/db/migrate/20170801000000_remove_locked_at_column_from_merge_requests.rb b/db/migrate/20170801000000_remove_locked_at_column_from_merge_requests.rb deleted file mode 100644 index ea3d1fb3e02..00000000000 --- a/db/migrate/20170801000000_remove_locked_at_column_from_merge_requests.rb +++ /dev/null @@ -1,11 +0,0 @@ -class RemoveLockedAtColumnFromMergeRequests < ActiveRecord::Migration - DOWNTIME = false - - def up - remove_column :merge_requests, :locked_at - end - - def down - add_column :merge_requests, :locked_at, :datetime_with_timezone - end -end diff --git a/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb b/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb new file mode 100644 index 00000000000..3949cf8261a --- /dev/null +++ b/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb @@ -0,0 +1,11 @@ +class RemoveLockedAtColumnFromMergeRequests < ActiveRecord::Migration + DOWNTIME = false + + def up + remove_column :merge_requests, :locked_at + end + + def down + # nothing to do to recover the values + end +end diff --git a/db/schema.rb b/db/schema.rb index 4816e1aefab..9fdf064a225 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170803130232) do +ActiveRecord::Schema.define(version: 20170807160457) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 15bb6e1ed7a4fb1d6554e193497836b79bc26bda Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 7 Aug 2017 12:48:22 -0700 Subject: more eagerly bail when the state is prevented --- lib/declarative_policy/runner.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb index 1122b9e42ec..56afd1f1392 100644 --- a/lib/declarative_policy/runner.rb +++ b/lib/declarative_policy/runner.rb @@ -76,6 +76,8 @@ module DeclarativePolicy @state = State.new steps_by_score do |step, score| + return if !debug && @state.prevented? + passed = nil case step.action when :enable then @@ -93,10 +95,7 @@ module DeclarativePolicy # been prevented. unless @state.prevented? passed = step.pass? - if passed - @state.prevent! - return unless debug - end + @state.prevent! if passed end debug << inspect_step(step, score, passed) if debug -- cgit v1.2.1 From aa25db89c2c63b614c8c8de944809792f9047837 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 7 Aug 2017 20:10:24 +0000 Subject: [EE Backport] Update log audit event in omniauth_callbacks_controller.rb --- app/controllers/omniauth_callbacks_controller.rb | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 323d5d26eb6..b4213574561 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -34,12 +34,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController if @user.two_factor_enabled? prompt_for_two_factor(@user) else - log_audit_event(@user, with: :ldap) + log_audit_event(@user, with: oauth['provider']) sign_in_and_redirect(@user) end else - flash[:alert] = "Access denied for your LDAP account." - redirect_to new_user_session_path + fail_ldap_login end end @@ -123,9 +122,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController sign_in_and_redirect(@user) end else - error_message = @user.errors.full_messages.to_sentence - - return redirect_to omniauth_error_path(oauth['provider'], error: error_message) + fail_login end end @@ -145,6 +142,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def oauth @oauth ||= request.env['omniauth.auth'] end + + def fail_login + error_message = @user.errors.full_messages.to_sentence + + return redirect_to omniauth_error_path(oauth['provider'], error: error_message) + end + + def fail_ldap_login + flash[:alert] = 'Access denied for your LDAP account.' + + redirect_to new_user_session_path + end def log_audit_event(user, options = {}) AuditEventService.new(user, user, options) -- cgit v1.2.1 From 0532bff6d41fd3c685c88622f34fa726f171568a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 7 Aug 2017 20:55:50 +0000 Subject: Group-level new issue & MR using previously selected project --- app/assets/javascripts/project_select.js | 17 ++-- .../javascripts/project_select_combo_button.js | 85 +++++++++++++++++ app/assets/stylesheets/framework/nav.scss | 26 ++++- .../shared/_new_project_item_select.html.haml | 7 +- changelogs/unreleased/group-new-issue.yml | 4 + spec/features/dashboard/issues_spec.rb | 15 ++- spec/features/groups/empty_states_spec.rb | 4 +- .../fixtures/project_select_combo_button.html.haml | 6 ++ .../project_select_combo_button_spec.js | 105 +++++++++++++++++++++ 9 files changed, 251 insertions(+), 18 deletions(-) create mode 100644 app/assets/javascripts/project_select_combo_button.js create mode 100644 changelogs/unreleased/group-new-issue.yml create mode 100644 spec/javascripts/fixtures/project_select_combo_button.html.haml create mode 100644 spec/javascripts/project_select_combo_button_spec.js diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index ebcefc819f5..1b4ed6be90a 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */ import Api from './api'; +import ProjectSelectComboButton from './project_select_combo_button'; (function() { this.ProjectSelect = (function() { @@ -58,7 +59,8 @@ import Api from './api'; if (this.includeGroups) { placeholder += " or group"; } - return $(select).select2({ + + $(select).select2({ placeholder: placeholder, minimumInputLength: 0, query: (function(_this) { @@ -96,21 +98,18 @@ import Api from './api'; }; })(this), id: function(project) { - return project.web_url; + return JSON.stringify({ + name: project.name, + url: project.web_url, + }); }, text: function(project) { return project.name_with_namespace || project.name; }, dropdownCssClass: "ajax-project-dropdown" }); - }); - - $('.new-project-item-select-button').on('click', function() { - $('.project-item-select', this.parentNode).select2('open'); - }); - $('.project-item-select').on('click', function() { - window.location = `${$(this).val()}/${this.dataset.relativePath}`; + return new ProjectSelectComboButton(select); }); } diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js new file mode 100644 index 00000000000..f799d9d619a --- /dev/null +++ b/app/assets/javascripts/project_select_combo_button.js @@ -0,0 +1,85 @@ +import AccessorUtilities from './lib/utils/accessor'; + +export default class ProjectSelectComboButton { + constructor(select) { + this.projectSelectInput = $(select); + this.newItemBtn = $('.new-project-item-link'); + this.newItemBtnBaseText = this.newItemBtn.data('label'); + this.itemType = this.deriveItemTypeFromLabel(); + this.groupId = this.projectSelectInput.data('groupId'); + + this.bindEvents(); + this.initLocalStorage(); + } + + bindEvents() { + this.projectSelectInput.siblings('.new-project-item-select-button') + .on('click', this.openDropdown); + + this.projectSelectInput.on('change', () => this.selectProject()); + } + + initLocalStorage() { + const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe(); + + if (localStorageIsSafe) { + const itemTypeKebabed = this.newItemBtnBaseText.toLowerCase().split(' ').join('-'); + + this.localStorageKey = ['group', this.groupId, itemTypeKebabed, 'recent-project'].join('-'); + this.setBtnTextFromLocalStorage(); + } + } + + openDropdown() { + $(this).siblings('.project-item-select').select2('open'); + } + + selectProject() { + const selectedProjectData = JSON.parse(this.projectSelectInput.val()); + const projectUrl = `${selectedProjectData.url}/${this.projectSelectInput.data('relativePath')}`; + const projectName = selectedProjectData.name; + + const projectMeta = { + url: projectUrl, + name: projectName, + }; + + this.setNewItemBtnAttributes(projectMeta); + this.setProjectInLocalStorage(projectMeta); + } + + setBtnTextFromLocalStorage() { + const cachedProjectData = this.getProjectFromLocalStorage(); + + this.setNewItemBtnAttributes(cachedProjectData); + } + + setNewItemBtnAttributes(project) { + if (project) { + this.newItemBtn.attr('href', project.url); + this.newItemBtn.text(`${this.newItemBtnBaseText} in ${project.name}`); + this.newItemBtn.enable(); + } else { + this.newItemBtn.text(`Select project to create ${this.itemType}`); + this.newItemBtn.disable(); + } + } + + deriveItemTypeFromLabel() { + // label is either 'New issue' or 'New merge request' + return this.newItemBtnBaseText.split(' ').slice(1).join(' '); + } + + getProjectFromLocalStorage() { + const projectString = localStorage.getItem(this.localStorageKey); + + return JSON.parse(projectString); + } + + setProjectInLocalStorage(projectMeta) { + const projectString = JSON.stringify(projectMeta); + + localStorage.setItem(this.localStorageKey, projectString); + } +} + diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 88e7ba117d5..d386ac5ba9c 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -251,7 +251,6 @@ // Applies on /dashboard/issues .project-item-select-holder { - display: block; margin: 0; } } @@ -283,6 +282,31 @@ } } +.project-item-select-holder.btn-group { + display: flex; + max-width: 350px; + overflow: hidden; + + @media(max-width: $screen-xs-max) { + width: 100%; + max-width: none; + } + + .new-project-item-link { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .new-project-item-select-button { + width: 32px; + } +} + +.new-project-item-select-button .fa-caret-down { + margin-left: 2px; +} + .layout-nav { width: 100%; background: $gray-light; diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index b417e83cdb6..96502d7ce93 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -1,6 +1,7 @@ - if any_projects?(@projects) - .project-item-select-holder + .project-item-select-holder.btn-group.pull-right + %a.btn.btn-new.new-project-item-link{ href: '', data: { label: local_assigns[:label] } } + = icon('spinner spin') = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled] - %a.btn.btn-new.new-project-item-select-button - = local_assigns[:label] + %button.btn.btn-new.new-project-item-select-button = icon('caret-down') diff --git a/changelogs/unreleased/group-new-issue.yml b/changelogs/unreleased/group-new-issue.yml new file mode 100644 index 00000000000..5480a44526b --- /dev/null +++ b/changelogs/unreleased/group-new-issue.yml @@ -0,0 +1,4 @@ +--- +title: Cache recent projects for group-level new resource creation. +merge_request: !13058 +author: diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index be6f78ee607..795335aa106 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -79,12 +79,21 @@ RSpec.describe 'Dashboard Issues' do end end - it 'shows the new issue page', :js do + it 'shows the new issue page', js: true do find('.new-project-item-select-button').trigger('click') + wait_for_requests - find('.select2-results li').click - expect(page).to have_current_path("/#{project.path_with_namespace}/issues/new") + project_path = "/#{project.path_with_namespace}" + project_json = { name: project.name_with_namespace, url: project_path }.to_json + + # similate selection, and prevent overlap by dropdown menu + execute_script("$('.project-item-select').val('#{project_json}').trigger('change');") + execute_script("$('#select2-drop-mask').remove();") + + find('.new-project-item-link').trigger('click') + + expect(page).to have_current_path("#{project_path}/issues/new") page.within('#content-body') do expect(page).to have_selector('.issue-form') diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb index 7f28553c44e..243e8536168 100644 --- a/spec/features/groups/empty_states_spec.rb +++ b/spec/features/groups/empty_states_spec.rb @@ -38,7 +38,7 @@ feature 'Groups Merge Requests Empty States' do it 'should show a new merge request button' do within '.empty-state' do - expect(page).to have_content('New merge request') + expect(page).to have_content('create merge request') end end @@ -63,7 +63,7 @@ feature 'Groups Merge Requests Empty States' do it 'should not show a new merge request button' do within '.empty-state' do - expect(page).not_to have_link('New merge request') + expect(page).not_to have_link('create merge request') end end end diff --git a/spec/javascripts/fixtures/project_select_combo_button.html.haml b/spec/javascripts/fixtures/project_select_combo_button.html.haml new file mode 100644 index 00000000000..54bc1a59279 --- /dev/null +++ b/spec/javascripts/fixtures/project_select_combo_button.html.haml @@ -0,0 +1,6 @@ +.project-item-select-holder + %input.project-item-select{ data: { group_id: '12345' , relative_path: 'issues/new' } } + %a.new-project-item-link{ data: { label: 'New issue' }, href: ''} + %i.fa.fa-spinner.spin + %a.new-project-item-select-button + %i.fa.fa-caret-down diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js new file mode 100644 index 00000000000..e10a5a3bef6 --- /dev/null +++ b/spec/javascripts/project_select_combo_button_spec.js @@ -0,0 +1,105 @@ +import ProjectSelectComboButton from '~/project_select_combo_button'; + +const fixturePath = 'static/project_select_combo_button.html.raw'; + +describe('Project Select Combo Button', function () { + preloadFixtures(fixturePath); + + beforeEach(function () { + this.defaults = { + label: 'Select project to create issue', + groupId: 12345, + projectMeta: { + name: 'My Cool Project', + url: 'http://mycoolproject.com', + }, + newProjectMeta: { + name: 'My Other Cool Project', + url: 'http://myothercoolproject.com', + }, + localStorageKey: 'group-12345-new-issue-recent-project', + relativePath: 'issues/new', + }; + + loadFixtures(fixturePath); + + this.newItemBtn = document.querySelector('.new-project-item-link'); + this.projectSelectInput = document.querySelector('.project-item-select'); + }); + + describe('on page load when localStorage is empty', function () { + beforeEach(function () { + this.comboButton = new ProjectSelectComboButton(this.projectSelectInput); + }); + + it('newItemBtn is disabled', function () { + expect(this.newItemBtn.hasAttribute('disabled')).toBe(true); + expect(this.newItemBtn.classList.contains('disabled')).toBe(true); + }); + + it('newItemBtn href is null', function () { + expect(this.newItemBtn.getAttribute('href')).toBe(''); + }); + + it('newItemBtn text is the plain default label', function () { + expect(this.newItemBtn.textContent).toBe(this.defaults.label); + }); + }); + + describe('on page load when localStorage is filled', function () { + beforeEach(function () { + window.localStorage + .setItem(this.defaults.localStorageKey, JSON.stringify(this.defaults.projectMeta)); + this.comboButton = new ProjectSelectComboButton(this.projectSelectInput); + }); + + it('newItemBtn is not disabled', function () { + expect(this.newItemBtn.hasAttribute('disabled')).toBe(false); + expect(this.newItemBtn.classList.contains('disabled')).toBe(false); + }); + + it('newItemBtn href is correctly set', function () { + expect(this.newItemBtn.getAttribute('href')).toBe(this.defaults.projectMeta.url); + }); + + it('newItemBtn text is the cached label', function () { + expect(this.newItemBtn.textContent) + .toBe(`New issue in ${this.defaults.projectMeta.name}`); + }); + + afterEach(function () { + window.localStorage.clear(); + }); + }); + + describe('after selecting a new project', function () { + beforeEach(function () { + this.comboButton = new ProjectSelectComboButton(this.projectSelectInput); + + // mock the effect of selecting an item from the projects dropdown (select2) + $('.project-item-select') + .val(JSON.stringify(this.defaults.newProjectMeta)) + .trigger('change'); + }); + + it('newItemBtn is not disabled', function () { + expect(this.newItemBtn.hasAttribute('disabled')).toBe(false); + expect(this.newItemBtn.classList.contains('disabled')).toBe(false); + }); + + it('newItemBtn href is correctly set', function () { + expect(this.newItemBtn.getAttribute('href')) + .toBe('http://myothercoolproject.com/issues/new'); + }); + + it('newItemBtn text is the selected project label', function () { + expect(this.newItemBtn.textContent) + .toBe(`New issue in ${this.defaults.newProjectMeta.name}`); + }); + + afterEach(function () { + window.localStorage.clear(); + }); + }); +}); + -- cgit v1.2.1 From 43b03e9c5a6918a6106819d8819ac1088c2e7af9 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 7 Aug 2017 18:09:53 -0300 Subject: Re-add column locked_at on migration rollback --- .../20170807160457_remove_locked_at_column_from_merge_requests.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb b/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb index 3949cf8261a..ea3d1fb3e02 100644 --- a/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb +++ b/db/post_migrate/20170807160457_remove_locked_at_column_from_merge_requests.rb @@ -6,6 +6,6 @@ class RemoveLockedAtColumnFromMergeRequests < ActiveRecord::Migration end def down - # nothing to do to recover the values + add_column :merge_requests, :locked_at, :datetime_with_timezone end end -- cgit v1.2.1 From cb1b4af099ef88c530f4bfd24dd3290bf9387dfb Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Mon, 7 Aug 2017 21:44:42 +0000 Subject: Resolve "User dropdown in filtered search does not load avatar on `master`" --- app/assets/stylesheets/framework/avatar.scss | 2 ++ app/helpers/avatars_helper.rb | 3 ++- app/views/shared/issuable/_user_dropdown_item.html.haml | 3 ++- spec/features/merge_requests/form_spec.rb | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index cb41df8a88d..486d88efbc5 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -100,6 +100,8 @@ margin: 0; align-self: center; } + + &.s40 { min-width: 40px; min-height: 40px; } } .avatar-counter { diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index 0e068d4b51c..4b51269533c 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -19,7 +19,8 @@ module AvatarsHelper class: %W[avatar has-tooltip s#{avatar_size}].push(*options[:css_class]), alt: "#{user_name}'s avatar", title: user_name, - data: data_attributes + data: data_attributes, + lazy: true ) end diff --git a/app/views/shared/issuable/_user_dropdown_item.html.haml b/app/views/shared/issuable/_user_dropdown_item.html.haml index a82c01c6dc2..c18e4975bb8 100644 --- a/app/views/shared/issuable/_user_dropdown_item.html.haml +++ b/app/views/shared/issuable/_user_dropdown_item.html.haml @@ -3,7 +3,8 @@ %li.filter-dropdown-item{ class: ('js-current-user' if user == current_user) } %button.btn.btn-link.dropdown-user{ type: :button } - = user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 30) + .avatar-container.s40 + = user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 40).gsub('/images/{{avatar_url}}','{{avatar_url}}').html_safe .dropdown-user-details %span = user.name diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index 6ffb05c5030..89410b0e90f 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -41,7 +41,7 @@ describe 'New/edit merge request', :js do expect(page).to have_content user2.name end - click_link 'Assign to me' + find('a', text: 'Assign to me').trigger('click') expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) page.within '.js-assignee-search' do expect(page).to have_content user.name -- cgit v1.2.1 From a0c22d1edafbd87d59dbf01acd610da97ec87912 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 7 Aug 2017 20:04:16 -0300 Subject: Exclude merge_jid on Import/Export attribute configuration --- lib/gitlab/import_export/import_export.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index c8ad3a7a5e0..c5c05bfe2fb 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -101,6 +101,7 @@ excluded_attributes: merge_requests: - :milestone_id - :ref_fetched + - :merge_jid award_emoji: - :awardable_id statuses: -- cgit v1.2.1 From f59bdbf0f12b2f370a6931753d8ee14ba92d66ea Mon Sep 17 00:00:00 2001 From: Regis Boudinot Date: Mon, 7 Aug 2017 23:56:16 +0000 Subject: 33874 confidential issue redesign --- .../confidential/confidential_issue_sidebar.vue | 82 ++++++++++++++++++++++ .../sidebar/components/confidential/edit_form.vue | 47 +++++++++++++ .../components/confidential/edit_form_buttons.vue | 45 ++++++++++++ app/assets/javascripts/sidebar/sidebar_bundle.js | 18 ++++- app/assets/stylesheets/pages/issuable.scss | 24 +++++++ app/assets/stylesheets/pages/note_form.scss | 53 ++++++++------ app/views/projects/_md_preview.html.haml | 12 ++-- app/views/projects/issues/show.html.haml | 3 +- app/views/shared/issuable/_sidebar.html.haml | 4 ++ changelogs/unreleased/33874_confi.yml | 5 ++ spec/features/issues_spec.rb | 26 +++++++ .../sidebar/confidential_edit_buttons_spec.js | 39 ++++++++++ .../sidebar/confidential_edit_form_buttons_spec.js | 39 ++++++++++ .../sidebar/confidential_issue_sidebar_spec.js | 65 +++++++++++++++++ 14 files changed, 434 insertions(+), 28 deletions(-) create mode 100644 app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue create mode 100644 app/assets/javascripts/sidebar/components/confidential/edit_form.vue create mode 100644 app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue create mode 100644 changelogs/unreleased/33874_confi.yml create mode 100644 spec/javascripts/sidebar/confidential_edit_buttons_spec.js create mode 100644 spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js create mode 100644 spec/javascripts/sidebar/confidential_issue_sidebar_spec.js diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue new file mode 100644 index 00000000000..422c02c7b7e --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue @@ -0,0 +1,82 @@ + + + diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue new file mode 100644 index 00000000000..d578b663a54 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue @@ -0,0 +1,47 @@ + + + diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue new file mode 100644 index 00000000000..97af4a3f505 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form_buttons.vue @@ -0,0 +1,45 @@ + + + diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js index a9df66748c5..9edded3ead6 100644 --- a/app/assets/javascripts/sidebar/sidebar_bundle.js +++ b/app/assets/javascripts/sidebar/sidebar_bundle.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking'; import sidebarAssignees from './components/assignees/sidebar_assignees'; +import confidential from './components/confidential/confidential_issue_sidebar.vue'; import Mediator from './sidebar_mediator'; @@ -10,13 +11,28 @@ function domContentLoaded() { mediator.fetch(); const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees'); - + const confidentialEl = document.querySelector('#js-confidential-entry-point'); // Only create the sidebarAssignees vue app if it is found in the DOM // We currently do not use sidebarAssignees for the MR page if (sidebarAssigneesEl) { new Vue(sidebarAssignees).$mount(sidebarAssigneesEl); } + if (confidentialEl) { + const dataNode = document.getElementById('js-confidential-issue-data'); + const initialData = JSON.parse(dataNode.innerHTML); + + const ConfidentialComp = Vue.extend(confidential); + + new ConfidentialComp({ + propsData: { + isConfidential: initialData.is_confidential, + isEditable: initialData.is_editable, + service: mediator.service, + }, + }).$mount(confidentialEl); + } + new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker'); } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 88343bd0113..b78db402c13 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -5,6 +5,30 @@ margin-right: auto; } +.is-confidential { + color: $orange-600; + background-color: $orange-50; + border-radius: 3px; + padding: 5px; + margin: 0 3px 0 -4px; +} + +.is-not-confidential { + border-radius: 3px; + padding: 5px; + margin: 0 3px 0 -4px; +} + +.confidentiality { + .is-not-confidential { + margin: auto; + } + + .is-confidential { + margin: auto; + } +} + .limit-container-width { .detail-page-header, .page-content-header, diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index cdb1e65e4be..c90642178fc 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -104,40 +104,51 @@ } .confidential-issue-warning { - background-color: $gray-normal; - border-radius: 3px; + color: $orange-600; + background-color: $orange-50; + border-radius: $border-radius-default $border-radius-default 0 0; + border: 1px solid $border-gray-normal; padding: 3px 12px; margin: auto; - margin-top: 0; - text-align: center; - font-size: 12px; align-items: center; +} - @media (max-width: $screen-md-max) { - // On smaller devices the warning becomes the fourth item in the list, - // rather than centering, and grows to span the full width of the - // comment area. - order: 4; - margin: 6px auto; - width: 100%; +.confidential-value { + .fa { + background-color: inherit; } +} - .fa { - margin-right: 8px; +.confidential-warning-message { + line-height: 1.5; + padding: 16px; + + .confidential-warning-message-actions { + display: flex; + + button { + flex-grow: 1; + } } } +.not-confidential { + padding: 0; + border-top: none; +} + .right-sidebar-expanded { - .confidential-issue-warning { - // When the sidebar is open the warning becomes the fourth item in the list, - // rather than centering, and grows to span the full width of the - // comment area. - order: 4; - margin: 6px auto; - width: 100%; + .md-area { + border-radius: 0; + border-top: none; } } +.right-sidebar-collapsed { + .confidential-issue-warning { + border-bottom: none; + } +} .discussion-form { padding: $gl-padding-top $gl-padding $gl-padding; diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index d0698285f84..6e13bf47ff6 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -1,5 +1,12 @@ - referenced_users = local_assigns.fetch(:referenced_users, nil) +- if defined?(@issue) && @issue.confidential? + %li.confidential-issue-warning + = confidential_icon(@issue) + %span This is a confidential issue. Your comment will not be visible to the public. +- else + %li.confidential-issue-warning.not-confidential + .md-area .md-header %ul.nav-links.clearfix @@ -10,11 +17,6 @@ %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 } Preview - - if defined?(@issue) && @issue.confidential? - %li.confidential-issue-warning - = icon('warning') - %span This is a confidential issue. Your comment will not be visible to the public. - %li.pull-right .toolbar-group = markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index a57844f974e..ad5befc6ee5 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -19,7 +19,8 @@ = icon('angle-double-left') .issuable-meta - = confidential_icon(@issue) + - if @issue.confidential + = icon('eye-slash', class: 'is-confidential') = issuable_meta(@issue, @project, "Issue") .issuable-actions diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index e7510c1d1ec..c2de6926460 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -115,6 +115,10 @@ - if can? current_user, :admin_label, @project and @project = render partial: "shared/issuable/label_page_create" + - if issuable.has_attribute?(:confidential) + %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe + #js-confidential-entry-point + = render "shared/issuable/participants", participants: issuable.participants(current_user) - if current_user - subscribed = issuable.subscribed?(current_user, @project) diff --git a/changelogs/unreleased/33874_confi.yml b/changelogs/unreleased/33874_confi.yml new file mode 100644 index 00000000000..940753d9aaa --- /dev/null +++ b/changelogs/unreleased/33874_confi.yml @@ -0,0 +1,5 @@ +--- +title: Update confidential issue UI - add confidential visibility and settings to + sidebar +merge_request: +author: diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 489baa4291f..a5bb642221c 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -706,4 +706,30 @@ describe 'Issues' do expect(page).to have_text("updated title") end end + + describe 'confidential issue#show', js: true do + it 'shows confidential sibebar information as confidential and can be turned off' do + issue = create(:issue, :confidential, project: project) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.confidential-issue-warning') + expect(page).to have_css('.is-confidential') + expect(page).not_to have_css('.is-not-confidential') + + find('.confidential-edit').click + expect(page).to have_css('.confidential-warning-message') + + within('.confidential-warning-message') do + find('.btn-close').click + end + + wait_for_requests + + visit project_issue_path(project, issue) + + expect(page).not_to have_css('.is-confidential') + expect(page).to have_css('.is-not-confidential') + end + end end diff --git a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js b/spec/javascripts/sidebar/confidential_edit_buttons_spec.js new file mode 100644 index 00000000000..482be466aad --- /dev/null +++ b/spec/javascripts/sidebar/confidential_edit_buttons_spec.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import editFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue'; + +describe('Edit Form Buttons', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editFormButtons); + const toggleForm = () => { }; + const updateConfidentialAttribute = () => { }; + + vm1 = new Component({ + propsData: { + isConfidential: true, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isConfidential: false, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + }); + + it('renders on or off text based on confidentiality', () => { + expect( + vm1.$el.innerHTML.includes('Turn Off'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('Turn On'), + ).toBe(true); + }); +}); diff --git a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js b/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js new file mode 100644 index 00000000000..724f5126945 --- /dev/null +++ b/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import editForm from '~/sidebar/components/confidential/edit_form.vue'; + +describe('Edit Form Dropdown', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editForm); + const toggleForm = () => { }; + const updateConfidentialAttribute = () => { }; + + vm1 = new Component({ + propsData: { + isConfidential: true, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isConfidential: false, + toggleForm, + updateConfidentialAttribute, + }, + }).$mount(); + }); + + it('renders on the appropriate warning text', () => { + expect( + vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('You are going to turn on the confidentiality.'), + ).toBe(true); + }); +}); diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js new file mode 100644 index 00000000000..90eac1ed1ab --- /dev/null +++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js @@ -0,0 +1,65 @@ +import Vue from 'vue'; +import confidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue'; + +describe('Confidential Issue Sidebar Block', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(confidentialIssueSidebar); + const service = { + update: () => new Promise((resolve, reject) => { + resolve(true); + reject('failed!'); + }), + }; + + vm1 = new Component({ + propsData: { + isConfidential: true, + isEditable: true, + service, + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isConfidential: false, + isEditable: false, + service, + }, + }).$mount(); + }); + + it('shows if confidential and/or editable', () => { + expect( + vm1.$el.innerHTML.includes('Edit'), + ).toBe(true); + + expect( + vm1.$el.innerHTML.includes('This issue is confidential'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('None'), + ).toBe(true); + }); + + it('displays the edit form when editable', (done) => { + expect(vm1.edit).toBe(false); + + vm1.$el.querySelector('.confidential-edit').click(); + + expect(vm1.edit).toBe(true); + + setTimeout(() => { + expect( + vm1.$el + .innerHTML + .includes('You are going to turn off the confidentiality.'), + ).toBe(true); + + done(); + }); + }); +}); -- cgit v1.2.1 From c543970d93647f716d46e518ea5b61782700af66 Mon Sep 17 00:00:00 2001 From: Regis Boudinot Date: Tue, 8 Aug 2017 01:52:18 +0000 Subject: Show all labels --- .../boards/components/issue_card_inner.js | 5 ++-- app/assets/stylesheets/pages/boards.scss | 2 ++ .../projects/boards/components/_board.html.haml | 12 ++++++++-- spec/features/boards/boards_spec.rb | 4 ++-- spec/features/boards/sidebar_spec.rb | 6 ++--- spec/javascripts/boards/issue_card_spec.js | 28 ++++++++++++---------- 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index daef01bc93d..d3de1830895 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -97,9 +97,8 @@ gl.issueBoards.IssueCardInner = Vue.extend({ return `Avatar for ${assignee.name}`; }, showLabel(label) { - if (!this.list) return true; - - return !this.list.label || label.id !== this.list.label.id; + if (!this.list || !label) return true; + return true; }, filterByLabel(label, e) { if (!this.updateFilters) return; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 6039cda96d8..e5b467a2691 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -165,6 +165,7 @@ .board-title { padding-top: ($gl-padding - 3px); + padding-bottom: $gl-padding; } } } @@ -178,6 +179,7 @@ position: relative; margin: 0; padding: $gl-padding; + padding-bottom: ($gl-padding + 3px); font-size: 1em; border-bottom: 1px solid $border-color; } diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 539ee087b14..64f5f6d7ba0 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -6,8 +6,16 @@ %i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable", ":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded && list.position === -1, \"fa-caret-left\": !list.isExpanded && list.position !== -1 }", "aria-hidden": "true" } - %span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")', - data: { container: "body", placement: "bottom" } } + + %span.has-tooltip{ "v-if": "list.type !== \"label\"", + ":title" => '(list.label ? list.label.description : "")' } + {{ list.title }} + + %span.has-tooltip{ "v-if": "list.type === \"label\"", + ":title" => '(list.label ? list.label.description : "")', + data: { container: "body", placement: "bottom" }, + class: "label color-label title", + ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.color ? list.label.text_color : \"#2e2e2e\") }" } {{ list.title }} .issue-count-badge.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' } %span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index c51b81c1cff..ce458431c55 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -233,7 +233,7 @@ describe 'Issue Boards', js: true do wait_for_board_cards(4, 1) expect(find('.board:nth-child(3)')).to have_content(issue6.title) - expect(find('.board:nth-child(3)').all('.card').last).not_to have_content(development.title) + expect(find('.board:nth-child(3)').all('.card').last).to have_content(development.title) end it 'issue moves between lists' do @@ -244,7 +244,7 @@ describe 'Issue Boards', js: true do wait_for_board_cards(4, 1) expect(find('.board:nth-child(2)')).to have_content(issue7.title) - expect(find('.board:nth-child(2)').all('.card').first).not_to have_content(planning.title) + expect(find('.board:nth-child(2)').all('.card').first).to have_content(planning.title) end it 'issue moves from closed' do diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 373cd92793e..8d3d4ff8773 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -257,7 +257,7 @@ describe 'Issue Boards', js: true do end end - expect(card).to have_selector('.label', count: 2) + expect(card).to have_selector('.label', count: 3) expect(card).to have_content(bug.title) end @@ -283,7 +283,7 @@ describe 'Issue Boards', js: true do end end - expect(card).to have_selector('.label', count: 3) + expect(card).to have_selector('.label', count: 4) expect(card).to have_content(bug.title) expect(card).to have_content(regression.title) end @@ -308,7 +308,7 @@ describe 'Issue Boards', js: true do end end - expect(card).not_to have_selector('.label') + expect(card).to have_selector('.label', count: 1) expect(card).not_to have_content(stretch.title) end end diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index bd9b4fbfdd3..69cfcbbce5a 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -238,12 +238,6 @@ describe('Issue card component', () => { }); describe('labels', () => { - it('does not render any', () => { - expect( - component.$el.querySelector('.label'), - ).toBeNull(); - }); - describe('exists', () => { beforeEach((done) => { component.issue.addLabel(label1); @@ -251,16 +245,21 @@ describe('Issue card component', () => { Vue.nextTick(() => done()); }); - it('does not render list label', () => { + it('renders list label', () => { expect( component.$el.querySelectorAll('.label').length, - ).toBe(1); + ).toBe(2); }); it('renders label', () => { + const nodes = []; + component.$el.querySelectorAll('.label').forEach((label) => { + nodes.push(label.title); + }); + expect( - component.$el.querySelector('.label').textContent, - ).toContain(label1.title); + nodes.includes(label1.description), + ).toBe(true); }); it('sets label description as title', () => { @@ -270,9 +269,14 @@ describe('Issue card component', () => { }); it('sets background color of button', () => { + const nodes = []; + component.$el.querySelectorAll('.label').forEach((label) => { + nodes.push(label.style.backgroundColor); + }); + expect( - component.$el.querySelector('.label').style.backgroundColor, - ).toContain(label1.color); + nodes.includes(label1.color), + ).toBe(true); }); }); }); -- cgit v1.2.1