diff options
35 files changed, 460 insertions, 203 deletions
diff --git a/CHANGELOG b/CHANGELOG index e578411c589..b1a35c3c2ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.1.0 (unreleased) - Add support for creating directories from Files page (Stan Hu) - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) + - Improved performance of the trending projects page - Improved performance of finding projects by their namespace - Fix bug where transferring a project would result in stale commit links (Stan Hu) - Include full path of source and target branch names in New Merge Request page (Stan Hu) @@ -38,6 +39,8 @@ v 8.1.0 (unreleased) - Use commit status in merge request widget as preffered source of CI status - Integrate CI commit and build pages into project pages - Move CI services page to project settings area + - Add "Quick Submit" behavior to input fields throughout the application. Use + Cmd+Enter on Mac and Ctrl+Enter on Windows/Linux. v 8.0.4 - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) @@ -46,6 +49,7 @@ v 8.0.4 - Remove CI token from build traces - Fix "Assign All" button on Runner admin page - Fix search in Files + - Add full project namespace to payload of system webhooks (Ricardo Band) v 8.0.3 - Fix URL shown in Slack notifications @@ -65,9 +65,9 @@ gem 'gollum-lib', '~> 4.0.2' gem "gitlab-linguist", "~> 3.0.1", require: "linguist" # API -gem "grape", "~> 0.6.1" -gem "grape-entity", "~> 0.4.2" -gem 'rack-cors', '~> 0.2.9', require: 'rack/cors' +gem 'grape', '~> 0.6.1' +gem 'grape-entity', '~> 0.4.2' +gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' # Format dates and times # based on human-friendly examples @@ -80,7 +80,7 @@ gem 'enumerize', '~> 0.7.0' gem "kaminari", "~> 0.16.3" # HAML -gem "haml-rails", '~> 0.5.3' +gem "haml-rails", '~> 0.9.0' # Files attachments gem "carrierwave", '~> 0.9.0' @@ -151,7 +151,7 @@ gem 'version_sorter', '~> 2.0.0' gem "redis-rails", '~> 4.0.0' # Campfire integration -gem 'tinder', '~> 1.9.2' +gem 'tinder', '~> 1.10.0' # HipChat integration gem 'hipchat', '~> 1.5.0' @@ -163,7 +163,7 @@ gem "gitlab-flowdock-git-hook", "~> 1.0.1" gem "gemnasium-gitlab-service", "~> 0.2" # Slack integration -gem "slack-notifier", "~> 1.0.0" +gem "slack-notifier", "~> 1.2.0" # Asana integration gem 'asana', '~> 0.0.6' diff --git a/Gemfile.lock b/Gemfile.lock index fc9244304a3..f716c0254ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -182,8 +182,8 @@ GEM factory_girl_rails (4.3.0) factory_girl (~> 4.3.0) railties (>= 3.0.0) - faraday (0.8.10) - multipart-post (~> 1.2.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) faraday_middleware (0.10.0) faraday (>= 0.7.4, < 0.10) fastercsv (1.5.5) @@ -330,12 +330,13 @@ GEM rspec (>= 2.14, < 4.0) haml (4.0.7) tilt - haml-rails (0.5.3) + haml-rails (0.9.0) actionpack (>= 4.0.1) activesupport (>= 4.0.1) - haml (>= 3.1, < 5.0) + haml (>= 4.0.6, < 5.0) + html2haml (>= 1.0.1) railties (>= 4.0.1) - hashie (2.1.2) + hashie (3.4.2) highline (1.6.21) hike (1.2.3) hipchat (1.5.2) @@ -345,6 +346,11 @@ GEM html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) + html2haml (2.0.0) + erubis (~> 2.7.0) + haml (~> 4.0.0) + nokogiri (~> 1.6.0) + ruby_parser (~> 3.5) http-cookie (1.0.2) domain_name (~> 0.5) http_parser.rb (0.5.3) @@ -396,7 +402,7 @@ GEM mousetrap-rails (1.4.6) multi_json (1.11.2) multi_xml (0.5.5) - multipart-post (1.2.0) + multipart-post (2.0.0) mysql2 (0.3.20) nenv (0.2.0) nested_form (0.3.2) @@ -496,7 +502,7 @@ GEM rack (>= 0.4) rack-attack (4.3.0) rack - rack-cors (0.2.9) + rack-cors (0.4.0) rack-mini-profiler (0.9.7) rack (>= 1.1.3) rack-mount (0.8.3) @@ -666,7 +672,7 @@ GEM rack-protection (~> 1.4) tilt (>= 1.3, < 3) six (0.2.0) - slack-notifier (1.0.0) + slack-notifier (1.2.1) slim (2.0.3) temple (~> 0.6.6) tilt (>= 1.3.3, < 2.1) @@ -721,13 +727,13 @@ GEM timers (4.0.4) hitimes timfel-krb5-auth (0.8.3) - tinder (1.9.4) + tinder (1.10.1) eventmachine (~> 1.0) - faraday (~> 0.8.9) + faraday (~> 0.9.0) faraday_middleware (~> 0.9) - hashie (>= 1.0, < 3) + hashie (>= 1.0) json (~> 1.8.0) - mime-types (~> 1.19) + mime-types multi_json (~> 1.7) twitter-stream (~> 0.1) tins (1.6.0) @@ -845,7 +851,7 @@ DEPENDENCIES grape-entity (~> 0.4.2) growl guard-rspec (~> 4.2.0) - haml-rails (~> 0.5.3) + haml-rails (~> 0.9.0) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty (~> 0.13.3) @@ -883,7 +889,7 @@ DEPENDENCIES pry-rails quiet_assets (~> 1.0.2) rack-attack (~> 4.3.0) - rack-cors (~> 0.2.9) + rack-cors (~> 0.4.0) rack-mini-profiler (~> 0.9.0) rack-oauth2 (~> 1.0.5) rails (= 4.1.12) @@ -912,7 +918,7 @@ DEPENDENCIES simplecov (~> 0.10.0) sinatra (~> 1.4.4) six (~> 0.2.0) - slack-notifier (~> 1.0.0) + slack-notifier (~> 1.2.0) slim (~> 2.0.2) spinach-rails (~> 0.2.1) spring (~> 1.3.6) @@ -927,7 +933,7 @@ DEPENDENCIES teaspoon-jasmine (~> 2.2.0) test_after_commit (~> 0.2.2) thin (~> 1.6.1) - tinder (~> 1.9.2) + tinder (~> 1.10.0) turbolinks (~> 2.5.0) uglifier (~> 2.3.2) underscore-rails (~> 1.4.4) diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee new file mode 100644 index 00000000000..4ec8531d580 --- /dev/null +++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee @@ -0,0 +1,29 @@ +# Quick Submit behavior +# +# When an input field with the `js-quick-submit` class receives a "Meta+Enter" +# (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, its parent form is +# submitted. +# +#= require extensions/jquery +# +# ### Example Markup +# +# <form action="/foo"> +# <input type="text" class="js-quick-submit" /> +# <textarea class="js-quick-submit"></textarea> +# </form> +# +$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> + return if (e.originalEvent && e.originalEvent.repeat) || e.repeat + return unless e.keyCode == 13 # Enter + + if navigator.userAgent.match(/Macintosh/) + return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) + else + return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) + + e.preventDefault() + + $form = $(e.target).closest('form') + $form.find('input[type=submit], button[type=submit]').disable() + $form.submit() diff --git a/app/assets/javascripts/behaviors/requires_input.js.coffee b/app/assets/javascripts/behaviors/requires_input.js.coffee index 8318fe435b3..79d750d1847 100644 --- a/app/assets/javascripts/behaviors/requires_input.js.coffee +++ b/app/assets/javascripts/behaviors/requires_input.js.coffee @@ -34,6 +34,5 @@ $.fn.requiresInput = -> $form.on 'change input', fieldSelector, requireInput -# Triggered on standard document `ready` and on Turbolinks `page:load` events -$(document).on 'ready page:load', -> +$ -> $('form.js-requires-input').requiresInput() diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 4b9f0d68912..ea75c656bcc 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -63,12 +63,6 @@ class @Notes # fetch notes when tab becomes visible $(document).on "visibilitychange", @visibilityChange - # Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown. - $(document).on 'keydown', '.js-note-text', (e) -> - return if e.originalEvent.repeat - if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13) - $(@).closest('form').submit() - cleanBinding: -> $(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-discussion-note-form" @@ -82,7 +76,6 @@ class @Notes $(document).off "click", ".js-discussion-reply-button" $(document).off "click", ".js-add-diff-note-button" $(document).off "visibilitychange" - $(document).off "keydown", ".js-note-text" $(document).off "keyup", ".js-note-text" $(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-close" diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss index befd63832d5..eb9a2966389 100644 --- a/app/assets/stylesheets/base/variables.scss +++ b/app/assets/stylesheets/base/variables.scss @@ -23,15 +23,67 @@ $gl-gray: #7f8fa4; $gl-padding: 16px; $gl-avatar-size: 46px; +/* + * Color schema + */ + +$white-light: #FFFFFF; +$white-normal: #DCE0E5; +$white-dark: #E4E7ED; + +$gray-light: #F0F2F5; +$gray-normal: #DCE0E5; +$gray-dark: #E4E7ED; + +$green-light: #31AF64; +$green-normal: #2FAA60; +$green-dark: #2CA05B; + +$blue-light: #2EA8E5; +$blue-normal: #2D9FD8; +$blue-dark: #2897CE; + +$orange-light: #FC6443; +$orange-normal: #E75E40; +$orange-dark: #CE5237; + +$red-light: #F43263; +$red-normal: #E52C5A; +$red-dark: #D22852; + +$border-white-light: #E3E7EC; +$border-white-normal: #D6DAE2; +$border-white-dark: #C6CACF; + +$border-gray-light: #DCE0E5; +$border-gray-normal: #D6DAE2; +$border-gray-dark: #C6CACF; + +$border-green-light: #2FAA60; +$border-green-normal: #2CA05B; +$border-green-dark: #279654; + +$border-blue-light: #2D9FD8; +$border-blue-normal: #2897CE; +$border-blue-dark: #258DC1; + +$border-orange-light: #ED5C3D; +$border-orange-normal: #CE5237; +$border-orange-dark: #C14E35; + +$border-red-light: #E52C5A; +$border-red-normal: #D22852; +$border-red-dark: #CA264F; + /* * State colors: */ -$gl-primary: #446e9b; -$gl-success: #44c679; -$gl-info: #00aaff; -$gl-warning: #EB9532; -$gl-danger: #d9534f; +$gl-primary: $blue-normal; +$gl-success: $green-normal; +$gl-info: $blue-normal; +$gl-warning: $orange-normal; +$gl-danger: $red-normal; /* * Commit Diff Colors diff --git a/app/assets/stylesheets/ci/builds.scss b/app/assets/stylesheets/ci/builds.scss index a27dd0db581..74dc3e321c1 100644 --- a/app/assets/stylesheets/ci/builds.scss +++ b/app/assets/stylesheets/ci/builds.scss @@ -73,4 +73,3 @@ margin-bottom: 2px; } } - diff --git a/app/assets/stylesheets/ci/xterm.scss b/app/assets/stylesheets/ci/xterm.scss index 532dede0b23..9a50096c0d0 100644 --- a/app/assets/stylesheets/ci/xterm.scss +++ b/app/assets/stylesheets/ci/xterm.scss @@ -1,4 +1,4 @@ -.ci-body { +.build-page { // color codes are based on http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg // see also: https://gist.github.com/jasonm23/2868981 diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index 62922e6a330..11acbe3adfa 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -1,6 +1,5 @@ @mixin btn-default { @include border-radius(2px); - border-width: 1px; border-style: solid; text-transform: uppercase; @@ -10,150 +9,62 @@ padding: 11px 16px; letter-spacing: .4px; - &:hover { - border-width: 1px; - border-style: solid; - } - - &:focus { - border-width: 1px; - border-style: solid; - } - + &:focus, &:active { + outline: none; @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); - border-width: 1px; - border-style: solid; } } @mixin btn-middle { + @include btn-default; @include border-radius(2px); - - border-width: 1px; - border-style: solid; - text-transform: uppercase; - font-size: 13px; - font-weight: 600; - line-height: 18px; padding: 11px 24px; - letter-spacing: .4px; - - &:hover { - border-width: 1px; - border-style: solid; - } - - &:focus { - border-width: 1px; - border-style: solid; - } - - &:active { - @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); - border-width: 1px; - border-style: solid; - } } +@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) { + background-color: $light; + border-color: $border-light; + color: $color; -@mixin btn-green { - background-color: #28b061; - border: 1px solid #26a65c; - color: #fff; - - &:hover { - background-color: #26ab5d; - border: 1px solid #229954; - color: #fff; - } - + &:hover, &:focus { - background-color: #26ab5d; - border: 1px solid #229954; - color: #fff; + background-color: $normal; + border-color: $border-normal; + color: $color; } &:active { @include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12)); - background-color: #23a158 !important; - border: 1px solid #229954 !important; - color: #fff !important; + background-color: $dark; + border-color: $border-dark; + color: $color; } } -@mixin btn-gray { - background-color: #f0f2f5; - border-color: #dce0e5; - color: #313236; - - &:hover { - border-color:#dce0e5; - background-color: #ebeef2; - color: #313236; - } - - &:focus { - border-color: #dce0e5; - background-color: #ebeef2; - color: #313236; - } - - &:active { - @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); - - color: #313236 !important; - border-color: #c6cacf !important; - background-color: #e4e7ed !important; - } +@mixin btn-green { + @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #FFFFFF); } -@mixin btn-white { - background-color: #fff; - border-color: #dce0e5; - color: #313236; - - &:hover { - border-color:#dce0e5; - background-color: #f0f2f5; - color: #313236; - } - - &:focus { - border-color: #dce0e5; - background-color: #f0f2f5; - color: #313236; - } - - &:active { - @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); +@mixin btn-blue { + @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF); +} - color: #313236 !important; - border-color: #c6cacf !important; - background-color: #e4e7ed !important; - } +@mixin btn-orange { + @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF); } @mixin btn-red { - background-color: #f72e60; - border-color: #ee295a; - - &:hover { - background-color: #e82757; - border-color: #e32555; - } + @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #FFFFFF); +} - &:focus { - background-color: #e82757; - border-color: #e32555; - } +@mixin btn-gray { + @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, #313236); +} - &:active { - @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); - background-color: #d42450 !important; - border-color: #e12554 !important; - } +@mixin btn-white { + @include btn-color($white-light, $border-white-light, $white-normal, $border-white-normal, $white-dark, $border-white-dark, #313236); } .btn { @@ -172,6 +83,15 @@ @include btn-gray; } + &.btn-primary, + &.btn-info { + @include btn-blue; + } + + &.btn-warning { + @include btn-orange; + } + &.btn-danger, &.btn-remove, &.btn-red { diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index de2ae93df37..4e121b95d13 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -1,5 +1,6 @@ .commits-compare-switch{ - @extend .btn; + @include btn-default; + @include btn-white; background: image-url("switch_icon.png") no-repeat center center; text-indent: -9999px; float: left; diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb index 9ea342cb26d..81a12403801 100644 --- a/app/finders/trending_projects_finder.rb +++ b/app/finders/trending_projects_finder.rb @@ -1,13 +1,6 @@ class TrendingProjectsFinder - def execute(current_user, start_date = nil) - start_date ||= Date.today - 1.month - - projects = projects_for(current_user) - - # Determine trending projects based on comments count - # for period of time - ex. month - projects.joins(:notes).where('notes.created_at > ?', start_date). - group("projects.id").reorder("count(notes.id) DESC") + def execute(current_user, start_date = 1.month.ago) + projects_for(current_user).trending(start_date) end private diff --git a/app/models/project.rb b/app/models/project.rb index f75082a35d0..021920008ad 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -266,6 +266,20 @@ class Project < ActiveRecord::Base name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR %r{(?<project>#{name_pattern}/#{name_pattern})} end + + def trending(since = 1.month.ago) + # By counting in the JOIN we don't expose the GROUP BY to the outer query. + # This means that calls such as "any?" and "count" just return a number of + # the total count, instead of the counts grouped per project as a Hash. + join_body = "INNER JOIN ( + SELECT project_id, COUNT(*) AS amount + FROM notes + WHERE created_at >= #{sanitize(since)} + GROUP BY project_id + ) join_note_counts ON projects.id = join_note_counts.project_id" + + joins(join_body).reorder('join_note_counts.amount DESC') + end end def team diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 60235b6be2a..9a5fe4af9dd 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -54,6 +54,7 @@ class SystemHooksService data.merge!({ project_name: model.project.name, project_path: model.project.path, + project_path_with_namespace: model.project.path_with_namespace, project_id: model.project.id, user_name: model.user.name, user_email: model.user.email, diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 7c89457ace3..2169a821fb2 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -15,6 +15,8 @@ %li = link_to 'Tables', '#tables' %li + = link_to 'Nav', '#nav' + %li = link_to 'Buttons', '#buttons' %li = link_to 'Panels', '#panels' @@ -30,17 +32,32 @@ %h2#blocks Blocks %h3 - %code .well + %code .gray-content-block + - .well - %h4 Something + .gray-content-block.middle-block + %h4 Normal block inside content + = lorem + + .gray-content-block.second-block + %h4 Second block = lorem %h2#lists Lists %h3 + %code .content-list + %ul.content-list + %li + One item + %li + One item + %li + One item + + %h3 %code .well-list %ul.well-list %li @@ -102,11 +119,40 @@ %td the Bird %td @twitter + %h2#navs Navigation + + %h3 + %code .center-top-menu + .example + %ul.center-top-menu + %li.active + %a Open + %li + %a Closed + + %h3 + %code .btn-group.btn-group-next + .example + %div.btn-group.btn-group-next + %a.btn.active Open + %a.btn Closed + + + %h3 + %code .nav.nav-tabs + .example + %ul.nav.nav-tabs + %li.active + %a Open + %li + %a Closed + %h2#buttons Buttons .example %button.btn.btn-default{:type => "button"} Default + %button.btn.btn-gray{:type => "button"} Gray %button.btn.btn-primary{:type => "button"} Primary %button.btn.btn-success{:type => "button"} Success %button.btn.btn-info{:type => "button"} Info diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 9c3e1703c89..f1ad0c3c403 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -11,7 +11,7 @@ - if current_action?(:new) || current_action?(:create) \/ = text_field_tag 'file_name', params[:file_name], placeholder: "File name", - required: true, class: 'form-control new-file-name' + required: true, class: 'form-control new-file-name js-quick-submit' .pull-right = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index d7987e24ef3..7975137c37f 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -15,7 +15,7 @@ = label_tag 'branch', class: 'control-label' do Branch .col-sm-10 - = text_field_tag 'new_branch', @ref, class: "form-control" + = text_field_tag 'new_branch', @ref, class: "form-control js-quick-submit" = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 534c545329b..4cf13492e99 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -10,7 +10,7 @@ .form-group = f.label :title, class: 'control-label' .col-sm-10 - = f.text_field :title, class: "form-control", required: true + = f.text_field :title, class: "form-control js-quick-submit", required: true .form-group = f.label :color, "Background Color", class: 'control-label' .col-sm-10 diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 58d8478744e..e7ac7a0eaa4 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -34,7 +34,7 @@ = render "projects/merge_requests/widget/show.html.haml" - if @merge_request.open? && @merge_request.can_be_merged? - .light + .light.append-bottom-20 You can also accept this merge request manually using the = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 74e9668052d..255ddab479f 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -16,13 +16,13 @@ .form-group = f.label :title, "Title", class: "control-label" .col-sm-10 - = f.text_field :title, maxlength: 255, class: "form-control", required: true + = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true %p.hint Required .form-group.milestone-description = f.label :description, "Description", class: "control-label" .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' .hint .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index a0e26f9827e..a21c019986a 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -2,7 +2,7 @@ = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f| = note_target_fields(note) = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do - = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field' + = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field js-quick-submit' = render 'projects/notes/hints' .note-form-actions diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 512ccd48b38..13dfa0a1bb3 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -8,7 +8,7 @@ = f.hidden_field :noteable_type = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text' + = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-quick-submit' = render 'projects/notes/hints' .error-alert diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 05d754adbe5..261d4a92d7d 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -22,7 +22,7 @@ = f.label :content, class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do - = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' + = render 'projects/zen', f: f, attr: :content, classes: 'description form-control js-quick-submit' .col-sm-12.hint .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index 5071ff640f1..cc3f1268f8b 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -6,7 +6,7 @@ .max-width-marker = text_area_tag 'commit_message', (params[:commit_message] || local_assigns[:text]), - class: 'form-control', placeholder: local_assigns[:placeholder], + class: 'form-control js-quick-submit', placeholder: local_assigns[:placeholder], required: true, rows: (local_assigns[:rows] || 3) - if local_assigns[:hint] %p.hint diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 33ec726e93c..594e54f404c 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -10,7 +10,7 @@ %strong= 'Title *' .col-sm-10 = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off', - class: 'form-control pad js-gfm-input', required: true + class: 'form-control pad js-gfm-input js-quick-submit', required: true - if issuable.is_a?(MergeRequest) %p.help-block @@ -26,7 +26,7 @@ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, - classes: 'description form-control' + classes: 'description form-control js-quick-submit' .col-sm-12.hint .pull-left Parsed with diff --git a/config/application.rb b/config/application.rb index a96e22211e6..bfa2a809dd7 100644 --- a/config/application.rb +++ b/config/application.rb @@ -74,7 +74,7 @@ module Gitlab origins '*' resource '/api/*', headers: :any, - methods: [:get, :post, :options, :put, :delete], + methods: :any, expose: ['Link'] end end diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index b0e4613cdef..5cb05b13b3e 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -48,16 +48,17 @@ X-Gitlab-Event: System Hook ```json { - "created_at": "2012-07-21T07:30:56Z", - "event_name": "user_add_to_team", - "project_access": "Master", - "project_id": 74, - "project_name": "StoreCloud", - "project_path": "storecloud", - "user_email": "johnsmith@gmail.com", - "user_name": "John Smith", - "user_id": 41, - "project_visibility": "private", + "created_at": "2012-07-21T07:30:56Z", + "event_name": "user_add_to_team", + "project_access": "Master", + "project_id": 74, + "project_name": "StoreCloud", + "project_path": "storecloud", + "project_path_with_namespace": "jsmith/storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_id": 41, + "project_visibility": "private", } ``` @@ -65,16 +66,17 @@ X-Gitlab-Event: System Hook ```json { - "created_at": "2012-07-21T07:30:56Z", - "event_name": "user_remove_from_team", - "project_access": "Master", - "project_id": 74, - "project_name": "StoreCloud", - "project_path": "storecloud", - "user_email": "johnsmith@gmail.com", - "user_name": "John Smith", - "user_id": 41, - "project_visibility": "private", + "created_at": "2012-07-21T07:30:56Z", + "event_name": "user_remove_from_team", + "project_access": "Master", + "project_id": 74, + "project_name": "StoreCloud", + "project_path": "storecloud", + "project_path_with_namespace": "jsmith/storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_id": 41, + "project_visibility": "private", } ``` diff --git a/spec/benchmarks/finders/trending_projects_finder_spec.rb b/spec/benchmarks/finders/trending_projects_finder_spec.rb new file mode 100644 index 00000000000..551ce21840d --- /dev/null +++ b/spec/benchmarks/finders/trending_projects_finder_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe TrendingProjectsFinder, benchmark: true do + describe '#execute' do + let(:finder) { described_class.new } + let(:user) { create(:user) } + + # to_a is used to force actually running the query (instead of just building + # it). + benchmark_subject { finder.execute(user).non_archived.to_a } + + it { is_expected.to iterate_per_second(500) } + end +end diff --git a/spec/benchmarks/models/project_spec.rb b/spec/benchmarks/models/project_spec.rb index 0c6b533ac2b..cee0949edc5 100644 --- a/spec/benchmarks/models/project_spec.rb +++ b/spec/benchmarks/models/project_spec.rb @@ -1,6 +1,36 @@ require 'spec_helper' describe Project, benchmark: true do + describe '.trending' do + let(:group) { create(:group) } + let(:project1) { create(:empty_project, :public, group: group) } + let(:project2) { create(:empty_project, :public, group: group) } + + let(:iterations) { 500 } + + before do + 2.times do + create(:note_on_commit, project: project1) + end + + create(:note_on_commit, project: project2) + end + + describe 'without an explicit start date' do + benchmark_subject { described_class.trending.to_a } + + it { is_expected.to iterate_per_second(iterations) } + end + + describe 'with an explicit start date' do + let(:date) { 1.month.ago } + + benchmark_subject { described_class.trending(date).to_a } + + it { is_expected.to iterate_per_second(iterations) } + end + end + describe '.find_with_namespace' do let(:group) { create(:group, name: 'sisinmaru') } let(:project) { create(:project, name: 'maru', namespace: group) } diff --git a/spec/finders/trending_projects_finder_spec.rb b/spec/finders/trending_projects_finder_spec.rb new file mode 100644 index 00000000000..a49cbfd5160 --- /dev/null +++ b/spec/finders/trending_projects_finder_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe TrendingProjectsFinder do + let(:user) { build(:user) } + + describe '#execute' do + describe 'without an explicit start date' do + subject { described_class.new } + + it 'returns the trending projects' do + relation = double(:ar_relation) + + allow(subject).to receive(:projects_for) + .with(user) + .and_return(relation) + + allow(relation).to receive(:trending) + .with(an_instance_of(ActiveSupport::TimeWithZone)) + end + end + + describe 'with an explicit start date' do + let(:date) { 2.months.ago } + + subject { described_class.new } + + it 'returns the trending projects' do + relation = double(:ar_relation) + + allow(subject).to receive(:projects_for) + .with(user) + .and_return(relation) + + allow(relation).to receive(:trending) + .with(date) + end + end + end +end diff --git a/spec/javascripts/behaviors/quick_submit_spec.js.coffee b/spec/javascripts/behaviors/quick_submit_spec.js.coffee new file mode 100644 index 00000000000..09708c12ed4 --- /dev/null +++ b/spec/javascripts/behaviors/quick_submit_spec.js.coffee @@ -0,0 +1,70 @@ +#= require behaviors/quick_submit + +describe 'Quick Submit behavior', -> + fixture.preload('behaviors/quick_submit.html') + + beforeEach -> + fixture.load('behaviors/quick_submit.html') + + # Prevent a form submit from moving us off the testing page + $('form').submit (e) -> e.preventDefault() + + @spies = { + submit: spyOnEvent('form', 'submit') + } + + it 'does not respond to other keyCodes', -> + $('input').trigger(keydownEvent(keyCode: 32)) + + expect(@spies.submit).not.toHaveBeenTriggered() + + it 'does not respond to Enter alone', -> + $('input').trigger(keydownEvent(ctrlKey: false, metaKey: false)) + + expect(@spies.submit).not.toHaveBeenTriggered() + + it 'does not respond to repeated events', -> + $('input').trigger(keydownEvent(repeat: true)) + + expect(@spies.submit).not.toHaveBeenTriggered() + + it 'disables submit buttons', -> + $('textarea').trigger(keydownEvent()) + + expect($('input[type=submit]')).toBeDisabled() + expect($('button[type=submit]')).toBeDisabled() + + # We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll + # only run the tests that apply to the current platform + if navigator.userAgent.match(/Macintosh/) + it 'responds to Meta+Enter', -> + $('input').trigger(keydownEvent()) + + expect(@spies.submit).toHaveBeenTriggered() + + it 'excludes other modifier keys', -> + $('input').trigger(keydownEvent(altKey: true)) + $('input').trigger(keydownEvent(ctrlKey: true)) + $('input').trigger(keydownEvent(shiftKey: true)) + + expect(@spies.submit).not.toHaveBeenTriggered() + else + it 'responds to Ctrl+Enter', -> + $('input').trigger(keydownEvent()) + + expect(@spies.submit).toHaveBeenTriggered() + + it 'excludes other modifier keys', -> + $('input').trigger(keydownEvent(altKey: true)) + $('input').trigger(keydownEvent(metaKey: true)) + $('input').trigger(keydownEvent(shiftKey: true)) + + expect(@spies.submit).not.toHaveBeenTriggered() + + keydownEvent = (options) -> + if navigator.userAgent.match(/Macintosh/) + defaults = { keyCode: 13, metaKey: true } + else + defaults = { keyCode: 13, ctrlKey: true } + + $.Event('keydown', $.extend({}, defaults, options)) diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml new file mode 100644 index 00000000000..b80a28a33ea --- /dev/null +++ b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml @@ -0,0 +1,6 @@ +%form{ action: '/foo' } + %input.js-quick-submit{ type: 'text' } + %textarea.js-quick-submit + + %input{ type: 'submit'} Submit + %button.btn{ type: 'submit' } Submit diff --git a/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee index 47b41dd2c81..90b02a6aec5 100644 --- a/spec/javascripts/spec_helper.coffee +++ b/spec/javascripts/spec_helper.coffee @@ -9,6 +9,7 @@ # require the specific files that are being used in the spec that tests them. #= require jquery +#= require jquery.turbolinks #= require bootstrap #= require underscore diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8b5d2c3a1c1..f93935ebe3b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -423,4 +423,42 @@ describe Project do it { expect(project.gitlab_ci?).to be_truthy } it { expect(project.gitlab_ci_project).to be_a(Ci::Project) } end + + describe '.trending' do + let(:group) { create(:group) } + let(:project1) { create(:empty_project, :public, group: group) } + let(:project2) { create(:empty_project, :public, group: group) } + + before do + 2.times do + create(:note_on_commit, project: project1) + end + + create(:note_on_commit, project: project2) + end + + describe 'without an explicit start date' do + subject { described_class.trending.to_a } + + it 'sorts Projects by the amount of notes in descending order' do + expect(subject).to eq([project1, project2]) + end + end + + describe 'with an explicit start date' do + let(:date) { 2.months.ago } + + subject { described_class.trending(date).to_a } + + before do + 2.times do + create(:note_on_commit, project: project2, created_at: date) + end + end + + it 'sorts Projects by the amount of notes in descending order' do + expect(subject).to eq([project2, project1]) + end + end + end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 48c49e2f717..a31fc1e4b07 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -13,8 +13,8 @@ describe SystemHooksService do it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :email, :user_id) } it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } - it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) } - it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) } + it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) } + it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) } it { expect(event_data(key, :create)).to include(:username, :key, :id) } it { expect(event_data(key, :destroy)).to include(:username, :key, :id) } |