summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG4
-rw-r--r--Gemfile12
-rw-r--r--Gemfile.lock38
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js.coffee29
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js.coffee3
-rw-r--r--app/assets/javascripts/notes.js.coffee7
-rw-r--r--app/assets/stylesheets/base/variables.scss62
-rw-r--r--app/assets/stylesheets/ci/builds.scss1
-rw-r--r--app/assets/stylesheets/ci/xterm.scss2
-rw-r--r--app/assets/stylesheets/generic/buttons.scss154
-rw-r--r--app/assets/stylesheets/pages/commits.scss3
-rw-r--r--app/finders/trending_projects_finder.rb11
-rw-r--r--app/models/project.rb14
-rw-r--r--app/services/system_hooks_service.rb1
-rw-r--r--app/views/help/ui.html.haml52
-rw-r--r--app/views/projects/blob/_editor.html.haml2
-rw-r--r--app/views/projects/blob/new.html.haml2
-rw-r--r--app/views/projects/labels/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/milestones/_form.html.haml4
-rw-r--r--app/views/projects/notes/_edit_form.html.haml2
-rw-r--r--app/views/projects/notes/_form.html.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml2
-rw-r--r--app/views/shared/_commit_message_container.html.haml2
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--config/application.rb2
-rw-r--r--doc/system_hooks/system_hooks.md42
-rw-r--r--spec/benchmarks/finders/trending_projects_finder_spec.rb14
-rw-r--r--spec/benchmarks/models/project_spec.rb30
-rw-r--r--spec/finders/trending_projects_finder_spec.rb39
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js.coffee70
-rw-r--r--spec/javascripts/fixtures/behaviors/quick_submit.html.haml6
-rw-r--r--spec/javascripts/spec_helper.coffee1
-rw-r--r--spec/models/project_spec.rb38
-rw-r--r--spec/services/system_hooks_service_spec.rb4
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
diff --git a/Gemfile b/Gemfile
index 26ffb8c4b36..044dc30ecd4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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 &amp; 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) }