From 141e946c3da97c7af02aaca5324c6e4ce7362a04 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 9 Dec 2015 16:45:51 +0100 Subject: Storing of application metrics in InfluxDB This adds the ability to write application metrics (e.g. SQL timings) to InfluxDB. These metrics can in turn be visualized using Grafana, or really anything else that can read from InfluxDB. These metrics can be used to track application performance over time, between different Ruby versions, different GitLab versions, etc. == Transaction Metrics Currently the following is tracked on a per transaction basis (a transaction is a Rails request or a single Sidekiq job): * Timings per query along with the raw (obfuscated) SQL and information about what file the query originated from. * Timings per view along with the path of the view and information about what file triggered the rendering process. * The duration of a request itself along with the controller/worker class and method name. * The duration of any instrumented method calls (more below). == Sampled Metrics Certain metrics can't be directly associated with a transaction. For example, a process' total memory usage is unrelated to any running transactions. While a transaction can result in the memory usage going up there's no accurate way to determine what transaction is to blame, this becomes especially problematic in multi-threaded environments. To solve this problem there's a separate thread that takes samples at a fixed interval. This thread (using the class Gitlab::Metrics::Sampler) currently tracks the following: * The process' total memory usage. * The number of file descriptors opened by the process. * The amount of Ruby objects (using ObjectSpace.count_objects). * GC statistics such as timings, heap slots, etc. The default/current interval is 15 seconds, any smaller interval might put too much pressure on InfluxDB (especially when running dozens of processes). == Method Instrumentation While currently not yet used methods can be instrumented to track how long they take to run. Unlike the likes of New Relic this doesn't require modifying the source code (e.g. including modules), it all happens from the outside. For example, to track `User.by_login` we'd add the following code somewhere in an initializer: Gitlab::Metrics::Instrumentation. instrument_method(User, :by_login) to instead instrument an instance method: Gitlab::Metrics::Instrumentation. instrument_instance_method(User, :save) Instrumentation for either all public model methods or a few crucial ones will be added in the near future, I simply haven't gotten to doing so just yet. == Configuration By default metrics are disabled. This means users don't have to bother setting anything up if they don't want to. Metrics can be enabled by editing one's gitlab.yml configuration file (see config/gitlab.yml.example for example settings). == Writing Data To InfluxDB Because InfluxDB is still a fairly young product I expect the worse. Data loss, unexpected reboots, the database not responding, you name it. Because of this data is _not_ written to InfluxDB directly, instead it's queued and processed by Sidekiq. This ensures that users won't notice anything when InfluxDB is giving trouble. The metrics worker can be started in a standalone manner as following: bundle exec sidekiq -q metrics The corresponding class is called MetricsWorker. --- Gemfile | 6 ++ Gemfile.lock | 30 ++++--- Procfile | 2 +- app/workers/metrics_worker.rb | 29 +++++++ config/gitlab.yml.example | 17 ++++ config/initializers/metrics.rb | 25 ++++++ lib/gitlab/metrics.rb | 52 ++++++++++++ lib/gitlab/metrics/delta.rb | 32 ++++++++ lib/gitlab/metrics/instrumentation.rb | 47 +++++++++++ lib/gitlab/metrics/metric.rb | 34 ++++++++ lib/gitlab/metrics/obfuscated_sql.rb | 39 +++++++++ lib/gitlab/metrics/rack_middleware.rb | 49 ++++++++++++ lib/gitlab/metrics/sampler.rb | 77 ++++++++++++++++++ lib/gitlab/metrics/sidekiq_middleware.rb | 30 +++++++ lib/gitlab/metrics/subscribers/action_view.rb | 48 +++++++++++ lib/gitlab/metrics/subscribers/active_record.rb | 43 ++++++++++ lib/gitlab/metrics/subscribers/method_call.rb | 42 ++++++++++ lib/gitlab/metrics/system.rb | 35 ++++++++ lib/gitlab/metrics/transaction.rb | 66 ++++++++++++++++ spec/lib/gitlab/metrics/delta_spec.rb | 16 ++++ spec/lib/gitlab/metrics/instrumentation_spec.rb | 91 +++++++++++++++++++++ spec/lib/gitlab/metrics/metric_spec.rb | 57 ++++++++++++++ spec/lib/gitlab/metrics/obfuscated_sql_spec.rb | 79 +++++++++++++++++++ spec/lib/gitlab/metrics/rack_middleware_spec.rb | 63 +++++++++++++++ spec/lib/gitlab/metrics/sampler_spec.rb | 92 ++++++++++++++++++++++ spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb | 34 ++++++++ .../gitlab/metrics/subscribers/action_view_spec.rb | 32 ++++++++ .../metrics/subscribers/active_record_spec.rb | 31 ++++++++ .../gitlab/metrics/subscribers/method_call_spec.rb | 41 ++++++++++ spec/lib/gitlab/metrics/system_spec.rb | 29 +++++++ spec/lib/gitlab/metrics/transaction_spec.rb | 77 ++++++++++++++++++ spec/lib/gitlab/metrics_spec.rb | 36 +++++++++ 32 files changed, 1368 insertions(+), 13 deletions(-) create mode 100644 app/workers/metrics_worker.rb create mode 100644 config/initializers/metrics.rb create mode 100644 lib/gitlab/metrics.rb create mode 100644 lib/gitlab/metrics/delta.rb create mode 100644 lib/gitlab/metrics/instrumentation.rb create mode 100644 lib/gitlab/metrics/metric.rb create mode 100644 lib/gitlab/metrics/obfuscated_sql.rb create mode 100644 lib/gitlab/metrics/rack_middleware.rb create mode 100644 lib/gitlab/metrics/sampler.rb create mode 100644 lib/gitlab/metrics/sidekiq_middleware.rb create mode 100644 lib/gitlab/metrics/subscribers/action_view.rb create mode 100644 lib/gitlab/metrics/subscribers/active_record.rb create mode 100644 lib/gitlab/metrics/subscribers/method_call.rb create mode 100644 lib/gitlab/metrics/system.rb create mode 100644 lib/gitlab/metrics/transaction.rb create mode 100644 spec/lib/gitlab/metrics/delta_spec.rb create mode 100644 spec/lib/gitlab/metrics/instrumentation_spec.rb create mode 100644 spec/lib/gitlab/metrics/metric_spec.rb create mode 100644 spec/lib/gitlab/metrics/obfuscated_sql_spec.rb create mode 100644 spec/lib/gitlab/metrics/rack_middleware_spec.rb create mode 100644 spec/lib/gitlab/metrics/sampler_spec.rb create mode 100644 spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb create mode 100644 spec/lib/gitlab/metrics/subscribers/action_view_spec.rb create mode 100644 spec/lib/gitlab/metrics/subscribers/active_record_spec.rb create mode 100644 spec/lib/gitlab/metrics/subscribers/method_call_spec.rb create mode 100644 spec/lib/gitlab/metrics/system_spec.rb create mode 100644 spec/lib/gitlab/metrics/transaction_spec.rb create mode 100644 spec/lib/gitlab/metrics_spec.rb diff --git a/Gemfile b/Gemfile index b23e274081b..90db2c43006 100644 --- a/Gemfile +++ b/Gemfile @@ -208,6 +208,12 @@ gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' gem 'net-ssh', '~> 3.0.1' +# Metrics +group :metrics do + gem 'influxdb', '~> 0.2', require: false + gem 'connection_pool', '~> 2.0', require: false +end + group :development do gem "foreman" gem 'brakeman', '3.0.1', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 4dfff211134..7d592ba93a7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -65,7 +65,7 @@ GEM attr_encrypted (1.3.4) encryptor (>= 1.3.0) attr_required (1.0.0) - autoprefixer-rails (6.1.1) + autoprefixer-rails (6.1.2) execjs json awesome_print (1.2.0) @@ -102,7 +102,7 @@ GEM bundler-audit (0.4.0) bundler (~> 1.2) thor (~> 0.18) - byebug (8.2.0) + byebug (8.2.1) cal-heatmap-rails (0.0.1) capybara (2.4.4) mime-types (>= 1.16) @@ -117,6 +117,7 @@ GEM activemodel (>= 3.2.0) activesupport (>= 3.2.0) json (>= 1.7) + cause (0.1) charlock_holmes (0.7.3) chunky_png (1.3.5) cliver (0.3.2) @@ -140,10 +141,10 @@ GEM term-ansicolor (~> 1.3) thor (~> 0.19.1) tins (~> 1.6.0) - crack (0.4.2) + crack (0.4.3) safe_yaml (~> 1.0.0) creole (0.5.0) - d3_rails (3.5.6) + d3_rails (3.5.11) railties (>= 3.1.0) daemons (1.2.3) database_cleaner (1.4.1) @@ -230,7 +231,7 @@ GEM ipaddress (~> 0.5) nokogiri (~> 1.5, >= 1.5.11) opennebula - fog-brightbox (0.9.0) + fog-brightbox (0.10.1) fog-core (~> 1.22) fog-json inflecto (~> 0.0.2) @@ -249,7 +250,7 @@ GEM fog-core (>= 1.21.0) fog-json fog-xml (>= 0.0.1) - fog-sakuracloud (1.4.0) + fog-sakuracloud (1.5.0) fog-core fog-json fog-softlayer (1.0.2) @@ -277,11 +278,11 @@ GEM ruby-progressbar (~> 1.4) gemnasium-gitlab-service (0.2.6) rugged (~> 0.21) - gemojione (2.1.0) + gemojione (2.1.1) json get_process_mem (0.2.0) gherkin-ruby (0.3.2) - github-linguist (4.7.2) + github-linguist (4.7.3) charlock_holmes (~> 0.7.3) escape_utils (~> 1.1.0) mime-types (>= 1.19) @@ -298,7 +299,7 @@ GEM posix-spawn (~> 0.3) gitlab_emoji (0.2.0) gemojione (~> 2.1) - gitlab_git (7.2.21) + gitlab_git (7.2.22) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -370,6 +371,9 @@ GEM i18n (0.7.0) ice_nine (0.11.1) inflecto (0.0.2) + influxdb (0.2.3) + cause + json ipaddress (0.8.0) jquery-atwho-rails (1.3.2) jquery-rails (3.1.4) @@ -416,11 +420,11 @@ GEM net-ldap (0.12.1) net-ssh (3.0.1) netrc (0.11.0) - newrelic-grape (2.0.0) + newrelic-grape (2.1.0) grape newrelic_rpm newrelic_rpm (3.9.4.245) - nokogiri (1.6.7) + nokogiri (1.6.7.1) mini_portile2 (~> 2.0.0.rc2) nprogress-rails (0.1.6.7) oauth (0.4.7) @@ -783,7 +787,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - warden (1.2.3) + warden (1.2.4) rack (>= 1.0) web-console (2.2.1) activemodel (>= 4.0) @@ -836,6 +840,7 @@ DEPENDENCIES charlock_holmes (~> 0.7.3) coffee-rails (~> 4.1.0) colorize (~> 0.7.0) + connection_pool (~> 2.0) coveralls (~> 0.8.2) creole (~> 0.5.0) d3_rails (~> 3.5.5) @@ -873,6 +878,7 @@ DEPENDENCIES hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty (~> 0.13.3) + influxdb (~> 0.2) jquery-atwho-rails (~> 1.3.2) jquery-rails (~> 3.1.3) jquery-scrollto-rails (~> 1.4.3) diff --git a/Procfile b/Procfile index 9cfdee7040f..bbafdd33a2d 100644 --- a/Procfile +++ b/Procfile @@ -3,5 +3,5 @@ # lib/support/init.d, which call scripts in bin/ . # web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} -worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default +worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -q metrics # mail_room: bundle exec mail_room -q -c config/mail_room.yml diff --git a/app/workers/metrics_worker.rb b/app/workers/metrics_worker.rb new file mode 100644 index 00000000000..8fffe371572 --- /dev/null +++ b/app/workers/metrics_worker.rb @@ -0,0 +1,29 @@ +class MetricsWorker + include Sidekiq::Worker + + sidekiq_options queue: :metrics + + def perform(metrics) + prepared = prepare_metrics(metrics) + + Gitlab::Metrics.pool.with do |connection| + connection.write_points(prepared) + end + end + + def prepare_metrics(metrics) + metrics.map do |hash| + new_hash = hash.symbolize_keys + + new_hash[:tags].each do |key, value| + new_hash[:tags][key] = escape_value(value) + end + + new_hash + end + end + + def escape_value(value) + value.gsub('=', '\\=') + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index db378118f85..38b0920890f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -421,9 +421,22 @@ production: &base # # Ban an IP for one hour (3600s) after too many auth attempts # bantime: 3600 + metrics: + enabled: false + # The name of the InfluxDB database to store metrics in. + database: gitlab + # Credentials to use for logging in to InfluxDB. + # username: + # password: + # The amount of InfluxDB connections to open. + # pool_size: 16 + # The timeout of a connection in seconds. + # timeout: 10 development: <<: *base + metrics: + enabled: false test: <<: *base @@ -466,6 +479,10 @@ test: user_filter: '' group_base: 'ou=groups,dc=example,dc=com' admin_group: '' + metrics: + enabled: false staging: <<: *base + metrics: + enabled: false diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb new file mode 100644 index 00000000000..bde04232725 --- /dev/null +++ b/config/initializers/metrics.rb @@ -0,0 +1,25 @@ +if Gitlab::Metrics.enabled? + require 'influxdb' + require 'socket' + require 'connection_pool' + + # These are manually require'd so the classes are registered properly with + # ActiveSupport. + require 'gitlab/metrics/subscribers/action_view' + require 'gitlab/metrics/subscribers/active_record' + require 'gitlab/metrics/subscribers/method_call' + + Gitlab::Application.configure do |config| + config.middleware.use(Gitlab::Metrics::RackMiddleware) + end + + Sidekiq.configure_server do |config| + config.server_middleware do |chain| + chain.add Gitlab::Metrics::SidekiqMiddleware + end + end + + GC::Profiler.enable + + Gitlab::Metrics::Sampler.new.start +end diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb new file mode 100644 index 00000000000..08393995165 --- /dev/null +++ b/lib/gitlab/metrics.rb @@ -0,0 +1,52 @@ +module Gitlab + module Metrics + def self.pool_size + Settings.metrics['pool_size'] || 16 + end + + def self.timeout + Settings.metrics['timeout'] || 10 + end + + def self.enabled? + !!Settings.metrics['enabled'] + end + + def self.pool + @pool + end + + def self.hostname + @hostname + end + + def self.last_relative_application_frame + root = Rails.root.to_s + metrics = Rails.root.join('lib', 'gitlab', 'metrics').to_s + + frame = caller_locations.find do |l| + l.path.start_with?(root) && !l.path.start_with?(metrics) + end + + if frame + return frame.path.gsub(/^#{Rails.root.to_s}\/?/, ''), frame.lineno + else + return nil, nil + end + end + + @hostname = Socket.gethostname + + # When enabled this should be set before being used as the usual pattern + # "@foo ||= bar" is _not_ thread-safe. + if enabled? + @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do + db = Settings.metrics['database'] + user = Settings.metrics['username'] + pw = Settings.metrics['password'] + + InfluxDB::Client.new(db, username: user, password: pw) + end + end + end +end diff --git a/lib/gitlab/metrics/delta.rb b/lib/gitlab/metrics/delta.rb new file mode 100644 index 00000000000..bcf28eed84d --- /dev/null +++ b/lib/gitlab/metrics/delta.rb @@ -0,0 +1,32 @@ +module Gitlab + module Metrics + # Class for calculating the difference between two numeric values. + # + # Every call to `compared_with` updates the internal value. This makes it + # possible to use a single Delta instance to calculate the delta over time + # of an ever increasing number. + # + # Example usage: + # + # delta = Delta.new(0) + # + # delta.compared_with(10) # => 10 + # delta.compared_with(15) # => 5 + # delta.compared_with(20) # => 5 + class Delta + def initialize(value = 0) + @value = value + end + + # new_value - The value to compare with as a Numeric. + # + # Returns a new Numeric (depending on the type of `new_value`). + def compared_with(new_value) + delta = new_value - @value + @value = new_value + + delta + end + end + end +end diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb new file mode 100644 index 00000000000..1c2f84fb09a --- /dev/null +++ b/lib/gitlab/metrics/instrumentation.rb @@ -0,0 +1,47 @@ +module Gitlab + module Metrics + # Module for instrumenting methods. + # + # This module allows instrumenting of methods without having to actually + # alter the target code (e.g. by including modules). + # + # Example usage: + # + # Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login) + module Instrumentation + # Instruments a class method. + # + # mod - The module to instrument as a Module/Class. + # name - The name of the method to instrument. + def self.instrument_method(mod, name) + instrument(:class, mod, name) + end + + # Instruments an instance method. + # + # mod - The module to instrument as a Module/Class. + # name - The name of the method to instrument. + def self.instrument_instance_method(mod, name) + instrument(:instance, mod, name) + end + + def self.instrument(type, mod, name) + return unless Metrics.enabled? + + alias_name = "_original_#{name}" + target = type == :instance ? mod : mod.singleton_class + + target.class_eval do + alias_method(alias_name, name) + + define_method(name) do |*args, &block| + ActiveSupport::Notifications. + instrument("#{type}_method.method_call", module: mod, name: name) do + __send__(alias_name, *args, &block) + end + end + end + end + end + end +end diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb new file mode 100644 index 00000000000..f592f4e571f --- /dev/null +++ b/lib/gitlab/metrics/metric.rb @@ -0,0 +1,34 @@ +module Gitlab + module Metrics + # Class for storing details of a single metric (label, value, etc). + class Metric + attr_reader :series, :values, :tags, :created_at + + # series - The name of the series (as a String) to store the metric in. + # values - A Hash containing the values to store. + # tags - A Hash containing extra tags to add to the metrics. + def initialize(series, values, tags = {}) + @values = values + @series = series + @tags = tags + @created_at = Time.now.utc + end + + # Returns a Hash in a format that can be directly written to InfluxDB. + def to_hash + { + series: @series, + tags: @tags.merge( + hostname: Metrics.hostname, + ruby_engine: RUBY_ENGINE, + ruby_version: RUBY_VERSION, + gitlab_version: Gitlab::VERSION, + process_type: Sidekiq.server? ? 'sidekiq' : 'rails' + ), + values: @values, + timestamp: @created_at.to_i + } + end + end + end +end diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb new file mode 100644 index 00000000000..45f2e2bc62a --- /dev/null +++ b/lib/gitlab/metrics/obfuscated_sql.rb @@ -0,0 +1,39 @@ +module Gitlab + module Metrics + # Class for producing SQL queries with sensitive data stripped out. + class ObfuscatedSQL + REPLACEMENT = / + \d+(\.\d+)? # integers, floats + | '.+?' # single quoted strings + | \/.+?(? 'GET', 'REQUEST_URI' => '/foo' } } + + describe '#call' do + before do + expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish) + end + + it 'tracks a transaction' do + expect(app).to receive(:call).with(env).and_return('yay') + + expect(middleware.call(env)).to eq('yay') + end + + it 'tags a transaction with the name and action of a controller' do + klass = double(:klass, name: 'TestController') + controller = double(:controller, class: klass, action_name: 'show') + + env['action_controller.instance'] = controller + + allow(app).to receive(:call).with(env) + + expect(middleware).to receive(:tag_controller). + with(an_instance_of(Gitlab::Metrics::Transaction), env) + + middleware.call(env) + end + end + + describe '#transaction_from_env' do + let(:transaction) { middleware.transaction_from_env(env) } + + it 'returns a Transaction' do + expect(transaction).to be_an_instance_of(Gitlab::Metrics::Transaction) + end + + it 'tags the transaction with the request method and URI' do + expect(transaction.tags[:request_method]).to eq('GET') + expect(transaction.tags[:request_uri]).to eq('/foo') + end + end + + describe '#tag_controller' do + let(:transaction) { middleware.transaction_from_env(env) } + + it 'tags a transaction with the name and action of a controller' do + klass = double(:klass, name: 'TestController') + controller = double(:controller, class: klass, action_name: 'show') + + env['action_controller.instance'] = controller + + middleware.tag_controller(transaction, env) + + expect(transaction.tags[:action]).to eq('TestController#show') + end + end +end diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb new file mode 100644 index 00000000000..b486d38870f --- /dev/null +++ b/spec/lib/gitlab/metrics/sampler_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Sampler do + let(:sampler) { described_class.new(5) } + + describe '#start' do + it 'gathers a sample at a given interval' do + expect(sampler).to receive(:sleep).with(5) + expect(sampler).to receive(:sample) + expect(sampler).to receive(:loop).and_yield + + sampler.start.join + end + end + + describe '#sample' do + it 'samples various statistics' do + expect(sampler).to receive(:sample_memory_usage) + expect(sampler).to receive(:sample_file_descriptors) + expect(sampler).to receive(:sample_objects) + expect(sampler).to receive(:sample_gc) + expect(sampler).to receive(:flush) + + sampler.sample + end + + it 'clears any GC profiles' do + expect(sampler).to receive(:flush) + expect(GC::Profiler).to receive(:clear) + + sampler.sample + end + end + + describe '#flush' do + it 'schedules the metrics using Sidekiq' do + expect(MetricsWorker).to receive(:perform_async). + with([an_instance_of(Hash)]) + + sampler.sample_memory_usage + sampler.flush + end + end + + describe '#sample_memory_usage' do + it 'adds a metric containing the memory usage' do + expect(Gitlab::Metrics::System).to receive(:memory_usage). + and_return(9000) + + expect(Gitlab::Metrics::Metric).to receive(:new). + with('memory_usage', value: 9000). + and_call_original + + sampler.sample_memory_usage + end + end + + describe '#sample_file_descriptors' do + it 'adds a metric containing the amount of open file descriptors' do + expect(Gitlab::Metrics::System).to receive(:file_descriptor_count). + and_return(4) + + expect(Gitlab::Metrics::Metric).to receive(:new). + with('file_descriptors', value: 4). + and_call_original + + sampler.sample_file_descriptors + end + end + + describe '#sample_objects' do + it 'adds a metric containing the amount of allocated objects' do + expect(Gitlab::Metrics::Metric).to receive(:new). + with('object_counts', an_instance_of(Hash)). + and_call_original + + sampler.sample_objects + end + end + + describe '#sample_gc' do + it 'adds a metric containing garbage collection statistics' do + expect(GC::Profiler).to receive(:total_time).and_return(0.24) + + expect(Gitlab::Metrics::Metric).to receive(:new). + with('gc_statistics', an_instance_of(Hash)). + and_call_original + + sampler.sample_gc + end + end +end diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb new file mode 100644 index 00000000000..05214efc565 --- /dev/null +++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::Metrics::SidekiqMiddleware do + let(:middleware) { described_class.new } + + describe '#call' do + it 'tracks the transaction' do + worker = Class.new.new + + expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish) + + middleware.call(worker, 'test', :test) { nil } + end + + it 'does not track jobs of the MetricsWorker' do + worker = MetricsWorker.new + + expect(Gitlab::Metrics::Transaction).to_not receive(:new) + + middleware.call(worker, 'test', :test) { nil } + end + end + + describe '#tag_worker' do + it 'adds the worker class and action to the transaction' do + trans = Gitlab::Metrics::Transaction.new + worker = double(:worker, class: double(:class, name: 'TestWorker')) + + expect(trans).to receive(:add_tag).with(:action, 'TestWorker#perform') + + middleware.tag_worker(trans, worker) + end + end +end diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb new file mode 100644 index 00000000000..77f3e69d523 --- /dev/null +++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Subscribers::ActionView do + let(:transaction) { Gitlab::Metrics::Transaction.new } + + let(:subscriber) { described_class.new } + + let(:event) do + root = Rails.root.to_s + + double(:event, duration: 2.1, + payload: { identifier: "#{root}/app/views/x.html.haml" }) + end + + before do + allow(subscriber).to receive(:current_transaction).and_return(transaction) + + allow(Gitlab::Metrics).to receive(:last_relative_application_frame). + and_return(['app/views/x.html.haml', 4]) + end + + describe '#render_template' do + it 'tracks rendering of a template' do + values = { duration: 2.1, file: 'app/views/x.html.haml', line: 4 } + + expect(transaction).to receive(:add_metric). + with(described_class::SERIES, values, path: 'app/views/x.html.haml') + + subscriber.render_template(event) + end + end +end diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb new file mode 100644 index 00000000000..58e8e84df9b --- /dev/null +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Subscribers::ActiveRecord do + let(:transaction) { Gitlab::Metrics::Transaction.new } + + let(:subscriber) { described_class.new } + + let(:event) do + double(:event, duration: 0.2, + payload: { sql: 'SELECT * FROM users WHERE id = 10' }) + end + + before do + allow(subscriber).to receive(:current_transaction).and_return(transaction) + + allow(Gitlab::Metrics).to receive(:last_relative_application_frame). + and_return(['app/models/foo.rb', 4]) + end + + describe '#sql' do + it 'tracks the execution of a SQL query' do + values = { duration: 0.2, file: 'app/models/foo.rb', line: 4 } + sql = 'SELECT * FROM users WHERE id = ?' + + expect(transaction).to receive(:add_metric). + with(described_class::SERIES, values, sql: sql) + + subscriber.sql(event) + end + end +end diff --git a/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb b/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb new file mode 100644 index 00000000000..65de95d6d1e --- /dev/null +++ b/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Subscribers::MethodCall do + let(:transaction) { Gitlab::Metrics::Transaction.new } + + let(:subscriber) { described_class.new } + + let(:event) do + double(:event, duration: 0.2, + payload: { module: double(:mod, name: 'Foo'), name: :foo }) + end + + before do + allow(subscriber).to receive(:current_transaction).and_return(transaction) + + allow(Gitlab::Metrics).to receive(:last_relative_application_frame). + and_return(['app/models/foo.rb', 4]) + end + + describe '#instance_method' do + it 'tracks the execution of an instance method' do + values = { duration: 0.2, file: 'app/models/foo.rb', line: 4 } + + expect(transaction).to receive(:add_metric). + with(described_class::SERIES, values, method: 'Foo#foo') + + subscriber.instance_method(event) + end + end + + describe '#class_method' do + it 'tracks the execution of a class method' do + values = { duration: 0.2, file: 'app/models/foo.rb', line: 4 } + + expect(transaction).to receive(:add_metric). + with(described_class::SERIES, values, method: 'Foo.foo') + + subscriber.class_method(event) + end + end +end diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb new file mode 100644 index 00000000000..f8c1d956ca1 --- /dev/null +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Gitlab::Metrics::System do + if File.exist?('/proc') + describe '.memory_usage' do + it "returns the process' memory usage in bytes" do + expect(described_class.memory_usage).to be > 0 + end + end + + describe '.file_descriptor_count' do + it 'returns the amount of open file descriptors' do + expect(described_class.file_descriptor_count).to be > 0 + end + end + else + describe '.memory_usage' do + it 'returns 0.0' do + expect(described_class.memory_usage).to eq(0.0) + end + end + + describe '.file_descriptor_count' do + it 'returns 0' do + expect(described_class.file_descriptor_count).to eq(0) + end + end + end +end diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb new file mode 100644 index 00000000000..5f17ff8ee75 --- /dev/null +++ b/spec/lib/gitlab/metrics/transaction_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Transaction do + let(:transaction) { described_class.new } + + describe '#duration' do + it 'returns the duration of a transaction in seconds' do + transaction.run { sleep(0.5) } + + expect(transaction.duration).to be >= 0.5 + end + end + + describe '#run' do + it 'yields the supplied block' do + expect { |b| transaction.run(&b) }.to yield_control + end + + it 'stores the transaction in the current thread' do + transaction.run do + expect(Thread.current[described_class::THREAD_KEY]).to eq(transaction) + end + end + + it 'removes the transaction from the current thread upon completion' do + transaction.run { } + + expect(Thread.current[described_class::THREAD_KEY]).to be_nil + end + end + + describe '#add_metric' do + it 'adds a metric tagged with the transaction UUID' do + expect(Gitlab::Metrics::Metric).to receive(:new). + with('foo', { number: 10 }, { transaction_id: transaction.uuid }) + + transaction.add_metric('foo', number: 10) + end + end + + describe '#add_tag' do + it 'adds a tag' do + transaction.add_tag(:foo, 'bar') + + expect(transaction.tags).to eq({ foo: 'bar' }) + end + end + + describe '#finish' do + it 'tracks the transaction details and submits them to Sidekiq' do + expect(transaction).to receive(:track_self) + expect(transaction).to receive(:submit) + + transaction.finish + end + end + + describe '#track_self' do + it 'adds a metric for the transaction itself' do + expect(transaction).to receive(:add_metric). + with(described_class::SERIES, { duration: transaction.duration }, {}) + + transaction.track_self + end + end + + describe '#submit' do + it 'submits the metrics to Sidekiq' do + transaction.track_self + + expect(MetricsWorker).to receive(:perform_async). + with([an_instance_of(Hash)]) + + transaction.submit + end + end +end diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb new file mode 100644 index 00000000000..ebc69f8a75f --- /dev/null +++ b/spec/lib/gitlab/metrics_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::Metrics do + describe '.pool_size' do + it 'returns a Fixnum' do + expect(described_class.pool_size).to be_an_instance_of(Fixnum) + end + end + + describe '.timeout' do + it 'returns a Fixnum' do + expect(described_class.timeout).to be_an_instance_of(Fixnum) + end + end + + describe '.enabled?' do + it 'returns a boolean' do + expect([true, false].include?(described_class.enabled?)).to eq(true) + end + end + + describe '.hostname' do + it 'returns a String containing the hostname' do + expect(described_class.hostname).to eq(Socket.gethostname) + end + end + + describe '.last_relative_application_frame' do + it 'returns an Array containing a file path and line number' do + file, line = described_class.last_relative_application_frame + + expect(line).to eq(30) + expect(file).to eq('spec/lib/gitlab/metrics_spec.rb') + end + end +end -- cgit v1.2.3 From 60a6a240ea21cbfa564b9373b1c3bb57316aae46 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 13:25:16 +0100 Subject: Improved last_relative_application_frame timings The previous setup wasn't exactly fast, resulting in instrumented method calls taking about 600 times longer than non instrumented calls (including any ActiveSupport code involved). With this commit this slowdown has been reduced to around 185 times. --- lib/gitlab/metrics.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 08393995165..007429fa194 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -1,5 +1,9 @@ module Gitlab module Metrics + RAILS_ROOT = Rails.root.to_s + METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s + PATH_REGEX = /^#{RAILS_ROOT}\/?/ + def self.pool_size Settings.metrics['pool_size'] || 16 end @@ -20,16 +24,15 @@ module Gitlab @hostname end + # Returns a relative path and line number based on the last application call + # frame. def self.last_relative_application_frame - root = Rails.root.to_s - metrics = Rails.root.join('lib', 'gitlab', 'metrics').to_s - frame = caller_locations.find do |l| - l.path.start_with?(root) && !l.path.start_with?(metrics) + l.path.start_with?(RAILS_ROOT) && !l.path.start_with?(METRICS_ROOT) end if frame - return frame.path.gsub(/^#{Rails.root.to_s}\/?/, ''), frame.lineno + return frame.path.sub(PATH_REGEX, ''), frame.lineno else return nil, nil end -- cgit v1.2.3 From b66a16c8384b64eabeb04f3f32017581e4711eb8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 13:38:45 +0100 Subject: Use string evaluation for method instrumentation This is faster than using define_method since we don't have to keep block bindings around. --- lib/gitlab/metrics/instrumentation.rb | 16 +++++++++------- lib/gitlab/metrics/subscribers/method_call.rb | 4 ++-- spec/lib/gitlab/metrics/instrumentation_spec.rb | 6 ++++-- spec/lib/gitlab/metrics/subscribers/method_call_spec.rb | 3 +-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 1c2f84fb09a..982e35adfc9 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -31,16 +31,18 @@ module Gitlab alias_name = "_original_#{name}" target = type == :instance ? mod : mod.singleton_class - target.class_eval do - alias_method(alias_name, name) + target.class_eval <<-EOF, __FILE__, __LINE__ + 1 + alias_method :#{alias_name}, :#{name} - define_method(name) do |*args, &block| - ActiveSupport::Notifications. - instrument("#{type}_method.method_call", module: mod, name: name) do - __send__(alias_name, *args, &block) + def #{name}(*args, &block) + ActiveSupport::Notifications + .instrument("#{type}_method.method_call", + module: #{mod.name.inspect}, + name: #{name.inspect}) do + #{alias_name}(*args, &block) end end - end + EOF end end end diff --git a/lib/gitlab/metrics/subscribers/method_call.rb b/lib/gitlab/metrics/subscribers/method_call.rb index 1606134b7e5..0094ed0dc6a 100644 --- a/lib/gitlab/metrics/subscribers/method_call.rb +++ b/lib/gitlab/metrics/subscribers/method_call.rb @@ -10,7 +10,7 @@ module Gitlab def instance_method(event) return unless current_transaction - label = "#{event.payload[:module].name}##{event.payload[:name]}" + label = "#{event.payload[:module]}##{event.payload[:name]}" add_metric(label, event.duration) end @@ -18,7 +18,7 @@ module Gitlab def class_method(event) return unless current_transaction - label = "#{event.payload[:module].name}.#{event.payload[:name]}" + label = "#{event.payload[:module]}.#{event.payload[:name]}" add_metric(label, event.duration) end diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index eb31c9e52cd..3e7e9869e30 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -11,6 +11,8 @@ describe Gitlab::Metrics::Instrumentation do text end end + + allow(@dummy).to receive(:name).and_return('Dummy') end describe '.instrument_method' do @@ -31,7 +33,7 @@ describe Gitlab::Metrics::Instrumentation do it 'fires an ActiveSupport notification upon calling the method' do expect(ActiveSupport::Notifications).to receive(:instrument). - with('class_method.method_call', module: @dummy, name: :foo) + with('class_method.method_call', module: 'Dummy', name: :foo) @dummy.foo end @@ -69,7 +71,7 @@ describe Gitlab::Metrics::Instrumentation do it 'fires an ActiveSupport notification upon calling the method' do expect(ActiveSupport::Notifications).to receive(:instrument). - with('instance_method.method_call', module: @dummy, name: :bar) + with('instance_method.method_call', module: 'Dummy', name: :bar) @dummy.new.bar end diff --git a/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb b/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb index 65de95d6d1e..5c76eb3ba50 100644 --- a/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb @@ -6,8 +6,7 @@ describe Gitlab::Metrics::Subscribers::MethodCall do let(:subscriber) { described_class.new } let(:event) do - double(:event, duration: 0.2, - payload: { module: double(:mod, name: 'Foo'), name: :foo }) + double(:event, duration: 0.2, payload: { module: 'Foo', name: :foo }) end before do -- cgit v1.2.3 From 1b077d2d81bd25fe37492ea56c8bd884f944ce52 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 15:16:56 +0100 Subject: Use custom code for instrumenting method calls The use of ActiveSupport would slow down instrumented method calls by about 180x due to: 1. ActiveSupport itself not being the fastest thing on the planet 2. caller_locations() having quite some overhead The use of caller_locations() has been removed because it's not _that_ useful since we already know the full namespace of receivers and the names of the called methods. The use of ActiveSupport has been replaced with some custom code that's generated using eval() (which can be quite a bit faster than using define_method). This new setup results in instrumented methods only being about 35-40x slower (compared to non instrumented methods). --- config/initializers/metrics.rb | 1 - lib/gitlab/metrics/instrumentation.rb | 37 +++++++++++++++---- lib/gitlab/metrics/subscribers/method_call.rb | 42 ---------------------- spec/lib/gitlab/metrics/instrumentation_spec.rb | 22 ++++++++---- .../gitlab/metrics/subscribers/method_call_spec.rb | 40 --------------------- 5 files changed, 47 insertions(+), 95 deletions(-) delete mode 100644 lib/gitlab/metrics/subscribers/method_call.rb delete mode 100644 spec/lib/gitlab/metrics/subscribers/method_call_spec.rb diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index bde04232725..556e968137e 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -7,7 +7,6 @@ if Gitlab::Metrics.enabled? # ActiveSupport. require 'gitlab/metrics/subscribers/action_view' require 'gitlab/metrics/subscribers/active_record' - require 'gitlab/metrics/subscribers/method_call' Gitlab::Application.configure do |config| config.middleware.use(Gitlab::Metrics::RackMiddleware) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 982e35adfc9..5b56f2e8513 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -9,6 +9,8 @@ module Gitlab # # Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login) module Instrumentation + SERIES = 'method_calls' + # Instruments a class method. # # mod - The module to instrument as a Module/Class. @@ -31,19 +33,42 @@ module Gitlab alias_name = "_original_#{name}" target = type == :instance ? mod : mod.singleton_class + if type == :instance + target = mod + label = "#{mod.name}##{name}" + else + target = mod.singleton_class + label = "#{mod.name}.#{name}" + end + target.class_eval <<-EOF, __FILE__, __LINE__ + 1 alias_method :#{alias_name}, :#{name} def #{name}(*args, &block) - ActiveSupport::Notifications - .instrument("#{type}_method.method_call", - module: #{mod.name.inspect}, - name: #{name.inspect}) do - #{alias_name}(*args, &block) - end + trans = Gitlab::Metrics::Instrumentation.transaction + + if trans + start = Time.now + retval = #{alias_name}(*args, &block) + duration = (Time.now - start) * 1000.0 + + trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, + { duration: duration }, + method: #{label.inspect}) + + retval + else + #{alias_name}(*args, &block) + end end EOF end + + # Small layer of indirection to make it easier to stub out the current + # transaction. + def self.transaction + Transaction.current + end end end end diff --git a/lib/gitlab/metrics/subscribers/method_call.rb b/lib/gitlab/metrics/subscribers/method_call.rb deleted file mode 100644 index 0094ed0dc6a..00000000000 --- a/lib/gitlab/metrics/subscribers/method_call.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Gitlab - module Metrics - module Subscribers - # Class for tracking method call timings. - class MethodCall < ActiveSupport::Subscriber - attach_to :method_call - - SERIES = 'method_calls' - - def instance_method(event) - return unless current_transaction - - label = "#{event.payload[:module]}##{event.payload[:name]}" - - add_metric(label, event.duration) - end - - def class_method(event) - return unless current_transaction - - label = "#{event.payload[:module]}.#{event.payload[:name]}" - - add_metric(label, event.duration) - end - - private - - def add_metric(label, duration) - file, line = Metrics.last_relative_application_frame - - values = { duration: duration, file: file, line: line } - - current_transaction.add_metric(SERIES, values, method: label) - end - - def current_transaction - Transaction.current - end - end - end - end -end diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 3e7e9869e30..9308fb157d8 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Metrics::Instrumentation do + let(:transaction) { Gitlab::Metrics::Transaction.new } + before do @dummy = Class.new do def self.foo(text = 'foo') @@ -31,9 +33,13 @@ describe Gitlab::Metrics::Instrumentation do expect(@dummy.foo).to eq('foo') end - it 'fires an ActiveSupport notification upon calling the method' do - expect(ActiveSupport::Notifications).to receive(:instrument). - with('class_method.method_call', module: 'Dummy', name: :foo) + it 'tracks the call duration upon calling the method' do + allow(Gitlab::Metrics::Instrumentation).to receive(:transaction). + and_return(transaction) + + expect(transaction).to receive(:add_metric). + with(described_class::SERIES, an_instance_of(Hash), + method: 'Dummy.foo') @dummy.foo end @@ -69,9 +75,13 @@ describe Gitlab::Metrics::Instrumentation do expect(@dummy.new.bar).to eq('bar') end - it 'fires an ActiveSupport notification upon calling the method' do - expect(ActiveSupport::Notifications).to receive(:instrument). - with('instance_method.method_call', module: 'Dummy', name: :bar) + it 'tracks the call duration upon calling the method' do + allow(Gitlab::Metrics::Instrumentation).to receive(:transaction). + and_return(transaction) + + expect(transaction).to receive(:add_metric). + with(described_class::SERIES, an_instance_of(Hash), + method: 'Dummy#bar') @dummy.new.bar end diff --git a/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb b/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb deleted file mode 100644 index 5c76eb3ba50..00000000000 --- a/spec/lib/gitlab/metrics/subscribers/method_call_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Metrics::Subscribers::MethodCall do - let(:transaction) { Gitlab::Metrics::Transaction.new } - - let(:subscriber) { described_class.new } - - let(:event) do - double(:event, duration: 0.2, payload: { module: 'Foo', name: :foo }) - end - - before do - allow(subscriber).to receive(:current_transaction).and_return(transaction) - - allow(Gitlab::Metrics).to receive(:last_relative_application_frame). - and_return(['app/models/foo.rb', 4]) - end - - describe '#instance_method' do - it 'tracks the execution of an instance method' do - values = { duration: 0.2, file: 'app/models/foo.rb', line: 4 } - - expect(transaction).to receive(:add_metric). - with(described_class::SERIES, values, method: 'Foo#foo') - - subscriber.instance_method(event) - end - end - - describe '#class_method' do - it 'tracks the execution of a class method' do - values = { duration: 0.2, file: 'app/models/foo.rb', line: 4 } - - expect(transaction).to receive(:add_metric). - with(described_class::SERIES, values, method: 'Foo.foo') - - subscriber.class_method(event) - end - end -end -- cgit v1.2.3 From ad69ba57d6e7d52b2a44a20393c072538c299653 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 16:15:02 +0100 Subject: Proper method instrumentation for special symbols This ensures that methods such as "==" can be instrumented without producing syntax errors. --- lib/gitlab/metrics/instrumentation.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 5b56f2e8513..2ed75495650 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -30,7 +30,8 @@ module Gitlab def self.instrument(type, mod, name) return unless Metrics.enabled? - alias_name = "_original_#{name}" + name = name.to_sym + alias_name = :"_original_#{name}" target = type == :instance ? mod : mod.singleton_class if type == :instance @@ -42,14 +43,14 @@ module Gitlab end target.class_eval <<-EOF, __FILE__, __LINE__ + 1 - alias_method :#{alias_name}, :#{name} + alias_method #{alias_name.inspect}, #{name.inspect} def #{name}(*args, &block) trans = Gitlab::Metrics::Instrumentation.transaction if trans start = Time.now - retval = #{alias_name}(*args, &block) + retval = duration = (Time.now - start) * 1000.0 trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, @@ -58,7 +59,7 @@ module Gitlab retval else - #{alias_name}(*args, &block) + __send__(#{alias_name.inspect}, *args, &block) end end EOF -- cgit v1.2.3 From 5dbcb635a17aff6543150a66b597c75b819801e2 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 16:15:46 +0100 Subject: Methods for instrumenting multiple methods The methods Instrumentation.instrument_methods and Instrumentation.instrument_instance_methods can be used to instrument all methods of a module at once. --- lib/gitlab/metrics/instrumentation.rb | 23 +++++++++++++++++ spec/lib/gitlab/metrics/instrumentation_spec.rb | 34 +++++++++++++++++++++---- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 2ed75495650..12d5ede4be3 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -27,6 +27,29 @@ module Gitlab instrument(:instance, mod, name) end + # Instruments all public methods of a module. + # + # mod - The module to instrument. + def self.instrument_methods(mod) + mod.public_methods(false).each do |name| + instrument_method(mod, name) + end + end + + # Instruments all public instance methods of a module. + # + # mod - The module to instrument. + def self.instrument_instance_methods(mod) + mod.public_instance_methods(false).each do |name| + instrument_instance_method(mod, name) + end + end + + # Instruments a method. + # + # type - The type (:class or :instance) of method to instrument. + # mod - The module containing the method. + # name - The name of the method to instrument. def self.instrument(type, mod, name) return unless Metrics.enabled? diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 9308fb157d8..5427bacdc94 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -34,7 +34,7 @@ describe Gitlab::Metrics::Instrumentation do end it 'tracks the call duration upon calling the method' do - allow(Gitlab::Metrics::Instrumentation).to receive(:transaction). + allow(described_class).to receive(:transaction). and_return(transaction) expect(transaction).to receive(:add_metric). @@ -51,7 +51,7 @@ describe Gitlab::Metrics::Instrumentation do end it 'does not instrument the method' do - Gitlab::Metrics::Instrumentation.instrument_method(@dummy, :foo) + described_class.instrument_method(@dummy, :foo) expect(@dummy).to_not respond_to(:_original_foo) end @@ -63,7 +63,7 @@ describe Gitlab::Metrics::Instrumentation do before do allow(Gitlab::Metrics).to receive(:enabled?).and_return(true) - Gitlab::Metrics::Instrumentation. + described_class. instrument_instance_method(@dummy, :bar) end @@ -76,7 +76,7 @@ describe Gitlab::Metrics::Instrumentation do end it 'tracks the call duration upon calling the method' do - allow(Gitlab::Metrics::Instrumentation).to receive(:transaction). + allow(described_class).to receive(:transaction). and_return(transaction) expect(transaction).to receive(:add_metric). @@ -93,11 +93,35 @@ describe Gitlab::Metrics::Instrumentation do end it 'does not instrument the method' do - Gitlab::Metrics::Instrumentation. + described_class. instrument_instance_method(@dummy, :bar) expect(@dummy.method_defined?(:_original_bar)).to eq(false) end end end + + describe '.instrument_methods' do + before do + allow(Gitlab::Metrics).to receive(:enabled?).and_return(true) + end + + it 'instruments all public class methods' do + described_class.instrument_methods(@dummy) + + expect(@dummy).to respond_to(:_original_foo) + end + end + + describe '.instrument_instance_methods' do + before do + allow(Gitlab::Metrics).to receive(:enabled?).and_return(true) + end + + it 'instruments all public instance methods' do + described_class.instrument_instance_methods(@dummy) + + expect(@dummy.method_defined?(:_original_bar)).to eq(true) + end + end end -- cgit v1.2.3 From f43f3b89a633b5ceee4e71acba0c83ed5cb28963 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 16:16:48 +0100 Subject: Added Instrumentation.configure This makes it easier to instrument multiple modules without having to type the full namespace over and over again. --- lib/gitlab/metrics/instrumentation.rb | 4 ++++ spec/lib/gitlab/metrics/instrumentation_spec.rb | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 12d5ede4be3..8822a907967 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -11,6 +11,10 @@ module Gitlab module Instrumentation SERIES = 'method_calls' + def self.configure + yield self + end + # Instruments a class method. # # mod - The module to instrument as a Module/Class. diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 5427bacdc94..fdb0820b875 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -17,12 +17,20 @@ describe Gitlab::Metrics::Instrumentation do allow(@dummy).to receive(:name).and_return('Dummy') end + describe '.configure' do + it 'yields self' do + described_class.configure do |c| + expect(c).to eq(described_class) + end + end + end + describe '.instrument_method' do describe 'with metrics enabled' do before do allow(Gitlab::Metrics).to receive(:enabled?).and_return(true) - Gitlab::Metrics::Instrumentation.instrument_method(@dummy, :foo) + described_class.instrument_method(@dummy, :foo) end it 'renames the original method' do -- cgit v1.2.3 From 641761f1d6af5a94c0007e8d7463ee86fc047229 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 16:25:28 +0100 Subject: Only instrument methods defined directly When using instrument_methods/instrument_instance_methods we only want to instrument methods defined directly in a class, not those included via mixins (e.g. whatever RSpec throws in during development). In case an externally included method _has_ to be instrumented we can still use the regular instrument_method/instrument_instance_method methods. --- lib/gitlab/metrics/instrumentation.rb | 10 +++++++--- spec/lib/gitlab/metrics/instrumentation_spec.rb | 26 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 8822a907967..cf22aa93cdd 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -36,7 +36,9 @@ module Gitlab # mod - The module to instrument. def self.instrument_methods(mod) mod.public_methods(false).each do |name| - instrument_method(mod, name) + method = mod.method(name) + + instrument_method(mod, name) if method.owner == mod.singleton_class end end @@ -45,7 +47,9 @@ module Gitlab # mod - The module to instrument. def self.instrument_instance_methods(mod) mod.public_instance_methods(false).each do |name| - instrument_instance_method(mod, name) + method = mod.instance_method(name) + + instrument_instance_method(mod, name) if method.owner == mod end end @@ -77,7 +81,7 @@ module Gitlab if trans start = Time.now - retval = + retval = __send__(#{alias_name.inspect}, *args, &block) duration = (Time.now - start) * 1000.0 trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index fdb0820b875..80dc160ebd2 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -119,6 +119,19 @@ describe Gitlab::Metrics::Instrumentation do expect(@dummy).to respond_to(:_original_foo) end + + it 'only instruments methods directly defined in the module' do + mod = Module.new do + def kittens + end + end + + @dummy.extend(mod) + + described_class.instrument_methods(@dummy) + + expect(@dummy).to_not respond_to(:_original_kittens) + end end describe '.instrument_instance_methods' do @@ -131,5 +144,18 @@ describe Gitlab::Metrics::Instrumentation do expect(@dummy.method_defined?(:_original_bar)).to eq(true) end + + it 'only instruments methods directly defined in the module' do + mod = Module.new do + def kittens + end + end + + @dummy.include(mod) + + described_class.instrument_instance_methods(@dummy) + + expect(@dummy.method_defined?(:_original_kittens)).to eq(false) + end end end -- cgit v1.2.3 From 6dc25ad58c7bf2218cdda6d60061111a5365affb Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 17:09:20 +0100 Subject: Instrument Gitlab::Shel and Gitlab::Git --- config/initializers/metrics.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 556e968137e..0ac4299dcba 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -18,6 +18,18 @@ if Gitlab::Metrics.enabled? end end + Gitlab::Metrics::Instrumentation.configure do |config| + config.instrument_instance_methods(Gitlab::Shell) + + config.instrument_methods(Gitlab::Git) + + Gitlab::Git.constants.each do |name| + const = Gitlab::Git.const_get(name) + + config.instrument_methods(const) if const.is_a?(Module) + end + end + GC::Profiler.enable Gitlab::Metrics::Sampler.new.start -- cgit v1.2.3 From 09a311568abee739fae0c2577a9cf6aa01516977 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 10 Dec 2015 17:48:14 +0100 Subject: Track object count types as tags --- lib/gitlab/metrics/sampler.rb | 4 +++- spec/lib/gitlab/metrics/sampler_spec.rb | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/sampler.rb index 141953dc985..03afa6324dd 100644 --- a/lib/gitlab/metrics/sampler.rb +++ b/lib/gitlab/metrics/sampler.rb @@ -53,7 +53,9 @@ module Gitlab end def sample_objects - @metrics << Metric.new('object_counts', ObjectSpace.count_objects) + ObjectSpace.count_objects.each do |type, count| + @metrics << Metric.new('object_counts', { count: count }, type: type) + end end def sample_gc diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb index b486d38870f..319f287178d 100644 --- a/spec/lib/gitlab/metrics/sampler_spec.rb +++ b/spec/lib/gitlab/metrics/sampler_spec.rb @@ -71,7 +71,8 @@ describe Gitlab::Metrics::Sampler do describe '#sample_objects' do it 'adds a metric containing the amount of allocated objects' do expect(Gitlab::Metrics::Metric).to receive(:new). - with('object_counts', an_instance_of(Hash)). + with('object_counts', an_instance_of(Hash), an_instance_of(Hash)). + at_least(:once). and_call_original sampler.sample_objects -- cgit v1.2.3 From f932b781a79d9b829001c39bc214372b7efd8610 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 14 Dec 2015 12:31:56 +0100 Subject: Replace double quotes when obfuscating SQL InfluxDB escapes double quotes upon output which makes it a pain to deal with. This ensures that if we're using PostgreSQL we don't store any queries containing double quotes in InfluxDB, solving the escaping problem. --- lib/gitlab/metrics/obfuscated_sql.rb | 10 +++++++++- spec/lib/gitlab/metrics/obfuscated_sql_spec.rb | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb index 45f2e2bc62a..7b15670aa6b 100644 --- a/lib/gitlab/metrics/obfuscated_sql.rb +++ b/lib/gitlab/metrics/obfuscated_sql.rb @@ -30,9 +30,17 @@ module Gitlab regex = Regexp.union(regex, MYSQL_REPLACEMENTS) end - @sql.gsub(regex, '?').gsub(CONSECUTIVE) do |match| + sql = @sql.gsub(regex, '?').gsub(CONSECUTIVE) do |match| "#{match.count(',') + 1} values" end + + # InfluxDB escapes double quotes upon output, so lets get rid of them + # whenever we can. + if Gitlab::Database.postgresql? + sql = sql.gsub('"', '') + end + + sql end end end diff --git a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb index 6e9b62016d6..0f01ee588c9 100644 --- a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb +++ b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb @@ -75,5 +75,13 @@ describe Gitlab::Metrics::ObfuscatedSQL do expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)') end end + + if Gitlab::Database.postgresql? + it 'replaces double quotes' do + sql = described_class.new('SELECT "x" FROM "y"') + + expect(sql.to_s).to eq('SELECT x FROM y') + end + end end end -- cgit v1.2.3 From d0352e6604915a1a94fbe84252d1606f5adac57c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 14 Dec 2015 12:32:30 +0100 Subject: Added specs for MetricsWorker --- spec/workers/metrics_worker_spec.rb | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 spec/workers/metrics_worker_spec.rb diff --git a/spec/workers/metrics_worker_spec.rb b/spec/workers/metrics_worker_spec.rb new file mode 100644 index 00000000000..0d12516c1a3 --- /dev/null +++ b/spec/workers/metrics_worker_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe MetricsWorker do + let(:worker) { described_class.new } + + describe '#perform' do + it 'prepares and writes the metrics to InfluxDB' do + connection = double(:connection) + pool = double(:pool) + + expect(pool).to receive(:with).and_yield(connection) + expect(connection).to receive(:write_points).with(an_instance_of(Array)) + expect(Gitlab::Metrics).to receive(:pool).and_return(pool) + + worker.perform([{ 'series' => 'kittens', 'tags' => {} }]) + end + end + + describe '#prepare_metrics' do + it 'returns a Hash with the keys as Symbols' do + metrics = worker.prepare_metrics([{ 'values' => {}, 'tags' => {} }]) + + expect(metrics).to eq([{ values: {}, tags: {} }]) + end + + it 'escapes tag values' do + metrics = worker.prepare_metrics([ + { 'values' => {}, 'tags' => { 'foo' => 'bar=' } } + ]) + + expect(metrics).to eq([{ values: {}, tags: { 'foo' => 'bar\\=' } }]) + end + end + + describe '#escape_value' do + it 'escapes an equals sign' do + expect(worker.escape_value('foo=')).to eq('foo\\=') + end + end +end -- cgit v1.2.3 From 9f95ff0d90802467a04816f1d38e30770a026820 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 14 Dec 2015 16:51:38 +0100 Subject: Track location information as tags This allows the information to be displayed when using certain functions (e.g. top()) as well as making it easier to aggregate on a per file basis. --- lib/gitlab/metrics/subscribers/action_view.rb | 17 +++++++++++------ lib/gitlab/metrics/subscribers/active_record.rb | 17 +++++++++++------ spec/lib/gitlab/metrics/subscribers/action_view_spec.rb | 9 +++++++-- .../gitlab/metrics/subscribers/active_record_spec.rb | 5 +++-- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb index 2e88e4bea6a..7e0dcf99d92 100644 --- a/lib/gitlab/metrics/subscribers/action_view.rb +++ b/lib/gitlab/metrics/subscribers/action_view.rb @@ -16,10 +16,10 @@ module Gitlab private def track(event) - path = relative_path(event.payload[:identifier]) values = values_for(event) + tags = tags_for(event) - current_transaction.add_metric(SERIES, values, path: path) + current_transaction.add_metric(SERIES, values, tags) end def relative_path(path) @@ -27,16 +27,21 @@ module Gitlab end def values_for(event) - values = { duration: event.duration } + { duration: event.duration } + end + + def tags_for(event) + path = relative_path(event.payload[:identifier]) + tags = { view: path } file, line = Metrics.last_relative_application_frame if file and line - values[:file] = file - values[:line] = line + tags[:file] = file + tags[:line] = line end - values + tags end def current_transaction diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb index 3cc9b1addf6..d947c128ce2 100644 --- a/lib/gitlab/metrics/subscribers/active_record.rb +++ b/lib/gitlab/metrics/subscribers/active_record.rb @@ -13,25 +13,30 @@ module Gitlab def sql(event) return unless current_transaction - sql = ObfuscatedSQL.new(event.payload[:sql]).to_s values = values_for(event) + tags = tags_for(event) - current_transaction.add_metric(SERIES, values, sql: sql) + current_transaction.add_metric(SERIES, values, tags) end private def values_for(event) - values = { duration: event.duration } + { duration: event.duration } + end + + def tags_for(event) + sql = ObfuscatedSQL.new(event.payload[:sql]).to_s + tags = { sql: sql } file, line = Metrics.last_relative_application_frame if file and line - values[:file] = file - values[:line] = line + tags[:file] = file + tags[:line] = line end - values + tags end def current_transaction diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb index 77f3e69d523..c6cd584663f 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb @@ -21,10 +21,15 @@ describe Gitlab::Metrics::Subscribers::ActionView do describe '#render_template' do it 'tracks rendering of a template' do - values = { duration: 2.1, file: 'app/views/x.html.haml', line: 4 } + values = { duration: 2.1 } + tags = { + view: 'app/views/x.html.haml', + file: 'app/views/x.html.haml', + line: 4 + } expect(transaction).to receive(:add_metric). - with(described_class::SERIES, values, path: 'app/views/x.html.haml') + with(described_class::SERIES, values, tags) subscriber.render_template(event) end diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb index 58e8e84df9b..05b6cc14716 100644 --- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -19,11 +19,12 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do describe '#sql' do it 'tracks the execution of a SQL query' do - values = { duration: 0.2, file: 'app/models/foo.rb', line: 4 } sql = 'SELECT * FROM users WHERE id = ?' + values = { duration: 0.2 } + tags = { sql: sql, file: 'app/models/foo.rb', line: 4 } expect(transaction).to receive(:add_metric). - with(described_class::SERIES, values, sql: sql) + with(described_class::SERIES, values, tags) subscriber.sql(event) end -- cgit v1.2.3 From 5142c61707cc4169a3f8d9e378aacb8f88760db5 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 14 Dec 2015 16:52:05 +0100 Subject: Cast values to strings before escaping them This ensures that e.g. line numbers used in tags are first casted to strings. --- app/workers/metrics_worker.rb | 2 +- spec/workers/metrics_worker_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/workers/metrics_worker.rb b/app/workers/metrics_worker.rb index 8fffe371572..90a65579382 100644 --- a/app/workers/metrics_worker.rb +++ b/app/workers/metrics_worker.rb @@ -24,6 +24,6 @@ class MetricsWorker end def escape_value(value) - value.gsub('=', '\\=') + value.to_s.gsub('=', '\\=') end end diff --git a/spec/workers/metrics_worker_spec.rb b/spec/workers/metrics_worker_spec.rb index 0d12516c1a3..f5650494c7c 100644 --- a/spec/workers/metrics_worker_spec.rb +++ b/spec/workers/metrics_worker_spec.rb @@ -36,5 +36,9 @@ describe MetricsWorker do it 'escapes an equals sign' do expect(worker.escape_value('foo=')).to eq('foo\\=') end + + it 'casts values to Strings' do + expect(worker.escape_value(10)).to eq('10') + end end end -- cgit v1.2.3 From d67e2045a02d105bfbc7abf1805fe477eb9155ca Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 14 Dec 2015 17:37:20 +0100 Subject: Drop empty tag values from metrics InfluxDB throws an error when trying to store a list of tags where one or more have an empty value. --- app/workers/metrics_worker.rb | 6 +++++- spec/workers/metrics_worker_spec.rb | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/workers/metrics_worker.rb b/app/workers/metrics_worker.rb index 90a65579382..b15dc819c5c 100644 --- a/app/workers/metrics_worker.rb +++ b/app/workers/metrics_worker.rb @@ -16,7 +16,11 @@ class MetricsWorker new_hash = hash.symbolize_keys new_hash[:tags].each do |key, value| - new_hash[:tags][key] = escape_value(value) + if value.blank? + new_hash[:tags].delete(key) + else + new_hash[:tags][key] = escape_value(value) + end end new_hash diff --git a/spec/workers/metrics_worker_spec.rb b/spec/workers/metrics_worker_spec.rb index f5650494c7c..2acd0f8ba30 100644 --- a/spec/workers/metrics_worker_spec.rb +++ b/spec/workers/metrics_worker_spec.rb @@ -30,6 +30,14 @@ describe MetricsWorker do expect(metrics).to eq([{ values: {}, tags: { 'foo' => 'bar\\=' } }]) end + + it 'drops empty tags' do + metrics = worker.prepare_metrics([ + { 'values' => {}, 'tags' => { 'cats' => '', 'dogs' => nil }} + ]) + + expect(metrics).to eq([{ values: {}, tags: {} }]) + end end describe '#escape_value' do -- cgit v1.2.3 From 13dbd663acbbe91ddac77b650a90377cd12c54b8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 15 Dec 2015 16:31:24 +0100 Subject: Allow filtering of what methods to instrument This makes it possible to determine if a method should be instrumented or not using a block. --- lib/gitlab/metrics/instrumentation.rb | 19 +++++++++++++++++-- spec/lib/gitlab/metrics/instrumentation_spec.rb | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index cf22aa93cdd..91e09694cd8 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -33,23 +33,38 @@ module Gitlab # Instruments all public methods of a module. # + # This method optionally takes a block that can be used to determine if a + # method should be instrumented or not. The block is passed the receiving + # module and an UnboundMethod. If the block returns a non truthy value the + # method is not instrumented. + # # mod - The module to instrument. def self.instrument_methods(mod) mod.public_methods(false).each do |name| method = mod.method(name) - instrument_method(mod, name) if method.owner == mod.singleton_class + if method.owner == mod.singleton_class + if !block_given? || block_given? && yield(mod, method) + instrument_method(mod, name) + end + end end end # Instruments all public instance methods of a module. # + # See `instrument_methods` for more information. + # # mod - The module to instrument. def self.instrument_instance_methods(mod) mod.public_instance_methods(false).each do |name| method = mod.instance_method(name) - instrument_instance_method(mod, name) if method.owner == mod + if method.owner == mod + if !block_given? || block_given? && yield(mod, method) + instrument_instance_method(mod, name) + end + end end end diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 80dc160ebd2..5fe7a369cba 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -132,6 +132,14 @@ describe Gitlab::Metrics::Instrumentation do expect(@dummy).to_not respond_to(:_original_kittens) end + + it 'can take a block to determine if a method should be instrumented' do + described_class.instrument_methods(@dummy) do + false + end + + expect(@dummy).to_not respond_to(:_original_foo) + end end describe '.instrument_instance_methods' do @@ -157,5 +165,13 @@ describe Gitlab::Metrics::Instrumentation do expect(@dummy.method_defined?(:_original_kittens)).to eq(false) end + + it 'can take a block to determine if a method should be instrumented' do + described_class.instrument_instance_methods(@dummy) do + false + end + + expect(@dummy.method_defined?(:_original_bar)).to eq(false) + end end end -- cgit v1.2.3 From a41287d8989d7d49b405fd8f658d6c6e4edfd307 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 15 Dec 2015 16:38:25 +0100 Subject: Only track method calls above a certain threshold This ensures we don't end up wasting resources by tracking method calls that only take a few microseconds. By default the threshold is 10 milliseconds but this can be changed using the gitlab.yml configuration file. --- config/gitlab.yml.example | 3 +++ lib/gitlab/metrics.rb | 4 ++++ lib/gitlab/metrics/instrumentation.rb | 8 +++++--- spec/lib/gitlab/metrics/instrumentation_spec.rb | 24 ++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 38b0920890f..24f3f6f02dc 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -432,6 +432,9 @@ production: &base # pool_size: 16 # The timeout of a connection in seconds. # timeout: 10 + # The minimum amount of milliseconds a method call has to take before it's + # tracked. Defaults to 10. + # method_call_threshold: 10 development: <<: *base diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 007429fa194..4b92c3244fa 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -16,6 +16,10 @@ module Gitlab !!Settings.metrics['enabled'] end + def self.method_call_threshold + Settings.metrics['method_call_threshold'] || 10 + end + def self.pool @pool end diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 91e09694cd8..ca2dffbc46a 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -99,9 +99,11 @@ module Gitlab retval = __send__(#{alias_name.inspect}, *args, &block) duration = (Time.now - start) * 1000.0 - trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, - { duration: duration }, - method: #{label.inspect}) + if duration >= Gitlab::Metrics.method_call_threshold + trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, + { duration: duration }, + method: #{label.inspect}) + end retval else diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 5fe7a369cba..71d7209db0f 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -42,6 +42,9 @@ describe Gitlab::Metrics::Instrumentation do end it 'tracks the call duration upon calling the method' do + allow(Gitlab::Metrics).to receive(:method_call_threshold). + and_return(0) + allow(described_class).to receive(:transaction). and_return(transaction) @@ -51,6 +54,15 @@ describe Gitlab::Metrics::Instrumentation do @dummy.foo end + + it 'does not track method calls below a given duration threshold' do + allow(Gitlab::Metrics).to receive(:method_call_threshold). + and_return(100) + + expect(transaction).to_not receive(:add_metric) + + @dummy.foo + end end describe 'with metrics disabled' do @@ -84,6 +96,9 @@ describe Gitlab::Metrics::Instrumentation do end it 'tracks the call duration upon calling the method' do + allow(Gitlab::Metrics).to receive(:method_call_threshold). + and_return(0) + allow(described_class).to receive(:transaction). and_return(transaction) @@ -93,6 +108,15 @@ describe Gitlab::Metrics::Instrumentation do @dummy.new.bar end + + it 'does not track method calls below a given duration threshold' do + allow(Gitlab::Metrics).to receive(:method_call_threshold). + and_return(100) + + expect(transaction).to_not receive(:add_metric) + + @dummy.new.bar + end end describe 'with metrics disabled' do -- cgit v1.2.3 From a93a32a290c8e134763188ebd2b62935f5698e6c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 15 Dec 2015 17:22:46 +0100 Subject: Support for instrumenting class hierarchies This will be used to (for example) instrument all ActiveRecord models. --- lib/gitlab/metrics/instrumentation.rb | 23 +++++++++++++++++ spec/lib/gitlab/metrics/instrumentation_spec.rb | 33 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index ca2dffbc46a..06fc2f25948 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -31,6 +31,29 @@ module Gitlab instrument(:instance, mod, name) end + # Recursively instruments all subclasses of the given root module. + # + # This can be used to for example instrument all ActiveRecord models (as + # these all inherit from ActiveRecord::Base). + # + # This method can optionally take a block to pass to `instrument_methods` + # and `instrument_instance_methods`. + # + # root - The root module for which to instrument subclasses. The root + # module itself is not instrumented. + def self.instrument_class_hierarchy(root, &block) + visit = root.subclasses + + until visit.empty? + klass = visit.pop + + instrument_methods(klass, &block) + instrument_instance_methods(klass, &block) + + klass.subclasses.each { |c| visit << c } + end + end + # Instruments all public methods of a module. # # This method optionally takes a block that can be used to determine if a diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 71d7209db0f..a7eab9d11cc 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -133,6 +133,39 @@ describe Gitlab::Metrics::Instrumentation do end end + describe '.instrument_class_hierarchy' do + before do + allow(Gitlab::Metrics).to receive(:enabled?).and_return(true) + + @child1 = Class.new(@dummy) do + def self.child1_foo; end + def child1_bar; end + end + + @child2 = Class.new(@child1) do + def self.child2_foo; end + def child2_bar; end + end + end + + it 'recursively instruments a class hierarchy' do + described_class.instrument_class_hierarchy(@dummy) + + expect(@child1).to respond_to(:_original_child1_foo) + expect(@child2).to respond_to(:_original_child2_foo) + + expect(@child1.method_defined?(:_original_child1_bar)).to eq(true) + expect(@child2.method_defined?(:_original_child2_bar)).to eq(true) + end + + it 'does not instrument the root module' do + described_class.instrument_class_hierarchy(@dummy) + + expect(@dummy).to_not respond_to(:_original_foo) + expect(@dummy.method_defined?(:_original_bar)).to eq(false) + end + end + describe '.instrument_methods' do before do allow(Gitlab::Metrics).to receive(:enabled?).and_return(true) -- cgit v1.2.3 From bcee44ad33d8a84822a8df068d47812594c445a3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 15 Dec 2015 17:23:23 +0100 Subject: Instrument all ActiveRecord model methods This works by searching the raw source code for any references to commonly used ActiveRecord methods. While not bulletproof it saves us from having to list hundreds of methods by hand. It also ensures that (most) newly added methods are instrumented automatically. This _only_ instruments models defined in app/models, should a model reside somewhere else (e.g. somewhere in lib/) it _won't_ be instrumented. --- Gemfile | 1 + Gemfile.lock | 1 + config/initializers/metrics.rb | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/Gemfile b/Gemfile index 90db2c43006..e9e5c7df075 100644 --- a/Gemfile +++ b/Gemfile @@ -210,6 +210,7 @@ gem 'net-ssh', '~> 3.0.1' # Metrics group :metrics do + gem 'method_source', '~> 0.8', require: false gem 'influxdb', '~> 0.2', require: false gem 'connection_pool', '~> 2.0', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 7d592ba93a7..3f301111224 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -887,6 +887,7 @@ DEPENDENCIES kaminari (~> 0.16.3) letter_opener (~> 1.1.2) mail_room (~> 0.6.1) + method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) mysql2 (~> 0.3.16) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 0ac4299dcba..a47d2bf59a6 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -2,6 +2,7 @@ if Gitlab::Metrics.enabled? require 'influxdb' require 'socket' require 'connection_pool' + require 'method_source' # These are manually require'd so the classes are registered properly with # ActiveSupport. @@ -18,6 +19,26 @@ if Gitlab::Metrics.enabled? end end + # This instruments all methods residing in app/models that (appear to) use any + # of the ActiveRecord methods. This has to take place _after_ initializing as + # for some unknown reason calling eager_load! earlier breaks Devise. + Gitlab::Application.config.after_initialize do + Rails.application.eager_load! + + models = Rails.root.join('app', 'models').to_s + + regex = Regexp.union( + ActiveRecord::Querying.public_instance_methods(false).map(&:to_s) + ) + + Gitlab::Metrics::Instrumentation. + instrument_class_hierarchy(ActiveRecord::Base) do |_, method| + loc = method.source_location + + loc && loc[0].start_with?(models) && method.source =~ regex + end + end + Gitlab::Metrics::Instrumentation.configure do |config| config.instrument_instance_methods(Gitlab::Shell) -- cgit v1.2.3 From f181f05e8abd7b1066c11578193f6d7170764bf5 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 17 Dec 2015 17:17:18 +0100 Subject: Track object counts using the "allocations" Gem This allows us to track the counts of actual classes instead of "T_XXX" nodes. This is only enabled on CRuby as it uses CRuby specific APIs. --- Gemfile | 1 + Gemfile.lock | 2 ++ lib/gitlab/metrics.rb | 4 ++++ lib/gitlab/metrics/sampler.rb | 25 ++++++++++++++++++++++--- spec/lib/gitlab/metrics/sampler_spec.rb | 4 ++++ 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index e9e5c7df075..94660ab5217 100644 --- a/Gemfile +++ b/Gemfile @@ -210,6 +210,7 @@ gem 'net-ssh', '~> 3.0.1' # Metrics group :metrics do + gem 'allocations', '~> 1.0', require: false, platform: :mri gem 'method_source', '~> 0.8', require: false gem 'influxdb', '~> 0.2', require: false gem 'connection_pool', '~> 2.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 3f301111224..13e8168ee8a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -49,6 +49,7 @@ GEM addressable (2.3.8) after_commit_queue (1.3.0) activerecord (>= 3.0) + allocations (1.0.1) annotate (2.6.10) activerecord (>= 3.2, <= 4.3) rake (~> 10.4) @@ -818,6 +819,7 @@ DEPENDENCIES acts-as-taggable-on (~> 3.4) addressable (~> 2.3.8) after_commit_queue + allocations (~> 1.0) annotate (~> 2.6.0) asana (~> 0.4.0) asciidoctor (~> 1.5.2) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 4b92c3244fa..ce89be636d3 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -16,6 +16,10 @@ module Gitlab !!Settings.metrics['enabled'] end + def self.mri? + RUBY_ENGINE == 'ruby' + end + def self.method_call_threshold Settings.metrics['method_call_threshold'] || 10 end diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/sampler.rb index 03afa6324dd..828ee1f8c62 100644 --- a/lib/gitlab/metrics/sampler.rb +++ b/lib/gitlab/metrics/sampler.rb @@ -13,6 +13,12 @@ module Gitlab @last_minor_gc = Delta.new(GC.stat[:minor_gc_count]) @last_major_gc = Delta.new(GC.stat[:major_gc_count]) + + if Gitlab::Metrics.mri? + require 'allocations' + + Allocations.start + end end def start @@ -52,9 +58,22 @@ module Gitlab new('file_descriptors', value: System.file_descriptor_count) end - def sample_objects - ObjectSpace.count_objects.each do |type, count| - @metrics << Metric.new('object_counts', { count: count }, type: type) + if Metrics.mri? + def sample_objects + sample = Allocations.to_hash + counts = sample.each_with_object({}) do |(klass, count), hash| + hash[klass.name] = count + end + + # Symbols aren't allocated so we'll need to add those manually. + counts['Symbol'] = Symbol.all_symbols.length + + counts.each do |name, count| + @metrics << Metric.new('object_counts', { count: count }, type: name) + end + end + else + def sample_objects end end diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb index 319f287178d..69376c0b79b 100644 --- a/spec/lib/gitlab/metrics/sampler_spec.rb +++ b/spec/lib/gitlab/metrics/sampler_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' describe Gitlab::Metrics::Sampler do let(:sampler) { described_class.new(5) } + after do + Allocations.stop if Gitlab::Metrics.mri? + end + describe '#start' do it 'gathers a sample at a given interval' do expect(sampler).to receive(:sleep).with(5) -- cgit v1.2.3 From a3de46654b2fe0f02995913a771e6423bb584d64 Mon Sep 17 00:00:00 2001 From: Jose Torres Date: Sat, 19 Dec 2015 15:16:50 -0600 Subject: Adding how we manage CRIME vulnerability to security docs [ci skip] --- doc/security/README.md | 1 + doc/security/crime_vulnerability.md | 59 +++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 doc/security/crime_vulnerability.md diff --git a/doc/security/README.md b/doc/security/README.md index fba6013d9c1..7df7cef6aa5 100644 --- a/doc/security/README.md +++ b/doc/security/README.md @@ -6,3 +6,4 @@ - [Information exclusivity](information_exclusivity.md) - [Reset your root password](reset_root_password.md) - [User File Uploads](user_file_uploads.md) +- [How we manage the CRIME vulnerability](crime_vulnerability.md) diff --git a/doc/security/crime_vulnerability.md b/doc/security/crime_vulnerability.md new file mode 100644 index 00000000000..d716bff85a5 --- /dev/null +++ b/doc/security/crime_vulnerability.md @@ -0,0 +1,59 @@ +# How we manage the TLS protocol CRIME vulnerability + +> CRIME ("Compression Ratio Info-leak Made Easy") is a security exploit against +secret web cookies over connections using the HTTPS and SPDY protocols that also +use data compression.[1][2] When used to recover the content of secret +authentication cookies, it allows an attacker to perform session hijacking on an +authenticated web session, allowing the launching of further attacks. +([CRIME](https://en.wikipedia.org/w/index.php?title=CRIME&oldid=692423806)) + +### Description + +The TLS Protocol CRIME Vulnerability affects compression over HTTPS therefore +it warns against using SSL Compression, take gzip for example, or SPDY which +optionally uses compression as well. + +GitLab support both gzip and SPDY and manages the CRIME vulnerability by +deactivating gzip when https is enabled and not activating the compression +feature on SDPY. + +Take a look at our configuration file for NGINX if you'd like to explore how the +conditions are setup for gzip deactivation on this link: +[GitLab NGINX File](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb). + +For SPDY you can also watch how its implmented on NGINX at [GitLab NGINX File](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb) +but take into consideration the NGINX documentation on its default state here: +[Module ngx_http_spdy_module](http://nginx.org/en/docs/http/ngx_http_spdy_module.html). + + +### Nessus + +The Nessus scanner reports a possible CRIME vunerability for GitLab similar to the +following format: + + Description + + This remote service has one of two configurations that are known to be required for the CRIME attack: + SSL/TLS compression is enabled. + TLS advertises the SPDY protocol earlier than version 4. + + ... + + Output + + The following configuration indicates that the remote service may be vulnerable to the CRIME attack: + SPDY support earlier than version 4 is advertised. + +*[This](http://www.tenable.com/plugins/index.php?view=single&id=62565) is a complete description from Nessus.* + +From the report above its important to note that Nessus is only checkng if TLS +advertises the SPDY protocol earlier than version 4, it does not perform an +attack nor does it check if compression is enabled. With just this approach it +cannot tell that SPDY's compression is disabled and not subject to the CRIME +vulnerbility. + + +### Reference +* Nginx. "Module ngx_http_spdy_module", Fri. 18 Dec. +* Tenable Network Security, Inc. "Transport Layer Security (TLS) Protocol CRIME Vulnerability", Web. 15 Dec. +* Wikipedia contributors. "CRIME." Wikipedia, The Free Encyclopedia. Wikipedia, The Free Encyclopedia, 25 Nov. 2015. Web. 15 Dec. 2015. \ No newline at end of file -- cgit v1.2.3 From 70dfa3a721700cf0151a7d097933d75684e69fc9 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 21 Dec 2015 13:06:09 -0500 Subject: open and close issue via ajax request. With tests --- app/assets/javascripts/issue.js.coffee | 30 +++++++++++++++++++++ app/helpers/issues_helper.rb | 4 +++ app/views/projects/issues/show.html.haml | 13 +++------ spec/javascripts/fixtures/issues_show.html.haml | 5 +++- spec/javascripts/issue_spec.js.coffee | 36 +++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index eff80bf63bb..8d028268b81 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -8,11 +8,41 @@ class @Issue if $("a.btn-close").length @initTaskList() + @initIssueBtnEventListeners() initTaskList: -> $('.detail-page-description .js-task-list-container').taskList('enable') $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList + initIssueBtnEventListeners: -> + $("a.btn-close, a.btn-reopen").on "click", (e) -> + e.preventDefault() + e.stopImmediatePropagation() + $this = $(this) + isClose = $this.hasClass('btn-close') + $this.prop("disabled", true) + url = $this.data('url') + $.ajax + type: 'PUT' + url: url, + error: (jqXHR, textStatus, errorThrown) -> + issueStatus = if isClose then 'close' else 'open' + console.log("Cannot #{issueStatus} this issue, at this time.") + success: (data, textStatus, jqXHR) -> + if data.saved + $this.addClass('hidden') + if isClose + $('a.btn-reopen').removeClass('hidden') + $('div.issue-box-closed').removeClass('hidden') + $('div.issue-box-open').addClass('hidden') + else + $('a.btn-close').removeClass('hidden') + $('div.issue-box-closed').addClass('hidden') + $('div.issue-box-open').removeClass('hidden') + else + console.log("Did not work") + $this.prop('disabled', false) + disableTaskList: -> $('.detail-page-description .js-task-list-container').taskList('disable') $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container' diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index d2186427dba..1f0f6aeeac2 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -69,6 +69,10 @@ module IssuesHelper end end + def issue_button_visibility(issue, closed) + return 'hidden' if issue.closed? == closed + end + def issue_to_atom(xml, issue) xml.entry do xml.id namespace_project_issue_url(issue.project.namespace, diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 2fe6f88b2a9..9444a1c1d84 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -3,11 +3,8 @@ .issue .detail-page-header - .status-box{ class: status_box_class(@issue) } - - if @issue.closed? - Closed - - else - Open + .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed + .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open %span.identifier Issue ##{@issue.iid} %span.creator @@ -27,10 +24,8 @@ = icon('plus') New Issue - if can?(current_user, :update_issue, @issue) - - if @issue.closed? - = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen' - - else - = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue' + = link_to 'Reopen', '#', data: {no_turbolink: true, url: issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json')}, class: "btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue' + = link_to 'Close', '#', data: {no_turbolink: true, url: issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json')}, class: "btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue' = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do = icon('pencil-square-o') diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml index 8447dfdda32..6c985a32966 100644 --- a/spec/javascripts/fixtures/issues_show.html.haml +++ b/spec/javascripts/fixtures/issues_show.html.haml @@ -1,4 +1,7 @@ -%a.btn-close +.issue-box.issue-box-open Open +.issue-box.issue-box-closed.hidden Closed +%a.btn-close{"data-url" => "http://gitlab/issues/6/close"} Close +%a.btn-reopen.hidden{"data-url" => "http://gitlab/issues/6/reopen"} Reopen .detail-page-description .description.js-task-list-container diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index 268e4c68c31..60df3a8878b 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -20,3 +20,39 @@ describe 'Issue', -> expect(req.data.issue.description).not.toBe(null) $('.js-task-list-field').trigger('tasklist:changed') +describe 'reopen/close issue', -> + fixture.preload('issues_show.html') + beforeEach -> + fixture.load('issues_show.html') + @issue = new Issue() + it 'closes an issue', -> + $.ajax = (obj) -> + expect(obj.type).toBe('PUT') + expect(obj.url).toBe('http://gitlab/issues/6/close') + obj.success saved: true + $btnClose = $('a.btn-close') + $btnReopen = $('a.btn-reopen') + expect($btnReopen.hasClass('hidden')).toBe(true) + expect($btnClose.text()).toBe('Close') + expect(typeof $btnClose.prop('disabled')).toBe('undefined') + $btnClose.trigger('click') + expect($btnClose.hasClass('hidden')).toBe(true) + expect($btnReopen.hasClass('hidden')).toBe(false) + expect($btnClose.prop('disabled')).toBe(false) + expect($('div.issue-box-open').hasClass('hidden')).toBe(true) + expect($('div.issue-box-closed').hasClass('hidden')).toBe(false) + it 'reopens an issue', -> + $.ajax = (obj) -> + expect(obj.type).toBe('PUT') + expect(obj.url).toBe('http://gitlab/issues/6/reopen') + obj.success saved: true + $btnClose = $('a.btn-close') + $btnReopen = $('a.btn-reopen') + expect(typeof $btnReopen.prop('disabled')).toBe('undefined') + expect($btnReopen.text()).toBe('Reopen') + $btnReopen.trigger('click') + expect($btnReopen.hasClass('hidden')).toBe(true) + expect($btnClose.hasClass('hidden')).toBe(false) + expect($btnReopen.prop('disabled')).toBe(false) + expect($('div.issue-box-open').hasClass('hidden')).toBe(false) + expect($('div.issue-box-closed').hasClass('hidden')).toBe(true) \ No newline at end of file -- cgit v1.2.3 From 531d06d170fd5cfcb6d4f6c919034c49266e42e9 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 21 Dec 2015 16:16:04 -0500 Subject: removes `console.log`s --- app/assets/javascripts/issue.js.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 8d028268b81..1d57c24587d 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -27,7 +27,6 @@ class @Issue url: url, error: (jqXHR, textStatus, errorThrown) -> issueStatus = if isClose then 'close' else 'open' - console.log("Cannot #{issueStatus} this issue, at this time.") success: (data, textStatus, jqXHR) -> if data.saved $this.addClass('hidden') @@ -40,7 +39,6 @@ class @Issue $('div.issue-box-closed').addClass('hidden') $('div.issue-box-open').removeClass('hidden') else - console.log("Did not work") $this.prop('disabled', false) disableTaskList: -> -- cgit v1.2.3 From fec7c99e2f2221f22680c140b7d9b1d959d8aeb0 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 21 Dec 2015 16:27:52 -0500 Subject: updates tests style for four-phase-testing as per: https://robots.thoughtbot.com/four-phase-test --- spec/javascripts/issue_spec.js.coffee | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index 60df3a8878b..f8e60565b9a 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -30,29 +30,45 @@ describe 'reopen/close issue', -> expect(obj.type).toBe('PUT') expect(obj.url).toBe('http://gitlab/issues/6/close') obj.success saved: true + + # setup $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') expect($btnReopen.hasClass('hidden')).toBe(true) expect($btnClose.text()).toBe('Close') expect(typeof $btnClose.prop('disabled')).toBe('undefined') + + # excerize $btnClose.trigger('click') + + # verify expect($btnClose.hasClass('hidden')).toBe(true) expect($btnReopen.hasClass('hidden')).toBe(false) expect($btnClose.prop('disabled')).toBe(false) expect($('div.issue-box-open').hasClass('hidden')).toBe(true) expect($('div.issue-box-closed').hasClass('hidden')).toBe(false) + + # teardown it 'reopens an issue', -> $.ajax = (obj) -> expect(obj.type).toBe('PUT') expect(obj.url).toBe('http://gitlab/issues/6/reopen') obj.success saved: true + + # setup $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') expect(typeof $btnReopen.prop('disabled')).toBe('undefined') expect($btnReopen.text()).toBe('Reopen') + + # excerize $btnReopen.trigger('click') + + # verify expect($btnReopen.hasClass('hidden')).toBe(true) expect($btnClose.hasClass('hidden')).toBe(false) expect($btnReopen.prop('disabled')).toBe(false) expect($('div.issue-box-open').hasClass('hidden')).toBe(false) - expect($('div.issue-box-closed').hasClass('hidden')).toBe(true) \ No newline at end of file + expect($('div.issue-box-closed').hasClass('hidden')).toBe(true) + + # teardown \ No newline at end of file -- cgit v1.2.3 From 3e7e7ae0aceaa8a805093841b07b9b5d9f911cb1 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 21 Dec 2015 16:35:03 -0500 Subject: clarifies tests with methods like `toBeVisible()` etc. --- spec/javascripts/issue_spec.js.coffee | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index f8e60565b9a..f50640d1821 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -34,7 +34,7 @@ describe 'reopen/close issue', -> # setup $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') - expect($btnReopen.hasClass('hidden')).toBe(true) + expect($btnReopen.toBeHidden()) expect($btnClose.text()).toBe('Close') expect(typeof $btnClose.prop('disabled')).toBe('undefined') @@ -42,11 +42,10 @@ describe 'reopen/close issue', -> $btnClose.trigger('click') # verify - expect($btnClose.hasClass('hidden')).toBe(true) - expect($btnReopen.hasClass('hidden')).toBe(false) - expect($btnClose.prop('disabled')).toBe(false) - expect($('div.issue-box-open').hasClass('hidden')).toBe(true) - expect($('div.issue-box-closed').hasClass('hidden')).toBe(false) + expect($btnClose.toBeHidden()) + expect($btnReopen.toBeVisible()) + expect($('div.issue-box-open').toBeVisible()) + expect($('div.issue-box-closed').toBeHidden()) # teardown it 'reopens an issue', -> @@ -58,17 +57,15 @@ describe 'reopen/close issue', -> # setup $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') - expect(typeof $btnReopen.prop('disabled')).toBe('undefined') expect($btnReopen.text()).toBe('Reopen') # excerize $btnReopen.trigger('click') # verify - expect($btnReopen.hasClass('hidden')).toBe(true) - expect($btnClose.hasClass('hidden')).toBe(false) - expect($btnReopen.prop('disabled')).toBe(false) - expect($('div.issue-box-open').hasClass('hidden')).toBe(false) - expect($('div.issue-box-closed').hasClass('hidden')).toBe(true) + expect($btnReopen.toBeHidden()) + expect($btnClose.toBeVisible()) + expect($('div.issue-box-open').toBeVisible()) + expect($('div.issue-box-closed').toBeHidden()) # teardown \ No newline at end of file -- cgit v1.2.3 From 1f5b8e88f3fedc382555e89b5a6ba9be57d551c7 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 21 Dec 2015 16:45:52 -0500 Subject: changes `data-url` to `href` for javascript url grabbing --- app/assets/javascripts/issue.js.coffee | 3 ++- app/views/projects/issues/show.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 1d57c24587d..ba5205fb471 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -16,12 +16,13 @@ class @Issue initIssueBtnEventListeners: -> $("a.btn-close, a.btn-reopen").on "click", (e) -> + console.log('closing') e.preventDefault() e.stopImmediatePropagation() $this = $(this) isClose = $this.hasClass('btn-close') $this.prop("disabled", true) - url = $this.data('url') + url = $this.attr('href') $.ajax type: 'PUT' url: url, diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 9444a1c1d84..4ca122ba55f 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -24,8 +24,8 @@ = icon('plus') New Issue - if can?(current_user, :update_issue, @issue) - = link_to 'Reopen', '#', data: {no_turbolink: true, url: issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json')}, class: "btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue' - = link_to 'Close', '#', data: {no_turbolink: true, url: issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json')}, class: "btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue' + = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue' + = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue' = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do = icon('pencil-square-o') -- cgit v1.2.3 From 801b801bf08a0bc6df8ba13775f3050d091ce84d Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 21 Dec 2015 16:46:36 -0500 Subject: removes console logs --- app/assets/javascripts/issue.js.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index ba5205fb471..1423daa281b 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -16,7 +16,6 @@ class @Issue initIssueBtnEventListeners: -> $("a.btn-close, a.btn-reopen").on "click", (e) -> - console.log('closing') e.preventDefault() e.stopImmediatePropagation() $this = $(this) -- cgit v1.2.3 From 453479143dd4784d7284b2e6f98694ff8fc18ce8 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 21 Dec 2015 17:03:28 -0500 Subject: adds alerts for when http request errors out in some way. --- app/assets/javascripts/issue.js.coffee | 2 ++ spec/javascripts/issue_spec.js.coffee | 11 +---------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 1423daa281b..2e0ef99a587 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -27,6 +27,7 @@ class @Issue url: url, error: (jqXHR, textStatus, errorThrown) -> issueStatus = if isClose then 'close' else 'open' + new Flash("Issues update failed", 'alert') success: (data, textStatus, jqXHR) -> if data.saved $this.addClass('hidden') @@ -39,6 +40,7 @@ class @Issue $('div.issue-box-closed').addClass('hidden') $('div.issue-box-open').removeClass('hidden') else + new Flash("Issues update failed", 'alert') $this.prop('disabled', false) disableTaskList: -> diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index f50640d1821..ef78cfbc653 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -31,41 +31,32 @@ describe 'reopen/close issue', -> expect(obj.url).toBe('http://gitlab/issues/6/close') obj.success saved: true - # setup $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') expect($btnReopen.toBeHidden()) expect($btnClose.text()).toBe('Close') expect(typeof $btnClose.prop('disabled')).toBe('undefined') - # excerize $btnClose.trigger('click') - # verify expect($btnClose.toBeHidden()) expect($btnReopen.toBeVisible()) expect($('div.issue-box-open').toBeVisible()) expect($('div.issue-box-closed').toBeHidden()) - # teardown it 'reopens an issue', -> $.ajax = (obj) -> expect(obj.type).toBe('PUT') expect(obj.url).toBe('http://gitlab/issues/6/reopen') obj.success saved: true - # setup $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') expect($btnReopen.text()).toBe('Reopen') - # excerize $btnReopen.trigger('click') - # verify expect($btnReopen.toBeHidden()) expect($btnClose.toBeVisible()) expect($('div.issue-box-open').toBeVisible()) - expect($('div.issue-box-closed').toBeHidden()) - - # teardown \ No newline at end of file + expect($('div.issue-box-closed').toBeHidden()) \ No newline at end of file -- cgit v1.2.3 From 3ea3d9ef284fcfaaa1b631e98926df7dae4129c9 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 21 Dec 2015 17:10:27 -0500 Subject: changes `issue-box` to `status-box` since html was changed as well --- app/assets/javascripts/issue.js.coffee | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 2e0ef99a587..1a9e03e4ee3 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -6,7 +6,7 @@ class @Issue # Prevent duplicate event bindings @disableTaskList() - if $("a.btn-close").length + if $('a.btn-close').length @initTaskList() @initIssueBtnEventListeners() @@ -15,32 +15,32 @@ class @Issue $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList initIssueBtnEventListeners: -> - $("a.btn-close, a.btn-reopen").on "click", (e) -> + $('a.btn-close, a.btn-reopen').on 'click', (e) -> e.preventDefault() e.stopImmediatePropagation() $this = $(this) isClose = $this.hasClass('btn-close') - $this.prop("disabled", true) + $this.prop('disabled', true) url = $this.attr('href') $.ajax type: 'PUT' url: url, error: (jqXHR, textStatus, errorThrown) -> issueStatus = if isClose then 'close' else 'open' - new Flash("Issues update failed", 'alert') + new Flash('Issues update failed', 'alert') success: (data, textStatus, jqXHR) -> if data.saved $this.addClass('hidden') if isClose $('a.btn-reopen').removeClass('hidden') - $('div.issue-box-closed').removeClass('hidden') - $('div.issue-box-open').addClass('hidden') + $('div.status-box-closed').removeClass('hidden') + $('div.status-box-open').addClass('hidden') else $('a.btn-close').removeClass('hidden') - $('div.issue-box-closed').addClass('hidden') - $('div.issue-box-open').removeClass('hidden') + $('div.status-box-closed').addClass('hidden') + $('div.status-box-open').removeClass('hidden') else - new Flash("Issues update failed", 'alert') + new Flash('Issues update failed', 'alert') $this.prop('disabled', false) disableTaskList: -> -- cgit v1.2.3 From 9a166c1ed85dc4ba96cbd43098d71c83bda2a93a Mon Sep 17 00:00:00 2001 From: Raymii Date: Tue, 22 Dec 2015 05:29:19 +0000 Subject: `update-init-script` was listed two times. removed one without explanation. --- doc/update/8.2-to-8.3.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/update/8.2-to-8.3.md b/doc/update/8.2-to-8.3.md index c4661dc16af..3748941b781 100644 --- a/doc/update/8.2-to-8.3.md +++ b/doc/update/8.2-to-8.3.md @@ -99,8 +99,6 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production # Clean up assets and cache sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production -# Update init.d script -sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ``` ### 7. Update configuration files -- cgit v1.2.3 From 947a09583b25140ee386229f11c82a0984be2451 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 23 Dec 2015 13:55:15 -0500 Subject: Add Open Graph meta tags --- app/views/layouts/_head.html.haml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 74174a72f5a..be88ad9e2a6 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,10 +1,14 @@ - page_title "GitLab" -%head +%head{prefix: "og: http://ogp.me/ns#"} %meta{charset: "utf-8"} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} %meta{content: "GitLab Community Edition", name: "description"} %meta{name: 'referrer', content: 'origin-when-cross-origin'} + -# Open Graph - http://ogp.me/ + %meta{property: 'og:title', content: page_title} + %meta{property: 'og:url', content: url_for(only_path: false)} + %title= page_title = favicon_link_tag 'favicon.ico' -- cgit v1.2.3 From 3f452f539b909d1a379a4f3c5d48307246a14fff Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 23 Dec 2015 16:45:37 -0500 Subject: Add gitlab_logo.png, remove brand_logo.png --- app/assets/images/brand_logo.png | Bin 27059 -> 0 bytes app/assets/images/gitlab_logo.png | Bin 0 -> 5189 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/assets/images/brand_logo.png create mode 100644 app/assets/images/gitlab_logo.png diff --git a/app/assets/images/brand_logo.png b/app/assets/images/brand_logo.png deleted file mode 100644 index 9c564bb6141..00000000000 Binary files a/app/assets/images/brand_logo.png and /dev/null differ diff --git a/app/assets/images/gitlab_logo.png b/app/assets/images/gitlab_logo.png new file mode 100644 index 00000000000..0c157546b9c Binary files /dev/null and b/app/assets/images/gitlab_logo.png differ -- cgit v1.2.3 From b26eb782f5536d8c383aebe3d65571fb16a20bcd Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 23 Dec 2015 16:56:27 -0500 Subject: Add page descriptions and images A limited number of pages have defined their own descriptions, but otherwise we default to the Project's description (if `@project` is set), or the old `brand_title` fallback. The image will either be the uploaded project icon (never a generated one), the user's uploaded icon or Gravatar, or, finally, the GitLab logo. --- app/helpers/page_layout_helper.rb | 50 ++++++++++++++ app/views/layouts/_head.html.haml | 12 ++-- app/views/projects/issues/show.html.haml | 4 +- app/views/projects/merge_requests/_show.html.haml | 4 +- app/views/projects/milestones/show.html.haml | 4 +- app/views/users/show.html.haml | 5 +- spec/helpers/page_layout_helper_spec.rb | 79 +++++++++++++++++++++++ 7 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 spec/helpers/page_layout_helper_spec.rb diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 9bf750124b2..6b16528dde6 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -8,6 +8,56 @@ module PageLayoutHelper @page_title.join(" \u00b7 ") end + # Define or get a description for the current page + # + # description - String (default: nil) + # + # If this helper is called multiple times with an argument, only the last + # description will be returned when called without an argument. Descriptions + # have newlines replaced with spaces and all HTML tags are sanitized. + # + # Examples: + # + # page_description # => "GitLab Community Edition" + # page_description("Foo") + # page_description # => "Foo" + # + # page_description("Bar\nBaz") + # page_description # => "Bar Baz" + # + # Returns an HTML-safe String. + def page_description(description = nil) + @page_description ||= page_description_default + + if description.present? + @page_description = description + else + sanitize(@page_description.squish, tags: []) + end + end + + # Default value for page_description when one hasn't been defined manually by + # a view + def page_description_default + if @project + @project.description + else + brand_title + end + end + + def page_image + default = image_url('gitlab_logo.png') + + if @project + @project.avatar_url || default + elsif @user + avatar_icon(@user) + else + default + end + end + def header_title(title = nil, title_url = nil) if title @header_title = title diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index be88ad9e2a6..ad1dacac1c8 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,13 +1,17 @@ - page_title "GitLab" + %head{prefix: "og: http://ogp.me/ns#"} %meta{charset: "utf-8"} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} - %meta{content: "GitLab Community Edition", name: "description"} - %meta{name: 'referrer', content: 'origin-when-cross-origin'} + + %meta{name: "description", content: page_description} + %meta{name: 'referrer', content: 'origin-when-cross-origin'} -# Open Graph - http://ogp.me/ - %meta{property: 'og:title', content: page_title} - %meta{property: 'og:url', content: url_for(only_path: false)} + %meta{property: 'og:title', content: page_title} + %meta{property: 'og:description', content: page_description} + %meta{property: 'og:image', content: page_image} + %meta{property: 'og:url', content: url_for(only_path: false)} %title= page_title diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index b6efa05a1ae..f2a261ab426 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,4 +1,6 @@ -- page_title "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "#{@issue.title} (##{@issue.iid})", "Issues" +- page_description @issue.description + = render "header_title" .issue diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index e9ffbd06be2..75f44557964 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,6 @@ -- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" +- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" +- page_description @merge_request.description + = render "header_title" - if params[:view] == 'parallel' diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 7e73ae274e9..1670ea8741a 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,4 +1,6 @@ -- page_title @milestone.title, "Milestones" +- page_title @milestone.title, "Milestones" +- page_description @milestone.description + = render "header_title" .detail-page-header diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index b7a7eb4e6f7..0bca8177e14 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,5 +1,6 @@ -- page_title @user.name -- header_title @user.name, user_path(@user) +- page_title @user.name +- page_description @user.bio +- header_title @user.name, user_path(@user) = content_for :meta_tags do = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb new file mode 100644 index 00000000000..a60b8f508f2 --- /dev/null +++ b/spec/helpers/page_layout_helper_spec.rb @@ -0,0 +1,79 @@ +require 'rails_helper' + +describe PageLayoutHelper do + describe 'page_description' do + it 'defaults to value returned by page_description_default helper' do + allow(helper).to receive(:page_description_default).and_return('Foo') + + expect(helper.page_description).to eq 'Foo' + end + + it 'returns the last-pushed description' do + helper.page_description('Foo') + helper.page_description('Bar') + helper.page_description('Baz') + + expect(helper.page_description).to eq 'Baz' + end + + it 'squishes multiple newlines' do + helper.page_description("Foo\nBar\nBaz") + + expect(helper.page_description).to eq 'Foo Bar Baz' + end + + it 'sanitizes all HTML' do + helper.page_description("Bold

Header

") + + expect(helper.page_description).to eq 'Bold Header' + end + end + + describe 'page_description_default' do + it 'uses Project description when available' do + project = double(description: 'Project Description') + helper.instance_variable_set(:@project, project) + + expect(helper.page_description_default).to eq 'Project Description' + end + + it 'falls back to brand_title' do + allow(helper).to receive(:brand_title).and_return('Brand Title') + + expect(helper.page_description_default).to eq 'Brand Title' + end + end + + describe 'page_image' do + it 'defaults to the GitLab logo' do + expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + end + + context 'with @project' do + it 'uses Project avatar if available' do + project = double(avatar_url: 'http://example.com/uploads/avatar.png') + helper.instance_variable_set(:@project, project) + + expect(helper.page_image).to eq project.avatar_url + end + + it 'falls back to the default' do + project = double(avatar_url: nil) + helper.instance_variable_set(:@project, project) + + expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + end + end + + context 'with @user' do + it 'delegates to avatar_icon helper' do + user = double('User') + helper.instance_variable_set(:@user, user) + + expect(helper).to receive(:avatar_icon).with(user) + + helper.page_image + end + end + end +end -- cgit v1.2.3 From 7e43fa67096f60856d1cf862f245367bc710a44f Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 23 Dec 2015 16:56:36 -0500 Subject: fixes tests to work with jasmine/jquery --- spec/javascripts/fixtures/issues_show.html.haml | 11 +++++++---- spec/javascripts/issue_spec.js.coffee | 18 +++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml index 6c985a32966..f67ea5a13c7 100644 --- a/spec/javascripts/fixtures/issues_show.html.haml +++ b/spec/javascripts/fixtures/issues_show.html.haml @@ -1,7 +1,10 @@ -.issue-box.issue-box-open Open -.issue-box.issue-box-closed.hidden Closed -%a.btn-close{"data-url" => "http://gitlab/issues/6/close"} Close -%a.btn-reopen.hidden{"data-url" => "http://gitlab/issues/6/reopen"} Reopen +:css + .hidden { display: none !important; } + +.status-box.status-box-open Open +.status-box.status-box-closed.hidden Closed +%a.btn-close{"href" => "http://gitlab/issues/6/close"} Close +%a.btn-reopen.hidden{"href" => "http://gitlab/issues/6/reopen"} Reopen .detail-page-description .description.js-task-list-container diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index ef78cfbc653..142eb05cc67 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -33,16 +33,16 @@ describe 'reopen/close issue', -> $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') - expect($btnReopen.toBeHidden()) + expect($btnReopen).toBeHidden() expect($btnClose.text()).toBe('Close') expect(typeof $btnClose.prop('disabled')).toBe('undefined') $btnClose.trigger('click') - expect($btnClose.toBeHidden()) - expect($btnReopen.toBeVisible()) - expect($('div.issue-box-open').toBeVisible()) - expect($('div.issue-box-closed').toBeHidden()) + expect($btnReopen).toBeVisible() + expect($btnClose).toBeHidden() + expect($('div.status-box-closed')).toBeVisible() + expect($('div.status-box-open')).toBeHidden() it 'reopens an issue', -> $.ajax = (obj) -> @@ -56,7 +56,7 @@ describe 'reopen/close issue', -> $btnReopen.trigger('click') - expect($btnReopen.toBeHidden()) - expect($btnClose.toBeVisible()) - expect($('div.issue-box-open').toBeVisible()) - expect($('div.issue-box-closed').toBeHidden()) \ No newline at end of file + expect($btnReopen).toBeHidden() + expect($btnClose).toBeVisible() + expect($('div.status-box-open')).toBeVisible() + expect($('div.status-box-closed')).toBeHidden() \ No newline at end of file -- cgit v1.2.3 From 5a3b9c97e34ee69312ef9bcf575894a106c5a271 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 23 Dec 2015 17:14:18 -0500 Subject: Account for `@project.description` being nil --- app/helpers/page_layout_helper.rb | 2 +- spec/helpers/page_layout_helper_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 6b16528dde6..c4eb09bea2b 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -40,7 +40,7 @@ module PageLayoutHelper # a view def page_description_default if @project - @project.description + @project.description || brand_title else brand_title end diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index a60b8f508f2..5d95beac908 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -37,6 +37,14 @@ describe PageLayoutHelper do expect(helper.page_description_default).to eq 'Project Description' end + it 'uses brand_title when Project description is nil' do + project = double(description: nil) + helper.instance_variable_set(:@project, project) + + expect(helper).to receive(:brand_title).and_return('Brand Title') + expect(helper.page_description_default).to eq 'Brand Title' + end + it 'falls back to brand_title' do allow(helper).to receive(:brand_title).and_return('Brand Title') -- cgit v1.2.3 From e11ee5ff01bf031cd4fda377a7444f2ba4d50f40 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 23 Dec 2015 17:41:05 -0500 Subject: adds test for issue close/reopen failure --- app/assets/javascripts/issue.js.coffee | 5 ++- spec/javascripts/fixtures/issues_show.html.haml | 13 ++++++- spec/javascripts/issue_spec.js.coffee | 51 ++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 1a9e03e4ee3..3d9d514a9c7 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -15,6 +15,7 @@ class @Issue $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList initIssueBtnEventListeners: -> + issueFailMessage = 'Unable to update this issue at this time.' $('a.btn-close, a.btn-reopen').on 'click', (e) -> e.preventDefault() e.stopImmediatePropagation() @@ -27,7 +28,7 @@ class @Issue url: url, error: (jqXHR, textStatus, errorThrown) -> issueStatus = if isClose then 'close' else 'open' - new Flash('Issues update failed', 'alert') + new Flash(issueFailMessage, 'alert') success: (data, textStatus, jqXHR) -> if data.saved $this.addClass('hidden') @@ -40,7 +41,7 @@ class @Issue $('div.status-box-closed').addClass('hidden') $('div.status-box-open').removeClass('hidden') else - new Flash('Issues update failed', 'alert') + new Flash(issueFailMessage, 'alert') $this.prop('disabled', false) disableTaskList: -> diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml index f67ea5a13c7..dd01b6378c5 100644 --- a/spec/javascripts/fixtures/issues_show.html.haml +++ b/spec/javascripts/fixtures/issues_show.html.haml @@ -1,10 +1,19 @@ :css .hidden { display: none !important; } +.flash-container + - if alert + .flash-alert + = alert + + - elsif notice + .flash-notice + = notice + .status-box.status-box-open Open .status-box.status-box-closed.hidden Closed -%a.btn-close{"href" => "http://gitlab/issues/6/close"} Close -%a.btn-reopen.hidden{"href" => "http://gitlab/issues/6/reopen"} Reopen +%a.btn-close{"href" => "http://gitlab.com/issues/6/close"} Close +%a.btn-reopen.hidden{"href" => "http://gitlab.com/issues/6/reopen"} Reopen .detail-page-description .description.js-task-list-container diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index 142eb05cc67..f0a57d99c56 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -1,3 +1,4 @@ +#= require flash #= require issue describe 'Issue', -> @@ -28,7 +29,7 @@ describe 'reopen/close issue', -> it 'closes an issue', -> $.ajax = (obj) -> expect(obj.type).toBe('PUT') - expect(obj.url).toBe('http://gitlab/issues/6/close') + expect(obj.url).toBe('http://gitlab.com/issues/6/close') obj.success saved: true $btnClose = $('a.btn-close') @@ -44,10 +45,56 @@ describe 'reopen/close issue', -> expect($('div.status-box-closed')).toBeVisible() expect($('div.status-box-open')).toBeHidden() + it 'fails to closes an issue with success:false', -> + + $.ajax = (obj) -> + expect(obj.type).toBe('PUT') + expect(obj.url).toBe('http://goesnowhere.nothing/whereami') + obj.success saved: false + + $btnClose = $('a.btn-close') + $btnReopen = $('a.btn-reopen') + $btnClose.attr('href','http://goesnowhere.nothing/whereami') + expect($btnReopen).toBeHidden() + expect($btnClose.text()).toBe('Close') + expect(typeof $btnClose.prop('disabled')).toBe('undefined') + + $btnClose.trigger('click') + + expect($btnReopen).toBeHidden() + expect($btnClose).toBeVisible() + expect($('div.status-box-closed')).toBeHidden() + expect($('div.status-box-open')).toBeVisible() + expect($('div.flash-alert')).toBeVisible() + expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.') + + it 'fails to closes an issue with HTTP error', -> + + $.ajax = (obj) -> + expect(obj.type).toBe('PUT') + expect(obj.url).toBe('http://goesnowhere.nothing/whereami') + obj.error() + + $btnClose = $('a.btn-close') + $btnReopen = $('a.btn-reopen') + $btnClose.attr('href','http://goesnowhere.nothing/whereami') + expect($btnReopen).toBeHidden() + expect($btnClose.text()).toBe('Close') + expect(typeof $btnClose.prop('disabled')).toBe('undefined') + + $btnClose.trigger('click') + + expect($btnReopen).toBeHidden() + expect($btnClose).toBeVisible() + expect($('div.status-box-closed')).toBeHidden() + expect($('div.status-box-open')).toBeVisible() + expect($('div.flash-alert')).toBeVisible() + expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.') + it 'reopens an issue', -> $.ajax = (obj) -> expect(obj.type).toBe('PUT') - expect(obj.url).toBe('http://gitlab/issues/6/reopen') + expect(obj.url).toBe('http://gitlab.com/issues/6/reopen') obj.success saved: true $btnClose = $('a.btn-close') -- cgit v1.2.3 From 00e967a07f476f7df7aaddc652258668af392f5d Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 23 Dec 2015 20:57:16 -0500 Subject: removes unused `alert` from issue spec. Requires flash in the *implementation* instead of the spec. --- app/assets/javascripts/issue.js.coffee | 1 + spec/javascripts/fixtures/issues_show.html.haml | 9 ++------- spec/javascripts/issue_spec.js.coffee | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 3d9d514a9c7..c256ec8f41b 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -1,3 +1,4 @@ +#= require flash #= require jquery.waitforimages #= require task_list diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml index dd01b6378c5..470cabeafbb 100644 --- a/spec/javascripts/fixtures/issues_show.html.haml +++ b/spec/javascripts/fixtures/issues_show.html.haml @@ -2,13 +2,8 @@ .hidden { display: none !important; } .flash-container - - if alert - .flash-alert - = alert - - - elsif notice - .flash-notice - = notice + .flash-alert + .flash-notice .status-box.status-box-open Open .status-box.status-box-closed.hidden Closed diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index f0a57d99c56..7e67c778861 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -1,4 +1,3 @@ -#= require flash #= require issue describe 'Issue', -> -- cgit v1.2.3 From 672cbbff959c66ba10740ed17671d93060410d93 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 24 Dec 2015 15:33:51 +0100 Subject: Only allow group/project members to mention `@all` --- CHANGELOG | 1 + app/controllers/projects_controller.rb | 2 +- app/models/concerns/mentionable.rb | 2 +- lib/banzai/filter/redactor_filter.rb | 6 +++--- lib/banzai/filter/reference_filter.rb | 6 +++++- lib/banzai/filter/reference_gatherer_filter.rb | 8 +++++++- lib/banzai/filter/user_reference_filter.rb | 14 +++++++++++++- lib/gitlab/reference_extractor.rb | 19 ++++++++++++------- spec/lib/banzai/filter/user_reference_filter_spec.rb | 19 ++++++++++++++++--- 9 files changed, 59 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a80b776affa..e7897a0fe77 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.4.0 (unreleased) - Implement new UI for group page - Implement search inside emoji picker - Add project permissions to all project API endpoints (Stan Hu) + - Only allow group/project members to mention `@all` v 8.3.1 (unreleased) - Fix Error 500 when global milestones have slashes (Stan Hu) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index bf5e25ff895..682dbf2766a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -178,7 +178,7 @@ class ProjectsController < ApplicationController def markdown_preview text = params[:text] - ext = Gitlab::ReferenceExtractor.new(@project, current_user) + ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user) ext.analyze(text) render json: { diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 1fdcda97520..6316ee208b5 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -44,7 +44,7 @@ module Mentionable end def all_references(current_user = self.author, text = nil) - ext = Gitlab::ReferenceExtractor.new(self.project, current_user) + ext = Gitlab::ReferenceExtractor.new(self.project, current_user, self.author) if text ext.analyze(text) diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb index 89e7a79789a..f01a32b5ae5 100644 --- a/lib/banzai/filter/redactor_filter.rb +++ b/lib/banzai/filter/redactor_filter.rb @@ -11,7 +11,7 @@ module Banzai class RedactorFilter < HTML::Pipeline::Filter def call doc.css('a.gfm').each do |node| - unless user_can_reference?(node) + unless user_can_see_reference?(node) # The reference should be replaced by the original text, # which is not always the same as the rendered text. text = node.attr('data-original') || node.text @@ -24,12 +24,12 @@ module Banzai private - def user_can_reference?(node) + def user_can_see_reference?(node) if node.has_attribute?('data-reference-filter') reference_type = node.attr('data-reference-filter') reference_filter = Banzai::Filter.const_get(reference_type) - reference_filter.user_can_reference?(current_user, node, context) + reference_filter.user_can_see_reference?(current_user, node, context) else true end diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index 33457a3f361..5275ac8bf95 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -12,7 +12,7 @@ module Banzai # :project (required) - Current project, ignored if reference is cross-project. # :only_path - Generate path-only links. class ReferenceFilter < HTML::Pipeline::Filter - def self.user_can_reference?(user, node, context) + def self.user_can_see_reference?(user, node, context) if node.has_attribute?('data-project') project_id = node.attr('data-project').to_i return true if project_id == context[:project].try(:id) @@ -24,6 +24,10 @@ module Banzai end end + def self.user_can_reference?(user, node, context) + true + end + def self.referenced_by(node) raise NotImplementedError, "#{self} does not implement #{__method__}" end diff --git a/lib/banzai/filter/reference_gatherer_filter.rb b/lib/banzai/filter/reference_gatherer_filter.rb index 855f238ac1e..12412ff7ea9 100644 --- a/lib/banzai/filter/reference_gatherer_filter.rb +++ b/lib/banzai/filter/reference_gatherer_filter.rb @@ -35,7 +35,9 @@ module Banzai return if context[:reference_filter] && reference_filter != context[:reference_filter] - return unless reference_filter.user_can_reference?(current_user, node, context) + return if author && !reference_filter.user_can_reference?(author, node, context) + + return unless reference_filter.user_can_see_reference?(current_user, node, context) references = reference_filter.referenced_by(node) return unless references @@ -57,6 +59,10 @@ module Banzai def current_user context[:current_user] end + + def author + context[:author] + end end end end diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index 67c24faf991..7ec771266ed 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -39,7 +39,7 @@ module Banzai end end - def self.user_can_reference?(user, node, context) + def self.user_can_see_reference?(user, node, context) if node.has_attribute?('data-group') group = Group.find(node.attr('data-group')) rescue nil Ability.abilities.allowed?(user, :read_group, group) @@ -48,6 +48,18 @@ module Banzai end end + def self.user_can_reference?(user, node, context) + # Only team members can reference `@all` + if node.has_attribute?('data-project') + project = Project.find(node.attr('data-project')) rescue nil + return false unless project + + user && project.team.member?(user) + else + super + end + end + def call replace_text_nodes_matching(User.reference_pattern) do |content| user_link_filter(content) diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 0a70d21b1ce..be795649e59 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -3,11 +3,12 @@ require 'banzai' module Gitlab # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor < Banzai::ReferenceExtractor - attr_accessor :project, :current_user + attr_accessor :project, :current_user, :author - def initialize(project, current_user = nil) + def initialize(project, current_user = nil, author = nil) @project = project @current_user = current_user + @author = author @references = {} @@ -20,18 +21,22 @@ module Gitlab %i(user label merge_request snippet commit commit_range).each do |type| define_method("#{type}s") do - @references[type] ||= references(type, project: project, current_user: current_user) + @references[type] ||= references(type, reference_context) end end def issues - options = { project: project, current_user: current_user } - if project && project.jira_tracker? - @references[:external_issue] ||= references(:external_issue, options) + @references[:external_issue] ||= references(:external_issue, reference_context) else - @references[:issue] ||= references(:issue, options) + @references[:issue] ||= references(:issue, reference_context) end end + + private + + def reference_context + { project: project, current_user: current_user, author: author } + end end end diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 3534bf97784..8bdebae1841 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -37,9 +37,22 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do .to eq urls.namespace_project_url(project.namespace, project) end - it 'adds to the results hash' do - result = reference_pipeline_result("Hey #{reference}") - expect(result[:references][:user]).to eq [project.creator] + context "when the author is a member of the project" do + + it 'adds to the results hash' do + result = reference_pipeline_result("Hey #{reference}", author: project.creator) + expect(result[:references][:user]).to eq [project.creator] + end + end + + context "when the author is not a member of the project" do + + let(:other_user) { create(:user) } + + it "doesn't add to the results hash" do + result = reference_pipeline_result("Hey #{reference}", author: other_user) + expect(result[:references][:user]).to eq [] + end end end -- cgit v1.2.3 From 396c5c97ddbbe0744d9c27d855c22e2a57546ea1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 24 Dec 2015 20:34:13 +0100 Subject: Fix specs --- spec/models/concerns/mentionable_spec.rb | 4 ++++ spec/services/notification_service_spec.rb | 1 + 2 files changed, 5 insertions(+) diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 6653621a83e..20f0c561e44 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' describe Mentionable do include Mentionable + def author + nil + end + describe :references do let(:project) { create(:project) } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index d7a898e85ff..213adace14b 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -115,6 +115,7 @@ describe NotificationService, services: true do before do build_team(note.project) + note.project.team << [note.author, :master] ActionMailer::Base.deliveries.clear end -- cgit v1.2.3 From c6d25083626c9a97a0ae442298b59c2ff9351f3a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 24 Dec 2015 16:26:52 -0500 Subject: Truncate page_description to 30 words --- app/helpers/page_layout_helper.rb | 4 ++-- spec/helpers/page_layout_helper_spec.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index c4eb09bea2b..4f1276f93ec 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -30,9 +30,9 @@ module PageLayoutHelper @page_description ||= page_description_default if description.present? - @page_description = description + @page_description = description.squish else - sanitize(@page_description.squish, tags: []) + sanitize(@page_description, tags: []).truncate_words(30) end end diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 5d95beac908..530e9bab343 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -22,6 +22,19 @@ describe PageLayoutHelper do expect(helper.page_description).to eq 'Foo Bar Baz' end + it 'truncates' do + helper.page_description <<-LOREM.strip_heredoc + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo + ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis + dis parturient montes, nascetur ridiculus mus. Donec quam felis, + ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa + quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, + arcu. + LOREM + + expect(helper.page_description).to end_with 'quam felis,...' + end + it 'sanitizes all HTML' do helper.page_description("Bold

Header

") -- cgit v1.2.3 From 99dc1fce5ed84fb78bd993423db9c470021ea3a2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 24 Dec 2015 16:53:35 -0500 Subject: Use `request.fullpath` for `og:url` tag --- app/views/layouts/_head.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index ad1dacac1c8..d1cb07eaa66 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -11,7 +11,7 @@ %meta{property: 'og:title', content: page_title} %meta{property: 'og:description', content: page_description} %meta{property: 'og:image', content: page_image} - %meta{property: 'og:url', content: url_for(only_path: false)} + %meta{property: 'og:url', content: request.base_url + request.fullpath} %title= page_title -- cgit v1.2.3 From ab3d855c0e1869fd1986c3bcdf7519f6b1cf1fa8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 24 Dec 2015 17:03:54 -0500 Subject: Add support for `twitter:label` meta tags --- app/helpers/page_layout_helper.rb | 24 +++++++++++++++++++ app/models/concerns/issuable.rb | 8 +++++++ app/views/layouts/_head.html.haml | 1 + app/views/projects/issues/show.html.haml | 5 ++-- app/views/projects/merge_requests/_show.html.haml | 5 ++-- spec/helpers/page_layout_helper_spec.rb | 29 +++++++++++++++++++++++ spec/models/concerns/issuable_spec.rb | 18 ++++++++++++++ 7 files changed, 86 insertions(+), 4 deletions(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 4f1276f93ec..791cb9e50bd 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -58,6 +58,30 @@ module PageLayoutHelper end end + # Define or get attributes to be used as Twitter card metadata + # + # map - Hash of label => data pairs. Keys become labels, values become data + # + # Raises ArgumentError if given more than two attributes + def page_card_attributes(map = {}) + raise ArgumentError, 'cannot provide more than two attributes' if map.length > 2 + + @page_card_attributes ||= {} + @page_card_attributes = map.reject { |_,v| v.blank? } if map.present? + @page_card_attributes + end + + def page_card_meta_tags + tags = '' + + page_card_attributes.each_with_index do |pair, i| + tags << tag(:meta, property: "twitter:label#{i + 1}", content: pair[0]) + tags << tag(:meta, property: "twitter:data#{i + 1}", content: pair[1]) + end + + tags.html_safe + end + def header_title(title = nil, title_url = nil) if title @header_title = title diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index f56fd3e02d4..919833f6df5 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -161,6 +161,14 @@ module Issuable self.class.to_s.underscore end + # Returns a Hash of attributes to be used for Twitter card metadata + def card_attributes + { + 'Author' => author.try(:name), + 'Assignee' => assignee.try(:name) + } + end + def notes_with_associations notes.includes(:author, :project) end diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index d1cb07eaa66..434605e59ae 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -12,6 +12,7 @@ %meta{property: 'og:description', content: page_description} %meta{property: 'og:image', content: page_image} %meta{property: 'og:url', content: request.base_url + request.fullpath} + = page_card_meta_tags %title= page_title diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index f2a261ab426..f548383008d 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,5 +1,6 @@ -- page_title "#{@issue.title} (##{@issue.iid})", "Issues" -- page_description @issue.description +- page_title "#{@issue.title} (##{@issue.iid})", "Issues" +- page_description @issue.description +- page_card_attributes @issue.card_attributes = render "header_title" diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 75f44557964..ba7c2c01e93 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,5 +1,6 @@ -- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" -- page_description @merge_request.description +- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" +- page_description @merge_request.description +- page_card_attributes @merge_request.card_attributes = render "header_title" diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 530e9bab343..fd7107779f6 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -97,4 +97,33 @@ describe PageLayoutHelper do end end end + + describe 'page_card_attributes' do + it 'raises ArgumentError when given more than two attributes' do + map = { foo: 'foo', bar: 'bar', baz: 'baz' } + + expect { helper.page_card_attributes(map) }. + to raise_error(ArgumentError, /more than two attributes/) + end + + it 'rejects blank values' do + map = { foo: 'foo', bar: '' } + helper.page_card_attributes(map) + + expect(helper.page_card_attributes).to eq({ foo: 'foo' }) + end + end + + describe 'page_card_meta_tags' do + it 'returns the twitter:label and twitter:data tags' do + allow(helper).to receive(:page_card_attributes).and_return(foo: 'bar') + + tags = helper.page_card_meta_tags + + aggregate_failures do + expect(tags).to include %q() + expect(tags).to include %q() + end + end + end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 0f13c4410cd..901eb936688 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -81,4 +81,22 @@ describe Issue, "Issuable" do expect(hook_data[:object_attributes]).to eq(issue.hook_attrs) end end + + describe '#card_attributes' do + it 'includes the author name' do + allow(issue).to receive(:author).and_return(double(name: 'Robert')) + allow(issue).to receive(:assignee).and_return(nil) + + expect(issue.card_attributes). + to eq({'Author' => 'Robert', 'Assignee' => nil}) + end + + it 'includes the assignee name' do + allow(issue).to receive(:author).and_return(double(name: 'Robert')) + allow(issue).to receive(:assignee).and_return(double(name: 'Douwe')) + + expect(issue.card_attributes). + to eq({'Author' => 'Robert', 'Assignee' => 'Douwe'}) + end + end end -- cgit v1.2.3 From ff8cd116a04d187f63222b3b29ebb6818dfd65e5 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 25 Dec 2015 01:07:00 -0800 Subject: Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll `git` doesn't work properly when `--follow` and `--skip` are specified together. We could even be **omitting commits in the Web log** as a result. Here are the gory details. Let's say you ran: ``` git log -n=5 --skip=2 README ``` This is the working case since it omits `--follow`. This is what happens: 1. `git` starts at `HEAD` and traverses down the tree until it finds the top-most commit relevant to README. 2. Once this is found, this commit is returned via `get_revision_1()`. 3. If the `skip_count` is positive, decrement and repeat step 2. Otherwise go onto step 4. 4. `show_log()` gets called with that commit. 5. Repeat step 1 until we have all five entries. That's exactly what we want. What happens when you use `--follow`? You have to understand how step 1 is performed: * When you specify a pathspec on the command-line (e.g. README), a flag `prune` [gets set here](https://github.com/git/git/blob/master/revision.c#L2351). * If the `prune` flag is active, `get_commit_action()` determines whether the commit should be [scanned for matching paths](https://github.com/git/git/blob/master/revision.c#L2989). * In the case of `--follow`, however, `prune` is [disabled here](https://github.com/git/git/blob/master/revision.c#L2350). * As a result, a commit is never scanned for matching paths and therefore never pruned. `HEAD` will always get returned as the first commit, even if it's not relevant to the README. * Making matters worse, the `--skip` in the example above would actually skip every other after `HEAD` N times. If README were changed in these skipped commits, we would actually miss information! Since git uses a matching algorithm to determine whether a file was renamed, I believe `git` needs to generate a diff of each commit to do this and traverse each commit one-by-one to do this. I think that's the rationale for disabling the `prune` functionality since you can't just do a simple string comparison. Closes #4181, #4229, #3574, #2410 --- CHANGELOG | 1 + app/models/repository.rb | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2e12889cb70..f110b16e1f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.4.0 (unreleased) - Add link to merge request on build detail page. v 8.3.2 (unreleased) + - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) - Enable "Add key" button when user fills in a proper key v 8.3.1 diff --git a/app/models/repository.rb b/app/models/repository.rb index 9f688e3b45b..a9bf4eb4033 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -76,7 +76,9 @@ class Repository path: path, limit: limit, offset: offset, - follow: path.present? + # --follow doesn't play well with --skip. See: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520 + follow: false } commits = Gitlab::Git::Commit.where(options) -- cgit v1.2.3 From a1b63e12526c069a8754b5e64deb9eb15668832a Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 24 Dec 2015 19:46:35 +0200 Subject: revert back vote buttons to issue and MR pages --- CHANGELOG | 1 + app/assets/javascripts/awards_handler.coffee | 15 ++++++++------- app/models/note.rb | 9 ++++++++- spec/models/note_spec.rb | 11 ++++++++--- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 78d717a709f..a20c3978a11 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.4.0 (unreleased) - Add "Frequently used" category to emoji picker - Add CAS support (tduehr) - Add link to merge request on build detail page. + - Revert back upvote and downvote button to the issue and MR pages v 8.3.2 (unreleased) - Enable "Add key" button when user fills in a proper key diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 04bf5cc7bb5..eb1c3669032 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -43,15 +43,19 @@ class @AwardsHandler decrementCounter: (emoji) -> counter = @findEmojiIcon(emoji).siblings(".counter") + emojiIcon = counter.parent() if parseInt(counter.text()) > 1 counter.text(parseInt(counter.text()) - 1) - counter.parent().removeClass("active") + emojiIcon.removeClass("active") @removeMeFromAuthorList(emoji) + else if emoji =="thumbsup" || emoji == "thumbsdown" + emojiIcon.tooltip("destroy") + counter.text(0) + emojiIcon.removeClass("active") else - award = counter.parent() - award.tooltip("destroy") - award.remove() + emojiIcon.tooltip("destroy") + emojiIcon.remove() removeMeFromAuthorList: (emoji) -> award_block = @findEmojiIcon(emoji).parent() @@ -127,9 +131,6 @@ class @AwardsHandler getFrequentlyUsedEmojis: -> frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",") - - frequently_used_emojis = ["thumbsup", "thumbsdown"].concat(frequently_used_emojis) - _.compact(_.uniq(frequently_used_emojis)) renderFrequentlyUsedBlock: -> diff --git a/app/models/note.rb b/app/models/note.rb index 8c5b5836f9a..1222d99cf1f 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -107,9 +107,16 @@ class Note < ActiveRecord::Base end def grouped_awards + notes = {} + awards.select(:note).distinct.map do |note| - [ note.note, where(note: note.note) ] + notes[note.note] = where(note: note.note) end + + notes["thumbsup"] ||= Note.none + notes["thumbsdown"] ||= Note.none + + notes end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index b7006fa5e68..593d8f76215 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -137,9 +137,14 @@ describe Note, models: true do create :note, note: "smile", is_award: true end - it "returns grouped array of notes" do - expect(Note.grouped_awards.first.first).to eq("smile") - expect(Note.grouped_awards.first.last).to match_array(Note.all) + it "returns grouped hash of notes" do + expect(Note.grouped_awards.keys.size).to eq(3) + expect(Note.grouped_awards["smile"]).to match_array(Note.all) + end + + it "returns thumbsup and thumbsdown always" do + expect(Note.grouped_awards["thumbsup"]).to match_array(Note.none) + expect(Note.grouped_awards["thumbsdown"]).to match_array(Note.none) end end -- cgit v1.2.3 From 195dc3a7461468b060b9a9c6860aca37cebc9d8d Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 25 Dec 2015 12:08:53 +0200 Subject: add sorting of awards --- app/helpers/issues_helper.rb | 12 ++++++++++++ app/views/votes/_votes_block.html.haml | 2 +- features/steps/project/issues/award_emoji.rb | 2 +- spec/helpers/issues_helper_spec.rb | 7 +++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 4fe84322199..c1053554fbd 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -120,6 +120,18 @@ module IssuesHelper end end + def awards_sort(awards) + awards.sort_by do |award, notes| + if award == "thumbsup" + 0 + elsif award == "thumbsdown" + 1 + else + 2 + end + end.to_h + end + # Required for Banzai::Filter::IssueReferenceFilter module_function :url_for_issue end diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index e16187bb42f..ce0a0113403 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,5 +1,5 @@ .awards.votes-block - - votable.notes.awards.grouped_awards.each do |emoji, notes| + - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes| .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)} = emoji_icon(emoji) .counter diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index a7e15398819..12cf8860ec6 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -37,7 +37,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps step 'I have award added' do page.within '.awards' do expect(page).to have_selector '.award' - expect(page.find('.award .counter')).to have_content '1' + expect(page.find('.award.active .counter')).to have_content '1' end end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 04e795025d2..68df460da2d 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -141,4 +141,11 @@ describe IssuesHelper do expect(note_active_class(Note.all, @note.author)).to eq("active") end end + + describe "#awards_sort" do + it "sorts a hash so thumbsup and thumbsdown are always on top" do + data = {"thumbsdown" => "some value", "lifter" => "some value", "thumbsup" => "some value"} + expect(awards_sort(data).keys).to eq(["thumbsup", "thumbsdown", "lifter"]) + end + end end -- cgit v1.2.3 From 5605e1591401796c19c91967976c98ad1ae32015 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 25 Dec 2015 14:10:55 +0200 Subject: fix spinach --- features/steps/project/issues/award_emoji.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 12cf8860ec6..1404f34cfe0 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -15,15 +15,17 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I click to emoji in the picker' do - page.within '.emoji-menu' do + page.within '.emoji-menu-content' do page.first('.emoji-icon').click end end step 'I can remove it by clicking to icon' do page.within '.awards' do - page.first('.award').click - expect(page).to_not have_selector '.award' + expect do + page.find('.award.active').click + sleep 0.1 + end.to change{ page.all(".award").size }.from(3).to(2) end end -- cgit v1.2.3 From 9a0e16f4548bca25f6efc6cd7a4dd0af42b60042 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 25 Dec 2015 13:20:20 +0100 Subject: Fix spec --- spec/services/notification_service_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 213adace14b..c103752198d 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -127,6 +127,8 @@ describe NotificationService, services: true do note.project.team.members.each do |member| # User with disabled notification should not be notified next if member.id == @u_disabled.id + # Author should not be notified + next if member.id == note.author.id should_email(member) end -- cgit v1.2.3 From 6438c288a89ad1e17893eded187eba2785035aa3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 25 Dec 2015 13:22:32 +0100 Subject: Satisfy Rubocop --- spec/models/concerns/issuable_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 901eb936688..f9d3c56750f 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -88,7 +88,7 @@ describe Issue, "Issuable" do allow(issue).to receive(:assignee).and_return(nil) expect(issue.card_attributes). - to eq({'Author' => 'Robert', 'Assignee' => nil}) + to eq({ 'Author' => 'Robert', 'Assignee' => nil }) end it 'includes the assignee name' do @@ -96,7 +96,7 @@ describe Issue, "Issuable" do allow(issue).to receive(:assignee).and_return(double(name: 'Douwe')) expect(issue.card_attributes). - to eq({'Author' => 'Robert', 'Assignee' => 'Douwe'}) + to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' }) end end end -- cgit v1.2.3 From 4a2514ff0e1cf88786dcaf168b50af540568e79d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 25 Dec 2015 13:27:43 +0100 Subject: Add og:site_name --- app/views/layouts/_head.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 434605e59ae..2002f66af0e 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,5 +1,3 @@ -- page_title "GitLab" - %head{prefix: "og: http://ogp.me/ns#"} %meta{charset: "utf-8"} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} @@ -8,12 +6,14 @@ %meta{name: 'referrer', content: 'origin-when-cross-origin'} -# Open Graph - http://ogp.me/ + %meta{property: 'og:site_name', content: "GitLab"} %meta{property: 'og:title', content: page_title} %meta{property: 'og:description', content: page_description} %meta{property: 'og:image', content: page_image} %meta{property: 'og:url', content: request.base_url + request.fullpath} = page_card_meta_tags + - page_title "GitLab" %title= page_title = favicon_link_tag 'favicon.ico' -- cgit v1.2.3 From 0980dda6cdab6dc35cfa9a8b7b7b67fcd73ea699 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 25 Dec 2015 13:35:57 +0100 Subject: Add more twitter metatags. --- app/views/layouts/_head.html.haml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 2002f66af0e..0be00da0b9e 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -6,11 +6,17 @@ %meta{name: 'referrer', content: 'origin-when-cross-origin'} -# Open Graph - http://ogp.me/ + %meta{property: 'og:type', content: "object"} %meta{property: 'og:site_name', content: "GitLab"} %meta{property: 'og:title', content: page_title} %meta{property: 'og:description', content: page_description} %meta{property: 'og:image', content: page_image} %meta{property: 'og:url', content: request.base_url + request.fullpath} + + %meta{property: 'twitter:card', content: "summary"} + %meta{property: 'twitter:title', content: page_title} + %meta{property: 'twitter:description', content: page_description} + %meta{property: 'twitter:image', content: page_image} = page_card_meta_tags - page_title "GitLab" -- cgit v1.2.3 From 6d385e3365ffa68a3e4ddb7bf332522cceaccaff Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 25 Dec 2015 13:38:54 +0100 Subject: Add link to twitter docs --- app/views/layouts/_head.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 0be00da0b9e..2e0bd2007a3 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,9 +1,9 @@ %head{prefix: "og: http://ogp.me/ns#"} %meta{charset: "utf-8"} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} + %meta{name: 'referrer', content: 'origin-when-cross-origin'} %meta{name: "description", content: page_description} - %meta{name: 'referrer', content: 'origin-when-cross-origin'} -# Open Graph - http://ogp.me/ %meta{property: 'og:type', content: "object"} @@ -13,6 +13,7 @@ %meta{property: 'og:image', content: page_image} %meta{property: 'og:url', content: request.base_url + request.fullpath} + -# Twitter Card - https://dev.twitter.com/cards/types/summary %meta{property: 'twitter:card', content: "summary"} %meta{property: 'twitter:title', content: page_title} %meta{property: 'twitter:description', content: page_description} -- cgit v1.2.3 From e081edc1c474dec558f54983f0d0dc8c5841eaf6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 25 Dec 2015 15:23:06 +0200 Subject: Clean up CRIME security doc [ci skip] --- doc/security/crime_vulnerability.md | 78 +++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/doc/security/crime_vulnerability.md b/doc/security/crime_vulnerability.md index d716bff85a5..94ba5d1375d 100644 --- a/doc/security/crime_vulnerability.md +++ b/doc/security/crime_vulnerability.md @@ -1,59 +1,63 @@ # How we manage the TLS protocol CRIME vulnerability -> CRIME ("Compression Ratio Info-leak Made Easy") is a security exploit against -secret web cookies over connections using the HTTPS and SPDY protocols that also -use data compression.[1][2] When used to recover the content of secret -authentication cookies, it allows an attacker to perform session hijacking on an +> CRIME ("Compression Ratio Info-leak Made Easy") is a security exploit against +secret web cookies over connections using the HTTPS and SPDY protocols that also +use data compression. When used to recover the content of secret +authentication cookies, it allows an attacker to perform session hijacking on an authenticated web session, allowing the launching of further attacks. ([CRIME](https://en.wikipedia.org/w/index.php?title=CRIME&oldid=692423806)) ### Description -The TLS Protocol CRIME Vulnerability affects compression over HTTPS therefore -it warns against using SSL Compression, take gzip for example, or SPDY which -optionally uses compression as well. +The TLS Protocol CRIME Vulnerability affects compression over HTTPS, therefore +it warns against using SSL Compression (for example gzip) or SPDY which +optionally uses compression as well. -GitLab support both gzip and SPDY and manages the CRIME vulnerability by -deactivating gzip when https is enabled and not activating the compression -feature on SDPY. +GitLab supports both gzip and [SPDY][ngx-spdy] and mitigates the CRIME +vulnerability by deactivating gzip when HTTPS is enabled. You can see the +sources of the files in question: -Take a look at our configuration file for NGINX if you'd like to explore how the -conditions are setup for gzip deactivation on this link: -[GitLab NGINX File](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb). - -For SPDY you can also watch how its implmented on NGINX at [GitLab NGINX File](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb) -but take into consideration the NGINX documentation on its default state here: -[Module ngx_http_spdy_module](http://nginx.org/en/docs/http/ngx_http_spdy_module.html). +* [Source installation NGINX file][source-nginx] +* [Omnibus installation NGINX file][omnibus-nginx] +Although SPDY is enabled in Omnibus installations, CRIME relies on compression +(the 'C') and the default compression level in NGINX's SPDY module is 0 +(no compression). ### Nessus -The Nessus scanner reports a possible CRIME vunerability for GitLab similar to the -following format: - - Description +The Nessus scanner, [reports a possible CRIME vulnerability][nessus] in GitLab +similar to the following format: - This remote service has one of two configurations that are known to be required for the CRIME attack: - SSL/TLS compression is enabled. - TLS advertises the SPDY protocol earlier than version 4. +``` +Description - ... +This remote service has one of two configurations that are known to be required for the CRIME attack: +SSL/TLS compression is enabled. +TLS advertises the SPDY protocol earlier than version 4. - Output +... - The following configuration indicates that the remote service may be vulnerable to the CRIME attack: - SPDY support earlier than version 4 is advertised. +Output -*[This](http://www.tenable.com/plugins/index.php?view=single&id=62565) is a complete description from Nessus.* +The following configuration indicates that the remote service may be vulnerable to the CRIME attack: +SPDY support earlier than version 4 is advertised. +``` -From the report above its important to note that Nessus is only checkng if TLS -advertises the SPDY protocol earlier than version 4, it does not perform an -attack nor does it check if compression is enabled. With just this approach it +From the report above it is important to note that Nessus is only checking if +TLS advertises the SPDY protocol earlier than version 4, it does not perform an +attack nor does it check if compression is enabled. With just this approach, it cannot tell that SPDY's compression is disabled and not subject to the CRIME -vulnerbility. +vulnerability. + +### References +* Nginx ["Module ngx_http_spdy_module"][ngx-spdy] +* Tenable Network Security, Inc. ["Transport Layer Security (TLS) Protocol CRIME Vulnerability"][nessus] +* Wikipedia contributors, ["CRIME"][wiki-crime] Wikipedia, The Free Encyclopedia -### Reference -* Nginx. "Module ngx_http_spdy_module", Fri. 18 Dec. -* Tenable Network Security, Inc. "Transport Layer Security (TLS) Protocol CRIME Vulnerability", Web. 15 Dec. -* Wikipedia contributors. "CRIME." Wikipedia, The Free Encyclopedia. Wikipedia, The Free Encyclopedia, 25 Nov. 2015. Web. 15 Dec. 2015. \ No newline at end of file +[source-nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/nginx/gitlab-ssl +[omnibus-nginx]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb +[ngx-spdy]: http://nginx.org/en/docs/http/ngx_http_spdy_module.html +[nessus]: https://www.tenable.com/plugins/index.php?view=single&id=62565 +[wiki-crime]: https://en.wikipedia.org/wiki/CRIME -- cgit v1.2.3 From c79ffa01b49128c609099b7048649082b5e327fb Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 25 Dec 2015 15:46:01 +0200 Subject: satisfy rubocop --- spec/helpers/issues_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 68df460da2d..ffd8ebae029 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -144,7 +144,7 @@ describe IssuesHelper do describe "#awards_sort" do it "sorts a hash so thumbsup and thumbsdown are always on top" do - data = {"thumbsdown" => "some value", "lifter" => "some value", "thumbsup" => "some value"} + data = { "thumbsdown" => "some value", "lifter" => "some value", "thumbsup" => "some value" } expect(awards_sort(data).keys).to eq(["thumbsup", "thumbsdown", "lifter"]) end end -- cgit v1.2.3 From 17ee4e6cc633199d2134b8c75dce2418898c8aec Mon Sep 17 00:00:00 2001 From: Igor Matsko Date: Thu, 17 Dec 2015 15:15:29 +0300 Subject: Fix user autocomplete uri --- app/assets/javascripts/users_select.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 12abf806bfa..9467011799f 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -117,5 +117,5 @@ class @UsersSelect callback(users) buildUrl: (url) -> - url = gon.relative_url_root + url if gon.relative_url_root? + url = gon.relative_url_root.replace(/\/$/, '') + url if gon.relative_url_root? return url -- cgit v1.2.3 From f0d46b4de6acce7d09deda5449972ebb238591cb Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 26 Dec 2015 13:50:47 +0200 Subject: Remove incomplete text [ci skip] --- doc/ci/triggers/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index 2c1de5859f8..d41aed2eb2e 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -11,9 +11,6 @@ You can add a new trigger by going to your project's **Settings > Triggers**. The **Add trigger** button will create a new token which you can then use to trigger a rebuild of this particular project. -Once at least one trigger is created, on the **Triggers** page you will find -some descriptive information on how you can - Every new trigger you create, gets assigned a different token which you can then use inside your scripts or `.gitlab-ci.yml`. You also have a nice overview of the time the triggers were last used. -- cgit v1.2.3 From 8eed3ab35953a4d142fbb8e14fdaba3cdb9d3a29 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 26 Dec 2015 13:56:33 +0200 Subject: Fix typo on triggers docs [ci skip] --- doc/ci/triggers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index d41aed2eb2e..9f7c1bfe6a0 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -108,7 +108,7 @@ Now, whenever a new tag is pushed on project A, the build will run and the `stage: test` complete successfully. _**Note:** If your project is public, passing the token in plain text is -probably not the wiser idea, so you might want to use a +probably not the wisest idea, so you might want to use a [secure variable](../variables/README.md#user-defined-variables-secure-variables) for that purpose._ -- cgit v1.2.3 From 4ebd447a056ad3af624814ac3094ce132d38b1e9 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Sun, 27 Dec 2015 17:36:08 +0100 Subject: Use environment variables to configure GitLab. --- config/initializers/1_settings.rb | 2 +- doc/README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 816cb0c02a9..c151ea01d55 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -144,7 +144,7 @@ Settings.gitlab['default_projects_limit'] ||= 10 Settings.gitlab['default_branch_protection'] ||= 2 Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil? Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil? -Settings.gitlab['host'] ||= 'localhost' +Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost' Settings.gitlab['ssh_host'] ||= Settings.gitlab.host Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 diff --git a/doc/README.md b/doc/README.md index f40a257b42f..d82ff8b908b 100644 --- a/doc/README.md +++ b/doc/README.md @@ -56,6 +56,7 @@ - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Log system](logs/logs.md) Log system. +- [Environmental Variables](administration/environmental_variables.md) to configure GitLab. - [Operations](operations/README.md) Keeping GitLab up and running - [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. -- cgit v1.2.3 From fe2f1b44813116ed1e1cafef293460ae8a4cf11a Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Sun, 27 Dec 2015 17:36:50 +0100 Subject: Add documentation and example file for environent variables. --- config/database.yml.env | 9 ++++++ doc/administration/enviroment_variables.md | 45 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 config/database.yml.env create mode 100644 doc/administration/enviroment_variables.md diff --git a/config/database.yml.env b/config/database.yml.env new file mode 100644 index 00000000000..4fdc8eee7f5 --- /dev/null +++ b/config/database.yml.env @@ -0,0 +1,9 @@ +<%= ENV['RAILS_ENV'] %>: + adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql'' %> + encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %> + database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %> + pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %> + username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %> + password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %> + host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %> + port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %> diff --git a/doc/administration/enviroment_variables.md b/doc/administration/enviroment_variables.md new file mode 100644 index 00000000000..8c9e2fd03ad --- /dev/null +++ b/doc/administration/enviroment_variables.md @@ -0,0 +1,45 @@ +# Environment Variables + +## Introduction + +Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package. + +But if you prefer to use environment variables we allow that too. + +## Supported environment variables + +Variable | Type | Explanation +--- | --- | --- +GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation +GITLAB_HOST | url | hostname of the GitLab server includes http or https +RAILS_ENV | production/development/staging/test | Rails environment +DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5 + +## Complete database variables + +As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set: + +- adapter +- database +- username +- password +- host +- port + +To do set these please `cp config/database.yml.rb config/database.yml` and use the following variables: + +Variable | Default +--- | --- +GITLAB_DATABASE_ADAPTER | postgresql +GITLAB_DATABASE_ENCODING | unicode +GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV'] +GITLAB_DATABASE_POOL | 10 +GITLAB_DATABASE_USERNAME | root +GITLAB_DATABASE_PASSWORD | +GITLAB_DATABASE_HOST | localhost +GITLAB_DATABASE_PORT | 5432 + +## Other variables + +We welcome merge requests to make more settings configurable via variables. +Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}". -- cgit v1.2.3 From 9f7d379c2a018c86671bfc157fe1f0cf4e31e25e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 27 Dec 2015 09:03:06 -0800 Subject: Add support for Google reCAPTCHA in user registration to prevent spammers --- CHANGELOG | 1 + Gemfile | 3 +++ Gemfile.lock | 3 +++ app/controllers/registrations_controller.rb | 23 +++++++++++++++++++++++ app/controllers/sessions_controller.rb | 13 +++++++------ app/views/devise/shared/_signup_box.html.haml | 3 +++ config/gitlab.yml.example | 6 ++++++ config/initializers/1_settings.rb | 7 +++++++ config/initializers/recaptcha.rb | 6 ++++++ 9 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 config/initializers/recaptcha.rb diff --git a/CHANGELOG b/CHANGELOG index a20c3978a11..e54a6669080 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.4.0 (unreleased) + - Add support for Google reCAPTCHA in user registration to prevent spammers (Stan Hu) - Implement new UI for group page - Implement search inside emoji picker - Add API support for looking up a user by username (Stan Hu) diff --git a/Gemfile b/Gemfile index db54bf2f186..a63c84fe94b 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,9 @@ gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd' gem 'rack-oauth2', '~> 1.2.1' +# reCAPTCHA protection +gem 'recaptcha', require: 'recaptcha/rails' + # Two-factor authentication gem 'devise-two-factor', '~> 2.0.0' gem 'rqrcode-rails3', '~> 0.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index 4f4b10c0fb7..664a02c4bab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -566,6 +566,8 @@ GEM trollop rdoc (3.12.2) json (~> 1.4) + recaptcha (1.0.2) + json redcarpet (3.3.3) redis (3.2.2) redis-actionpack (4.0.1) @@ -924,6 +926,7 @@ DEPENDENCIES raphael-rails (~> 2.1.2) rblineprof rdoc (~> 3.6) + recaptcha redcarpet (~> 3.3.3) redis-namespace redis-rails (~> 4.0.0) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 3b3dc86cb68..283831f8149 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,10 +1,21 @@ class RegistrationsController < Devise::RegistrationsController before_action :signup_enabled? + include Recaptcha::Verify def new redirect_to(new_user_session_path) end + def create + if !Gitlab.config.recaptcha.enabled || verify_recaptcha + super + else + flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code." + flash.delete :recaptcha_error + render action: 'new' + end + end + def destroy DeleteUserService.new(current_user).execute(current_user) @@ -38,4 +49,16 @@ class RegistrationsController < Devise::RegistrationsController def sign_up_params params.require(:user).permit(:username, :email, :name, :password, :password_confirmation) end + + def resource_name + :user + end + + def resource + @resource ||= User.new + end + + def devise_mapping + @devise_mapping ||= Devise.mappings[:user] + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 1b60d3e27d0..da4b35d322b 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,5 +1,6 @@ class SessionsController < Devise::SessionsController include AuthenticatesWithTwoFactor + include Recaptcha::ClientHelper prepend_before_action :authenticate_with_two_factor, only: [:create] prepend_before_action :store_redirect_path, only: [:new] @@ -40,7 +41,7 @@ class SessionsController < Devise::SessionsController User.find(session[:otp_user_id]) end end - + def store_redirect_path redirect_path = if request.referer.present? && (params['redirect_to_referer'] == 'yes') @@ -87,14 +88,14 @@ class SessionsController < Devise::SessionsController provider = Gitlab.config.omniauth.auto_sign_in_with_provider return unless provider.present? - # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is - # registered or no alert at all. In case of another alert (such as a blocked user), it is safer + # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is + # registered or no alert at all. In case of another alert (such as a blocked user), it is safer # to do nothing to prevent redirection loops with certain Omniauth providers. return unless flash[:alert].blank? || flash[:alert] == I18n.t('devise.failure.unauthenticated') - + # Prevent alert from popping up on the first page shown after authentication. - flash[:alert] = nil - + flash[:alert] = nil + redirect_to user_omniauth_authorize_path(provider.to_sym) end diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 9dc6aeffd59..3c079d7e2f8 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -17,6 +17,9 @@ = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true .form-group.append-bottom-20#password-strength = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true + %div + - if Gitlab.config.recaptcha.enabled + = recaptcha_tags %div = f.submit "Sign up", class: "btn-create btn" diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index db68b5512b8..79fc7423b31 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -346,6 +346,12 @@ production: &base # cas3: # session_duration: 28800 + # reCAPTCHA settings. See: http://www.google.com/recaptcha + recaptcha: + enabled: false + public_key: 'YOUR_PUBLIC_KEY' + private_key: 'YOUR_PRIVATE_KEY' + # Shared file storage settings shared: # path: /mnt/gitlab # Default: shared diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 816cb0c02a9..0dc4838fec1 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -131,6 +131,13 @@ Settings.omniauth.cas3['session_duration'] ||= 8.hours Settings.omniauth['session_tickets'] ||= Settingslogic.new({}) Settings.omniauth.session_tickets['cas3'] = 'ticket' +# ReCAPTCHA settings +Settings['recaptcha'] ||= Settingslogic.new({}) +Settings.recaptcha['enabled'] = false if Settings.recaptcha['enabled'].nil? +Settings.recaptcha['public_key'] ||= Settings.recaptcha['public_key'] +Settings.recaptcha['private_key'] ||= Settings.recaptcha['private_key'] + + Settings['shared'] ||= Settingslogic.new({}) Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root) diff --git a/config/initializers/recaptcha.rb b/config/initializers/recaptcha.rb new file mode 100644 index 00000000000..7509e327ae1 --- /dev/null +++ b/config/initializers/recaptcha.rb @@ -0,0 +1,6 @@ +if Gitlab.config.recaptcha.enabled + Recaptcha.configure do |config| + config.public_key = Gitlab.config.recaptcha['public_key'] + config.private_key = Gitlab.config.recaptcha['private_key'] + end +end -- cgit v1.2.3 From e7314e4c278dc754882c95a4c037e72fd583f4d2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 27 Dec 2015 20:34:46 -0500 Subject: Fix the "Show all" link for the keyboard shortcut modal --- app/assets/javascripts/dispatcher.js.coffee | 2 +- app/assets/javascripts/shortcuts.js.coffee | 10 +++++++--- app/views/help/_shortcuts.html.haml | 8 -------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 599b4c49540..69e061ce6e9 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -49,7 +49,7 @@ class Dispatcher new DropzoneInput($('.release-form')) when 'projects:merge_requests:show' new Diff() - shortcut_handler = new ShortcutsIssuable() + shortcut_handler = new ShortcutsIssuable(true) new ZenMode() when "projects:merge_requests:diffs" new Diff() diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee index e9aeb1e9525..4d915bfc8c5 100644 --- a/app/assets/javascripts/shortcuts.js.coffee +++ b/app/assets/javascripts/shortcuts.js.coffee @@ -7,7 +7,7 @@ class @Shortcuts selectiveHelp: (e) => Shortcuts.showHelp(e, @enabledHelp) - + @showHelp: (e, location) -> if $('#modal-shortcuts').length > 0 $('#modal-shortcuts').modal('show') @@ -17,8 +17,7 @@ class @Shortcuts dataType: 'script', success: (e) -> if location and location.length > 0 - for l in location - $(l).show() + $(l).show() for l in location else $('.hidden-shortcut').show() $('.js-more-help-button').remove() @@ -28,3 +27,8 @@ class @Shortcuts @focusSearch: (e) -> $('#search').focus() e.preventDefault() + +$(document).on 'click.more_help', '.js-more-help-button', (e) -> + $(@).remove() + $('.hidden-shortcut').show() + e.preventDefault() diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 7e801b5332d..e8e331dd109 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -219,11 +219,3 @@ %td.shortcut .key r %td Reply (quoting selected text) - - -:javascript - $('.js-more-help-button').click(function (e) { - $(this).remove()l - $('.hidden-shortcut').show(); - e.preventDefault(); - }); -- cgit v1.2.3 From 7a20c6da9155fe112ceb3ec9e83cd0255eb15858 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 27 Dec 2015 21:19:01 -0500 Subject: Bump brakeman to ~> 3.1.0 --- Gemfile | 2 +- Gemfile.lock | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index db54bf2f186..cdf8884da91 100644 --- a/Gemfile +++ b/Gemfile @@ -214,7 +214,7 @@ gem 'net-ssh', '~> 3.0.1' group :development do gem "foreman" - gem 'brakeman', '3.0.1', require: false + gem 'brakeman', '~> 3.1.0', require: false gem "annotate", "~> 2.6.0" gem "letter_opener", '~> 1.1.2' diff --git a/Gemfile.lock b/Gemfile.lock index 4f4b10c0fb7..59051b9f36f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,15 +84,17 @@ GEM bootstrap-sass (3.3.5) autoprefixer-rails (>= 5.0.0.1) sass (>= 3.2.19) - brakeman (3.0.1) + brakeman (3.1.4) erubis (~> 2.6) fastercsv (~> 1.5) haml (>= 3.0, < 5.0) - highline (~> 1.6.20) + highline (>= 1.6.20, < 2.0) multi_json (~> 1.2) - ruby2ruby (~> 2.1.1) - ruby_parser (~> 3.5.0) + ruby2ruby (>= 2.1.1, < 2.3.0) + ruby_parser (~> 3.7.0) + safe_yaml (>= 1.0) sass (~> 3.0) + slim (>= 1.3.6, < 4.0) terminal-table (~> 1.4) browser (1.0.1) builder (3.2.2) @@ -347,7 +349,7 @@ GEM html2haml (>= 1.0.1) railties (>= 4.0.1) hashie (3.4.3) - highline (1.6.21) + highline (1.7.8) hike (1.2.3) hipchat (1.5.2) httparty @@ -636,10 +638,10 @@ GEM ruby-saml (1.0.0) nokogiri (>= 1.5.10) uuid (~> 2.3) - ruby2ruby (2.1.4) + ruby2ruby (2.2.0) ruby_parser (~> 3.1) sexp_processor (~> 4.0) - ruby_parser (3.5.0) + ruby_parser (3.7.2) sexp_processor (~> 4.1) rubyntlm (0.5.2) rubypants (0.2.0) @@ -693,6 +695,9 @@ GEM tilt (>= 1.3, < 3) six (0.2.0) slack-notifier (1.2.1) + slim (3.0.6) + temple (~> 0.7.3) + tilt (>= 1.3.3, < 2.1) slop (3.6.0) spinach (0.8.10) colorize @@ -734,6 +739,7 @@ GEM railties (>= 3.2.5, < 5) teaspoon-jasmine (2.2.0) teaspoon (>= 1.0.0) + temple (0.7.6) term-ansicolor (1.3.2) tins (~> 1.0) terminal-table (1.5.2) @@ -830,7 +836,7 @@ DEPENDENCIES better_errors (~> 1.0.1) binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.0) - brakeman (= 3.0.1) + brakeman (~> 3.1.0) browser (~> 1.0.0) bullet bundler-audit -- cgit v1.2.3 From 1bda2e43a2f5ffdd4afa7ae73798ca4e36c0de9f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 27 Dec 2015 21:19:14 -0500 Subject: Prevent an XSS warning from the updated Brakeman --- app/controllers/projects/commits_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 58fb946dbc2..04a88990bf4 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -9,7 +9,7 @@ class Projects::CommitsController < Projects::ApplicationController def show @repo = @project.repository - @limit, @offset = (params[:limit] || 40), (params[:offset] || 0) + @limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i @commits = @repo.commits(@ref, @path, @limit, @offset) @note_counts = project.notes.where(commit_id: @commits.map(&:id)). -- cgit v1.2.3 From 6f0ee5c9089d469a59879fbc0ffd6a2f3d69687e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 27 Dec 2015 19:47:10 -0800 Subject: Fix failed spec --- app/controllers/registrations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 283831f8149..ee1006dea49 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -55,7 +55,7 @@ class RegistrationsController < Devise::RegistrationsController end def resource - @resource ||= User.new + @resource ||= User.new(sign_up_params) end def devise_mapping -- cgit v1.2.3 From 4c6591c9220676c97ddf2dda36e8e855d3196a74 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 27 Dec 2015 19:47:21 -0800 Subject: Make sign-up form retain fields after failed CAPTCHA test --- app/views/devise/shared/_signup_box.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 3c079d7e2f8..49fab016bfa 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -6,17 +6,18 @@ .login-heading %h3 Create an account .login-body + - user = params[:user].present? ? params[:user] : {} = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| .devise-errors = devise_error_messages! %div - = f.text_field :name, class: "form-control top", placeholder: "Name", required: true + = f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true %div - = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true + = f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true %div - = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true + = f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true + = f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true %div - if Gitlab.config.recaptcha.enabled = recaptcha_tags -- cgit v1.2.3 From 9e0f532f3eca474bbb4bdf49ea744afb23178b82 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 27 Dec 2015 20:36:33 -0800 Subject: Add documentation for using reCAPTCHA --- config/initializers/1_settings.rb | 2 +- doc/integration/README.md | 1 + doc/integration/recaptcha.md | 56 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 doc/integration/recaptcha.md diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0dc4838fec1..fb7adc77284 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -133,7 +133,7 @@ Settings.omniauth.session_tickets['cas3'] = 'ticket' # ReCAPTCHA settings Settings['recaptcha'] ||= Settingslogic.new({}) -Settings.recaptcha['enabled'] = false if Settings.recaptcha['enabled'].nil? +Settings.recaptcha['enabled'] = false if Settings.recaptcha['enabled'].nil? Settings.recaptcha['public_key'] ||= Settings.recaptcha['public_key'] Settings.recaptcha['private_key'] ||= Settings.recaptcha['private_key'] diff --git a/doc/integration/README.md b/doc/integration/README.md index 6263353851f..2a9f76533b7 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -13,6 +13,7 @@ See the documentation below for details on how to configure these services. - [Slack](slack.md) Integrate with the Slack chat service - [OAuth2 provider](oauth_provider.md) OAuth2 application creation - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages +- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html) and [advanced Jenkins support](http://doc.gitlab.com/ee/integration/jenkins.html). diff --git a/doc/integration/recaptcha.md b/doc/integration/recaptcha.md new file mode 100644 index 00000000000..7e6f7e7e30a --- /dev/null +++ b/doc/integration/recaptcha.md @@ -0,0 +1,56 @@ +# reCAPTCHA + +GitLab leverages [Google's reCAPTCHA](https://www.google.com/recaptcha/intro/index.html) +to protect against spam and abuse. GitLab displays the CAPTCHA form on the sign-up page +to confirm that a real user, not a bot, is attempting to create an account. + +## Configuration + +To use reCAPTCHA, first you must create a public and private key. + +1. Go to the URL: https://www.google.com/recaptcha/admin + +1. Fill out the form necessary to obtain reCAPTCHA keys. + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For installations from source: + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. Enable reCAPTCHA and add the settings: + + For omnibus package: + + ```ruby + gitlab_rails['recaptcha_enabled'] = true + gitlab_rails['recaptcha_public_key'] = 'YOUR_PUBLIC_KEY' + gitlab_rails['recaptcha_private_key'] = 'YOUR_PUBLIC_KEY' + ``` + + For installation from source: + + ``` + recaptcha: + enabled: true + public_key: 'YOUR_PUBLIC_KEY' + private_key: 'YOUR_PRIVATE_KEY' + ``` + +1. Change 'YOUR_PUBLIC_KEY' to the public key from step 2. + +1. Change 'YOUR_PRIVATE_KEY' to the private key from step 2. + +1. Save the configuration file. + +1. Restart GitLab. -- cgit v1.2.3 From e4ec34468fe84633ac6a66f85d71405746af9be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Sun, 27 Dec 2015 21:37:50 -0500 Subject: Precompile assets before running feature specs. #4662 --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a8da3de83f8..c23a7a3bf0e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,6 +12,7 @@ before_script: spec:feature: script: + - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature tags: - ruby -- cgit v1.2.3 From 004b85540f2542a0c4bbdf62bd19db71adee7917 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 28 Dec 2015 10:11:01 +0200 Subject: Do not show frequently used emojis when empty --- app/assets/javascripts/awards_handler.coffee | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index eb1c3669032..619abb1fb07 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -134,15 +134,16 @@ class @AwardsHandler _.compact(_.uniq(frequently_used_emojis)) renderFrequentlyUsedBlock: -> - frequently_used_emojis = @getFrequentlyUsedEmojis() + if $.cookie('frequently_used_emojis') + frequently_used_emojis = @getFrequentlyUsedEmojis() - ul = $("
    ") + ul = $("
      ") - for emoji in frequently_used_emojis - do (emoji) -> - $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul) + for emoji in frequently_used_emojis + do (emoji) -> + $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul) - $("input.emoji-search").after(ul).after($("
      ").text("Frequently used")) + $("input.emoji-search").after(ul).after($("
      ").text("Frequently used")) setupSearch: -> $("input.emoji-search").keyup (ev) => -- cgit v1.2.3 From 61fba0f4cca1348978efd510ebb57ad55d991fdd Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 28 Dec 2015 09:37:59 +0100 Subject: Thanks Robert for the corrections in the environment variables docs. --- config/database.yml.env | 2 +- doc/administration/enviroment_variables.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/database.yml.env b/config/database.yml.env index 4fdc8eee7f5..b2ff23cb5ab 100644 --- a/config/database.yml.env +++ b/config/database.yml.env @@ -1,5 +1,5 @@ <%= ENV['RAILS_ENV'] %>: - adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql'' %> + adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %> encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %> database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %> pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %> diff --git a/doc/administration/enviroment_variables.md b/doc/administration/enviroment_variables.md index 8c9e2fd03ad..d7f5cb7c21f 100644 --- a/doc/administration/enviroment_variables.md +++ b/doc/administration/enviroment_variables.md @@ -26,7 +26,7 @@ As explained in the [Heroku documentation](https://devcenter.heroku.com/articles - host - port -To do set these please `cp config/database.yml.rb config/database.yml` and use the following variables: +To do so please `cp config/database.yml.env config/database.yml` and use the following variables: Variable | Default --- | --- -- cgit v1.2.3 From 83d42c1518274dc0af0f49fda3a10e846569cbcc Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 25 Dec 2015 18:13:55 +0200 Subject: Revert upvotes and downvotes params to MR API --- app/models/concerns/issuable.rb | 6 ++---- app/models/note.rb | 2 -- doc/api/merge_requests.md | 17 +++++------------ doc/api/notes.md | 3 +-- lib/api/entities.rb | 1 - spec/models/concerns/issuable_spec.rb | 14 ++++++++++++++ 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 919833f6df5..18a00f95b48 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -95,14 +95,12 @@ module Issuable opened? || reopened? end - # Deprecated. Still exists to preserve API compatibility. def downvotes - 0 + notes.awards.where(note: "thumbsdown").count end - # Deprecated. Still exists to preserve API compatibility. def upvotes - 0 + notes.awards.where(note: "thumbsup").count end def subscribed?(user) diff --git a/app/models/note.rb b/app/models/note.rb index 1222d99cf1f..1ca86b2592f 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -346,12 +346,10 @@ class Note < ActiveRecord::Base read_attribute(:system) end - # Deprecated. Still exists to preserve API compatibility. def downvote? false end - # Deprecated. Still exists to preserve API compatibility. def upvote? false end diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 366a1f8abec..8bc0a67067a 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -4,8 +4,7 @@ Get all merge requests for this project. The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). -The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. With GitLab 8.2 the return fields `upvotes` and -`downvotes` are deprecated and always return `0`. +The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. ``` GET /projects/:id/merge_requests @@ -58,7 +57,7 @@ Parameters: ## Get single MR -Shows information about a single merge request. With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and always return `0`. +Shows information about a single merge request. ``` GET /projects/:id/merge_request/:merge_request_id @@ -141,8 +140,6 @@ Parameters: ## Get single MR changes Shows information about the merge request including its files and changes. -With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and -always return `0`. ``` GET /projects/:id/merge_request/:merge_request_id/changes @@ -213,9 +210,7 @@ Parameters: ## Create MR -Creates a new merge request. With GitLab 8.2 the return fields `upvotes` and ` -downvotes` are deprecated and always return `0`. - +Creates a new merge request. ``` POST /projects/:id/merge_requests ``` @@ -266,8 +261,7 @@ If an error occurs, an error number and a message explaining the reason is retur ## Update MR -Updates an existing merge request. You can change the target branch, title, or even close the MR. With GitLab 8.2 the return fields `upvotes` and `downvotes` -are deprecated and always return `0`. +Updates an existing merge request. You can change the target branch, title, or even close the MR. ``` PUT /projects/:id/merge_request/:merge_request_id @@ -318,8 +312,7 @@ If an error occurs, an error number and a message explaining the reason is retur ## Accept MR -Merge changes submitted with MR using this API. With GitLab 8.2 the return -fields `upvotes` and `downvotes` are deprecated and always return `0`. +Merge changes submitted with MR using this API. If merge success you get `200 OK`. diff --git a/doc/api/notes.md b/doc/api/notes.md index 4d7ef288df8..d4d63e825ab 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -6,8 +6,7 @@ Notes are comments on snippets, issues or merge requests. ### List project issue notes -Gets a list of all notes for a single issue. With GitLab 8.2 the return fields -`upvote` and `downvote` are deprecated and always return `false`. +Gets a list of all notes for a single issue. ``` GET /projects/:id/issues/:issue_id/notes diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f8511ac5f5c..26e7c956e8f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -166,7 +166,6 @@ module API class MergeRequest < ProjectEntity expose :target_branch, :source_branch - # deprecated, always returns 0 expose :upvotes, :downvotes expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index f9d3c56750f..021d62cdf0c 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -99,4 +99,18 @@ describe Issue, "Issuable" do to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' }) end end + + describe "votes" do + before do + author = create :user + project = create :empty_project + issue.notes.awards.create!(note: "thumbsup", author: author, project: project) + issue.notes.awards.create!(note: "thumbsdown", author: author, project: project) + end + + it "returns correct values" do + expect(issue.upvotes).to eq(1) + expect(issue.downvotes).to eq(1) + end + end end -- cgit v1.2.3 From ddca57d3f25e52e5e4061dd3610f1269efe2400d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 28 Dec 2015 11:33:40 +0100 Subject: Use String#delete for removing double quotes --- lib/gitlab/metrics/obfuscated_sql.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb index 7b15670aa6b..481aca56efb 100644 --- a/lib/gitlab/metrics/obfuscated_sql.rb +++ b/lib/gitlab/metrics/obfuscated_sql.rb @@ -37,7 +37,7 @@ module Gitlab # InfluxDB escapes double quotes upon output, so lets get rid of them # whenever we can. if Gitlab::Database.postgresql? - sql = sql.gsub('"', '') + sql = sql.delete('"') end sql -- cgit v1.2.3 From db7bbadf95d9f2266a055ee50a67fa895b0f21a9 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 28 Dec 2015 11:34:11 +0100 Subject: Fixed styling of MetricsWorker specs --- spec/workers/metrics_worker_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/workers/metrics_worker_spec.rb b/spec/workers/metrics_worker_spec.rb index 2acd0f8ba30..18260ea0c24 100644 --- a/spec/workers/metrics_worker_spec.rb +++ b/spec/workers/metrics_worker_spec.rb @@ -33,7 +33,7 @@ describe MetricsWorker do it 'drops empty tags' do metrics = worker.prepare_metrics([ - { 'values' => {}, 'tags' => { 'cats' => '', 'dogs' => nil }} + { 'values' => {}, 'tags' => { 'cats' => '', 'dogs' => nil } } ]) expect(metrics).to eq([{ values: {}, tags: {} }]) -- cgit v1.2.3 From 1be5668ae0e663015d384ea7d8b404f9eeb5b478 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 28 Dec 2015 13:14:48 +0100 Subject: Added host option for InfluxDB --- config/gitlab.yml.example | 1 + lib/gitlab/metrics.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 24f3f6f02dc..acfc86bf4d1 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -422,6 +422,7 @@ production: &base # Ban an IP for one hour (3600s) after too many auth attempts # bantime: 3600 metrics: + host: localhost enabled: false # The name of the InfluxDB database to store metrics in. database: gitlab diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index ce89be636d3..d6f60732455 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -52,11 +52,12 @@ module Gitlab # "@foo ||= bar" is _not_ thread-safe. if enabled? @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do + host = Settings.metrics['host'] db = Settings.metrics['database'] user = Settings.metrics['username'] pw = Settings.metrics['password'] - InfluxDB::Client.new(db, username: user, password: pw) + InfluxDB::Client.new(db, host: host, username: user, password: pw) end end end -- cgit v1.2.3 From c68f8533aabe5e6fcc516ac8a67d29aaacb1b40b Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Mon, 28 Dec 2015 14:02:18 +0100 Subject: add issue weight to contributing --- CONTRIBUTING.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 950824e35ab..b9c2b3d2f8e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,6 +155,28 @@ sudo -u git -H bundle exec rake gitlab:env:info) ``` +### Issue weight + +Issue weight allows us to get an idea of the amount of work required to solve +one or multiple issues. This makes it possible to schedule work more accurately. + +You are encouraged to set the weight of any issue. Following the guidelines +below will make it easy to manage this, without unnecessary overhead. + +1. Set weight for any issue at the earliest possible convenience +1. If you don't agree with a set weight, discuss with other developers until +consensus is reached about the weight +1. Issue weights are an abstract measurement of complexity of the issue. Do not +relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring) +and something you want to avoid. +1. Something that has a weight of 1 (or no weight) is really small and simple. +Something that is 9 is rewriting a large fundamental part of GitLab, +which might lead to many hard problems to solve. Changing some text in GitLab +is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9. +1. If something is very large, it should probably be split up in multiple +issues or chunks. You can simply not set the weight of a parent issue and set +weights to children issues. + ## Merge requests We welcome merge requests with fixes and improvements to GitLab code, tests, -- cgit v1.2.3 From 42592201d9ce57817d439c5899b63776872a4175 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 28 Dec 2015 15:28:39 +0100 Subject: Hotfix for builds trace data integrity Issue #4246 --- app/models/ci/build.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7b89fe069ea..e251b1dcd97 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -194,8 +194,11 @@ module Ci end def raw_trace - if File.exist?(path_to_trace) + if File.file?(path_to_trace) File.read(path_to_trace) + elsif File.file?(old_path_to_trace) + # Temporary fix for build trace data integrity + File.read(old_path_to_trace) else # backward compatibility read_attribute :trace @@ -231,6 +234,24 @@ module Ci "#{dir_to_trace}/#{id}.log" end + ## + # Deprecated + # + def old_dir_to_trace + File.join( + Settings.gitlab_ci.builds_path, + created_at.utc.strftime("%Y_%m"), + project.ci_id.to_s + ) + end + + ## + # Deprecated + # + def old_path_to_trace + "#{old_dir_to_trace}/#{id}.log" + end + def token project.runners_token end -- cgit v1.2.3 From 297f83425e8917d6fbebdc00a612aa08ec855b5e Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 28 Dec 2015 16:33:37 +0100 Subject: Restart settings are moved too. --- config/gitlab.yml.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index db68b5512b8..0fa6cd306f2 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -4,8 +4,8 @@ # ########################### NOTE ##################################### # This file should not receive new settings. All configuration options # -# that do not require an application restart are being moved to # -# ApplicationSetting model! # +* are being moved to ApplicationSetting model! # +# If a setting requires an application restart say so in that screen. # # If you change this file in a Merge Request, please also create # # a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests # ######################################################################## -- cgit v1.2.3 From 4465e2eca0162a27142a401e6a46aa78add9177f Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 28 Dec 2015 17:08:15 +0100 Subject: Fix spelling mistake, thanks Connor. --- app/views/groups/edit.html.haml | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 8daac585960..1dea77c2e96 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -31,7 +31,7 @@ .col-sm-10 .checkbox = f.check_box :public - %span.descr Make this group public (even if there is no any public project inside this group) + %span.descr Make this group public (even if there are no public projects inside this group) .form-actions = f.submit 'Save group', class: "btn btn-save" diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index c90133fbf03..d15100fc6d8 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' module Ci describe GitlabCiYamlProcessor, lib: true do let(:path) { 'path' } - + describe "#builds_for_ref" do let(:type) { 'test' } @@ -29,7 +29,7 @@ module Ci when: "on_success" }) end - + describe :only do it "does not return builds if only has another branch" do config = YAML.dump({ @@ -517,7 +517,7 @@ module Ci end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra") end - it "returns errors if there is no any jobs defined" do + it "returns errors if there are no jobs defined" do config = YAML.dump({ before_script: ["bundle update"] }) expect do GitlabCiYamlProcessor.new(config, path) -- cgit v1.2.3 From 4d925f2147884812e349031a19f0d7ced9d5fdaf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 28 Dec 2015 18:00:32 +0100 Subject: Move InfluxDB settings to ApplicationSetting --- .../admin/application_settings_controller.rb | 8 ++++ .../admin/application_settings/_form.html.haml | 53 ++++++++++++++++++++++ config/gitlab.yml.example | 21 --------- config/initializers/metrics.rb | 13 ++++-- db/migrate/20151228150906_influxdb_settings.rb | 18 ++++++++ db/schema.rb | 10 +++- lib/gitlab/metrics.rb | 32 +++++++++---- 7 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 db/migrate/20151228150906_influxdb_settings.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 2f4a855c118..3c332adf1fa 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -67,6 +67,14 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :user_oauth_applications, :shared_runners_enabled, :max_artifacts_size, + :metrics_enabled, + :metrics_host, + :metrics_database, + :metrics_username, + :metrics_password, + :metrics_pool_size, + :metrics_timeout, + :metrics_method_call_threshold, restricted_visibility_levels: [], import_sources: [] ) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 58f5c621f4a..3cada08c2ba 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -156,5 +156,58 @@ .col-sm-10 = f.number_field :max_artifacts_size, class: 'form-control' + %fieldset + %legend Metrics + %p + These settings require a restart to take effect. + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :metrics_enabled do + = f.check_box :metrics_enabled + Enable InfluxDB Metrics + .form-group + = f.label :metrics_host, 'InfluxDB host', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :metrics_host, class: 'form-control', placeholder: 'influxdb.example.com' + .form-group + = f.label :metrics_database, 'InfluxDB database', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :metrics_database, class: 'form-control', placeholder: 'gitlab' + .help-block + The name of the InfluxDB database to store data in. Users will have to + create this database manually, GitLab does not do so automatically. + .form-group + = f.label :metrics_username, 'InfluxDB username', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :metrics_username, class: 'form-control' + .form-group + = f.label :metrics_password, 'InfluxDB password', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :metrics_password, class: 'form-control' + .form-group + = f.label :metrics_pool_size, 'Connection pool size', class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :metrics_pool_size, class: 'form-control' + .help-block + The amount of InfluxDB connections to open. Connections are opened + lazily. Users using multi-threaded application servers should ensure + enough connections are available (at minimum the amount of application + server threads). + .form-group + = f.label :metrics_timeout, 'Connection timeout', class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :metrics_timeout, class: 'form-control' + .help-block + The amount of seconds after which an InfluxDB connection will time + out. + .form-group + = f.label :metrics_method_call_threshold, 'Method Call Threshold (ms)', class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :metrics_method_call_threshold, class: 'form-control' + .help-block + A method call is only tracked when it takes longer to complete than + the given amount of milliseconds. + .form-actions = f.submit 'Save', class: 'btn btn-primary' diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index da6d4005da6..79fc7423b31 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -449,26 +449,9 @@ production: &base # # Ban an IP for one hour (3600s) after too many auth attempts # bantime: 3600 - metrics: - host: localhost - enabled: false - # The name of the InfluxDB database to store metrics in. - database: gitlab - # Credentials to use for logging in to InfluxDB. - # username: - # password: - # The amount of InfluxDB connections to open. - # pool_size: 16 - # The timeout of a connection in seconds. - # timeout: 10 - # The minimum amount of milliseconds a method call has to take before it's - # tracked. Defaults to 10. - # method_call_threshold: 10 development: <<: *base - metrics: - enabled: false test: <<: *base @@ -511,10 +494,6 @@ test: user_filter: '' group_base: 'ou=groups,dc=example,dc=com' admin_group: '' - metrics: - enabled: false staging: <<: *base - metrics: - enabled: false diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index a47d2bf59a6..2e4908192a1 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -32,10 +32,17 @@ if Gitlab::Metrics.enabled? ) Gitlab::Metrics::Instrumentation. - instrument_class_hierarchy(ActiveRecord::Base) do |_, method| - loc = method.source_location + instrument_class_hierarchy(ActiveRecord::Base) do |klass, method| + # Instrumenting the ApplicationSetting class can lead to an infinite + # loop. Since the data is cached any way we don't really need to + # instrument it. + if klass == ApplicationSetting + false + else + loc = method.source_location - loc && loc[0].start_with?(models) && method.source =~ regex + loc && loc[0].start_with?(models) && method.source =~ regex + end end end diff --git a/db/migrate/20151228150906_influxdb_settings.rb b/db/migrate/20151228150906_influxdb_settings.rb new file mode 100644 index 00000000000..3012bd52cfd --- /dev/null +++ b/db/migrate/20151228150906_influxdb_settings.rb @@ -0,0 +1,18 @@ +class InfluxdbSettings < ActiveRecord::Migration + def change + add_column :application_settings, :metrics_enabled, :boolean, default: false + + add_column :application_settings, :metrics_host, :string, + default: 'localhost' + + add_column :application_settings, :metrics_database, :string, + default: 'gitlab' + + add_column :application_settings, :metrics_username, :string + add_column :application_settings, :metrics_password, :string + add_column :application_settings, :metrics_pool_size, :integer, default: 16 + add_column :application_settings, :metrics_timeout, :integer, default: 10 + add_column :application_settings, :metrics_method_call_threshold, + :integer, default: 10 + end +end diff --git a/db/schema.rb b/db/schema.rb index 49fa258660d..dc9ba36d0c7 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: 20151224123230) do +ActiveRecord::Schema.define(version: 20151228150906) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -52,6 +52,14 @@ ActiveRecord::Schema.define(version: 20151224123230) do t.string "runners_registration_token" t.boolean "require_two_factor_authentication", default: false t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.string "metrics_database", default: "gitlab" + t.string "metrics_username" + t.string "metrics_password" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 end create_table "audit_events", force: :cascade do |t| diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index d6f60732455..8039e8e9e9d 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -4,16 +4,29 @@ module Gitlab METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s PATH_REGEX = /^#{RAILS_ROOT}\/?/ + # Returns the current settings, ensuring we _always_ have a default set of + # metrics settings (even during tests, when the migrations are lacking, + # etc). This ensures the application is able to boot up even when the + # migrations have not been executed. + def self.settings + ApplicationSetting.current || { + metrics_pool_size: 16, + metrics_timeout: 10, + metrics_enabled: false, + metrics_method_call_threshold: 10 + } + end + def self.pool_size - Settings.metrics['pool_size'] || 16 + settings[:metrics_pool_size] end def self.timeout - Settings.metrics['timeout'] || 10 + settings[:metrics_timeout] end def self.enabled? - !!Settings.metrics['enabled'] + settings[:metrics_enabled] end def self.mri? @@ -21,7 +34,10 @@ module Gitlab end def self.method_call_threshold - Settings.metrics['method_call_threshold'] || 10 + # This is memoized since this method is called for every instrumented + # method. Loading data from an external cache on every method call slows + # things down too much. + @method_call_threshold ||= settings[:metrics_method_call_threshold] end def self.pool @@ -52,10 +68,10 @@ module Gitlab # "@foo ||= bar" is _not_ thread-safe. if enabled? @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do - host = Settings.metrics['host'] - db = Settings.metrics['database'] - user = Settings.metrics['username'] - pw = Settings.metrics['password'] + host = settings[:metrics_host] + db = settings[:metrics_database] + user = settings[:metrics_username] + pw = settings[:metrics_password] InfluxDB::Client.new(db, host: host, username: user, password: pw) end -- cgit v1.2.3 From 0eab0c6efd6779e4e7c78fae4959b8897c055ecf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 28 Dec 2015 20:28:40 +0100 Subject: Fixed syntax in gitlab.yml.example --- config/gitlab.yml.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 7725fa34031..a26002ec07d 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -4,7 +4,7 @@ # ########################### NOTE ##################################### # This file should not receive new settings. All configuration options # -* are being moved to ApplicationSetting model! # +# * are being moved to ApplicationSetting model! # # If a setting requires an application restart say so in that screen. # # If you change this file in a Merge Request, please also create # # a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests # -- cgit v1.2.3 From eb162c2b2741bfd1ea8dd1b49e12027f7ed03594 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 23 Dec 2015 22:01:38 -0500 Subject: adds settings menu to the top for editting and leaving projects --- app/assets/stylesheets/pages/projects.scss | 7 +++++++ app/views/projects/_home_panel.html.haml | 21 +++++++++++++++++---- app/views/projects/show.html.haml | 6 +----- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index cff3edb7ed2..be6ef43e49c 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -26,6 +26,13 @@ } .project-home-panel { + + .cover-controls { + .project-settings-dropdown { + margin-left: 10px; + } + } + .project-identicon-holder { margin-bottom: 16px; diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index e92115b9b98..c3f710fa3fb 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -18,13 +18,26 @@ = visibility_level_label(@project.visibility_level) .cover-controls - - if can?(current_user, :admin_project, @project) - = link_to edit_project_path(@project), class: 'btn btn-gray' do - = icon('pencil') - if current_user -   = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do = icon('rss') + - access = user_max_access_in_project(current_user.id, @project) + - can_edit = can?(current_user, :admin_project, @project) + - if access || can_edit + %span.dropdown.project-settings-dropdown + %a.dropdown-new.btn.btn-gray.notifications-btn#notifications-button{href: '#', 'data-toggle' => 'dropdown'} + = icon('cog') + = icon('angle-down') + %ul.dropdown-menu.dropdown-menu-right + - if can_edit + %li + = link_to edit_project_path(@project) do + Edit Project + - if access + %li + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), + data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do + Leave project .project-repo-buttons .split-one.count-buttons diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 7466a098e24..5d04776f7f4 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -75,8 +75,4 @@ - if access .prepend-top-20.project-footer .gray-content-block.footer-block.center - You have #{access} access to this project. - - if @project.project_member_by_id(current_user) - = link_to leave_namespace_project_project_members_path(@project.namespace, @project), - data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do - Leave this project + You have #{access} access to this project. \ No newline at end of file -- cgit v1.2.3 From 8ee6abaaf472d7d4562d3c718ad1a68ceb71631f Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 28 Dec 2015 10:36:35 -0500 Subject: fixes tests failing --- app/views/projects/_home_panel.html.haml | 4 ++-- spec/features/projects_spec.rb | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index c3f710fa3fb..0f61e623396 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -25,7 +25,7 @@ - can_edit = can?(current_user, :admin_project, @project) - if access || can_edit %span.dropdown.project-settings-dropdown - %a.dropdown-new.btn.btn-gray.notifications-btn#notifications-button{href: '#', 'data-toggle' => 'dropdown'} + %a.dropdown-new.btn.btn-gray#project-settings-button{href: '#', 'data-toggle' => 'dropdown'} = icon('cog') = icon('angle-down') %ul.dropdown-menu.dropdown-menu-right @@ -37,7 +37,7 @@ %li = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do - Leave project + Leave Project .project-repo-buttons .split-one.count-buttons diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 74b148f5d17..d4a92072a94 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -81,7 +81,10 @@ feature 'Project', feature: true do end it { expect(page).to have_content('You have Master access to this project.') } - it { expect(page).to have_link('Leave this project') } + it { + find('#project-settings').click + expect(page).to have_link('Leave Project') + } end def remove_with_confirm(button_text, confirm_with) -- cgit v1.2.3 From a3469d914aaf28a1184247cbe72e5197ce7ca006 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Mon, 28 Dec 2015 18:21:34 -0200 Subject: reCAPTCHA is configurable through Admin Settings, no reload needed. --- .../admin/application_settings_controller.rb | 3 ++ app/controllers/registrations_controller.rb | 2 +- app/controllers/sessions_controller.rb | 5 +++ app/models/application_setting.rb | 28 ++++++++----- .../admin/application_settings/_form.html.haml | 22 ++++++++++ app/views/devise/shared/_signup_box.html.haml | 2 +- config/gitlab.yml.example | 6 --- config/initializers/1_settings.rb | 6 --- config/initializers/recaptcha.rb | 6 --- ...175719_add_recaptcha_to_application_settings.rb | 9 +++++ db/schema.rb | 5 ++- doc/integration/recaptcha.md | 47 ++++------------------ lib/gitlab/recaptcha.rb | 14 +++++++ 13 files changed, 84 insertions(+), 71 deletions(-) delete mode 100644 config/initializers/recaptcha.rb create mode 100644 db/migrate/20151228175719_add_recaptcha_to_application_settings.rb create mode 100644 lib/gitlab/recaptcha.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 3c332adf1fa..005db13fb9b 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -75,6 +75,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :metrics_pool_size, :metrics_timeout, :metrics_method_call_threshold, + :recaptcha_enabled, + :recaptcha_site_key, + :recaptcha_private_key, restricted_visibility_levels: [], import_sources: [] ) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index ee1006dea49..485aaf45b01 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -7,7 +7,7 @@ class RegistrationsController < Devise::RegistrationsController end def create - if !Gitlab.config.recaptcha.enabled || verify_recaptcha + if Gitlab::Recaptcha.load_configurations! && verify_recaptcha super else flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code." diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index da4b35d322b..825f85199be 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -5,6 +5,7 @@ class SessionsController < Devise::SessionsController prepend_before_action :authenticate_with_two_factor, only: [:create] prepend_before_action :store_redirect_path, only: [:new] before_action :auto_sign_in_with_provider, only: [:new] + before_action :load_recaptcha def new if Gitlab.config.ldap.enabled @@ -108,4 +109,8 @@ class SessionsController < Devise::SessionsController AuditEventService.new(user, user, options). for_authentication.security_event end + + def load_recaptcha + Gitlab::Recaptcha.load_configurations! + end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 7c107da116c..be69d317d73 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -44,24 +44,32 @@ class ApplicationSetting < ActiveRecord::Base attr_accessor :restricted_signup_domains_raw validates :session_expire_delay, - presence: true, - numericality: { only_integer: true, greater_than_or_equal_to: 0 } + presence: true, + numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :home_page_url, - allow_blank: true, - url: true, - if: :home_page_url_column_exist + allow_blank: true, + url: true, + if: :home_page_url_column_exist validates :after_sign_out_path, - allow_blank: true, - url: true + allow_blank: true, + url: true validates :admin_notification_email, - allow_blank: true, - email: true + allow_blank: true, + email: true validates :two_factor_grace_period, - numericality: { greater_than_or_equal_to: 0 } + numericality: { greater_than_or_equal_to: 0 } + + validates :recaptcha_site_key, + presence: true, + if: :recaptcha_enabled + + validates :recaptcha_private_key, + presence: true, + if: :recaptcha_enabled validates_each :restricted_visibility_levels do |record, attr, value| unless value.nil? diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 3cada08c2ba..6b240ffc97b 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -209,5 +209,27 @@ A method call is only tracked when it takes longer to complete than the given amount of milliseconds. + %fieldset + %legend Spam and Anti-bot Protection + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :recaptcha_enabled do + = f.check_box :recaptcha_enabled + Enable reCAPTCHA + %span.help-block#recaptcha_help_block Helps preventing bots from creating accounts + + .form-group + = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :recaptcha_site_key, class: 'form-control' + .help-block + Generate site and private keys here: + %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha + .form-group + = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :recaptcha_private_key, class: 'form-control' + .form-actions = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 49fab016bfa..cb93ff2465e 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -19,7 +19,7 @@ .form-group.append-bottom-20#password-strength = f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true %div - - if Gitlab.config.recaptcha.enabled + - if current_application_settings.recaptcha_enabled = recaptcha_tags %div = f.submit "Sign up", class: "btn-create btn" diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 84f0dfb64c8..2d9f730c183 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -346,12 +346,6 @@ production: &base # cas3: # session_duration: 28800 - # reCAPTCHA settings. See: http://www.google.com/recaptcha - recaptcha: - enabled: false - public_key: 'YOUR_PUBLIC_KEY' - private_key: 'YOUR_PRIVATE_KEY' - # Shared file storage settings shared: # path: /mnt/gitlab # Default: shared diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 045bab739ea..dea59f4fec8 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -131,12 +131,6 @@ Settings.omniauth.cas3['session_duration'] ||= 8.hours Settings.omniauth['session_tickets'] ||= Settingslogic.new({}) Settings.omniauth.session_tickets['cas3'] = 'ticket' -# ReCAPTCHA settings -Settings['recaptcha'] ||= Settingslogic.new({}) -Settings.recaptcha['enabled'] = false if Settings.recaptcha['enabled'].nil? -Settings.recaptcha['public_key'] ||= Settings.recaptcha['public_key'] -Settings.recaptcha['private_key'] ||= Settings.recaptcha['private_key'] - Settings['shared'] ||= Settingslogic.new({}) Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root) diff --git a/config/initializers/recaptcha.rb b/config/initializers/recaptcha.rb deleted file mode 100644 index 7509e327ae1..00000000000 --- a/config/initializers/recaptcha.rb +++ /dev/null @@ -1,6 +0,0 @@ -if Gitlab.config.recaptcha.enabled - Recaptcha.configure do |config| - config.public_key = Gitlab.config.recaptcha['public_key'] - config.private_key = Gitlab.config.recaptcha['private_key'] - end -end diff --git a/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb b/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb new file mode 100644 index 00000000000..259fd0248d2 --- /dev/null +++ b/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb @@ -0,0 +1,9 @@ +class AddRecaptchaToApplicationSettings < ActiveRecord::Migration + def change + change_table :application_settings do |t| + t.boolean :recaptcha_enabled, default: false + t.string :recaptcha_site_key + t.string :recaptcha_private_key + end + end +end diff --git a/db/schema.rb b/db/schema.rb index dc9ba36d0c7..ac6bd905eea 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: 20151228150906) do +ActiveRecord::Schema.define(version: 20151228175719) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -60,6 +60,9 @@ ActiveRecord::Schema.define(version: 20151228150906) do t.integer "metrics_pool_size", default: 16 t.integer "metrics_timeout", default: 10 t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false + t.string "recaptcha_site_key" + t.string "recaptcha_private_key" end create_table "audit_events", force: :cascade do |t| diff --git a/doc/integration/recaptcha.md b/doc/integration/recaptcha.md index 7e6f7e7e30a..a301d1a613c 100644 --- a/doc/integration/recaptcha.md +++ b/doc/integration/recaptcha.md @@ -6,51 +6,18 @@ to confirm that a real user, not a bot, is attempting to create an account. ## Configuration -To use reCAPTCHA, first you must create a public and private key. +To use reCAPTCHA, first you must create a site and private key. 1. Go to the URL: https://www.google.com/recaptcha/admin -1. Fill out the form necessary to obtain reCAPTCHA keys. +2. Fill out the form necessary to obtain reCAPTCHA keys. -1. On your GitLab server, open the configuration file. +3. Login to your GitLab server, with administrator credentials. - For omnibus package: +4. Go to Applications Settings on Admin Area (`admin/application_settings`) - ```sh - sudo editor /etc/gitlab/gitlab.rb - ``` +5. Fill all recaptcha fields with keys from previous steps - For installations from source: +6. Check the `Enable reCAPTCHA` checkbox - ```sh - cd /home/git/gitlab - - sudo -u git -H editor config/gitlab.yml - ``` - -1. Enable reCAPTCHA and add the settings: - - For omnibus package: - - ```ruby - gitlab_rails['recaptcha_enabled'] = true - gitlab_rails['recaptcha_public_key'] = 'YOUR_PUBLIC_KEY' - gitlab_rails['recaptcha_private_key'] = 'YOUR_PUBLIC_KEY' - ``` - - For installation from source: - - ``` - recaptcha: - enabled: true - public_key: 'YOUR_PUBLIC_KEY' - private_key: 'YOUR_PRIVATE_KEY' - ``` - -1. Change 'YOUR_PUBLIC_KEY' to the public key from step 2. - -1. Change 'YOUR_PRIVATE_KEY' to the private key from step 2. - -1. Save the configuration file. - -1. Restart GitLab. +7. Save the configuration. diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb new file mode 100644 index 00000000000..70e7f25d518 --- /dev/null +++ b/lib/gitlab/recaptcha.rb @@ -0,0 +1,14 @@ +module Gitlab + module Recaptcha + def self.load_configurations! + if current_application_settings.recaptcha_enabled + ::Recaptcha.configure do |config| + config.public_key = current_application_settings.recaptcha_site_key + config.private_key = current_application_settings.recaptcha_private_key + end + + true + end + end + end +end -- cgit v1.2.3 From c5c6a945f29921b969663aa3e79e09148145e60c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 28 Dec 2015 23:06:40 +0200 Subject: Fix broken link in permissions page [ci skip] --- doc/permissions/permissions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index bcd00cfc6bf..1be78ac1823 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -6,11 +6,11 @@ If a user is both in a project group and in the project itself, the highest perm If a user is a GitLab administrator they receive all permissions. -On public projects the Guest role is not enforced. -All users will be able to create issues, leave comments, and pull or download the project code. +On public projects the Guest role is not enforced. +All users will be able to create issues, leave comments, and pull or download the project code. To add or import a user, you can follow the [project users and members -documentation](doc/workflow/add-user/add-user.md). +documentation](../workflow/add-user/add-user.md). ## Project -- cgit v1.2.3 From ed214a11ca1566582a084b9dc4b9e79470c1cc18 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 28 Dec 2015 22:14:33 +0100 Subject: Handle missing settings table for metrics This ensures we can still boot, even when the "application_settings" table doesn't exist. --- lib/gitlab/metrics.rb | 16 ++++++++++------ spec/lib/gitlab/metrics_spec.rb | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 8039e8e9e9d..9470633b065 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -9,12 +9,16 @@ module Gitlab # etc). This ensures the application is able to boot up even when the # migrations have not been executed. def self.settings - ApplicationSetting.current || { - metrics_pool_size: 16, - metrics_timeout: 10, - metrics_enabled: false, - metrics_method_call_threshold: 10 - } + if ApplicationSetting.table_exists? and curr = ApplicationSetting.current + curr + else + { + metrics_pool_size: 16, + metrics_timeout: 10, + metrics_enabled: false, + metrics_method_call_threshold: 10 + } + end end def self.pool_size diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index ebc69f8a75f..944642909aa 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -29,7 +29,7 @@ describe Gitlab::Metrics do it 'returns an Array containing a file path and line number' do file, line = described_class.last_relative_application_frame - expect(line).to eq(30) + expect(line).to eq(__LINE__ - 2) expect(file).to eq('spec/lib/gitlab/metrics_spec.rb') end end -- cgit v1.2.3 From d985d08a19d945ad4615b319c9346ec88abf2f97 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 28 Dec 2015 17:37:03 -0500 Subject: adds proper `it` for multi line. adds `find` with correct id for button. --- spec/features/projects_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index d4a92072a94..4836b3b9b14 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -81,10 +81,10 @@ feature 'Project', feature: true do end it { expect(page).to have_content('You have Master access to this project.') } - it { - find('#project-settings').click + it 'click project-settings and find leave project' do + find('#project-settings-button').click expect(page).to have_link('Leave Project') - } + end end def remove_with_confirm(button_text, confirm_with) -- cgit v1.2.3 From 41c74cec09af146ed7fdb4778dd72fe578eb1407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Kahlh=C3=B6fer?= Date: Wed, 2 Dec 2015 07:56:34 +0000 Subject: Downcased user or email search for avatar_icon. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rubén Dávila --- app/helpers/application_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0b00b9a0702..f7f7a1a02d3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -72,7 +72,7 @@ module ApplicationHelper if user_or_email.is_a?(User) user = user_or_email else - user = User.find_by(email: user_or_email) + user = User.find_by(email: user_or_email.downcase) end if user -- cgit v1.2.3 From e619d0b615a394a08ca1d0be59f0028c8e390b88 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 28 Dec 2015 16:59:59 -0800 Subject: When reCAPTCHA is disabled, allow registrations to go through without a code --- app/controllers/registrations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 485aaf45b01..c48175a4c5a 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -7,7 +7,7 @@ class RegistrationsController < Devise::RegistrationsController end def create - if Gitlab::Recaptcha.load_configurations! && verify_recaptcha + if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha super else flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code." -- cgit v1.2.3 From d3807328d8ed9be2915f67708b093269bf0b1b9f Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 29 Dec 2015 10:11:20 +0200 Subject: note votes methids implementation --- app/models/note.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 1ca86b2592f..3d5b663c99f 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -347,11 +347,11 @@ class Note < ActiveRecord::Base end def downvote? - false + is_award && note == "thumbsdown" end def upvote? - false + is_award && note == "thumbsup" end def editable? -- cgit v1.2.3 From 504696453bb7d89278bf021393274e475b84ebbd Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 29 Dec 2015 08:52:06 +0100 Subject: Add hotfix that allows to access build artifacts created before 8.3 This is a temporary hotfix that allows to access build artifacts created before 8.3. See #5257. This needs to be changed after migrating CI build files. Note that `ArtifactUploader` uses `artifacts_path` to create a storage directory before and after parsisting `Ci::Build` instance, before and after moving a file to store (save and fetch a file). --- app/models/ci/build.rb | 37 ++++++++++++++++++++++++++++++++++--- app/uploaders/artifact_uploader.rb | 8 ++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e251b1dcd97..3e67b2771c1 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -196,7 +196,7 @@ module Ci def raw_trace if File.file?(path_to_trace) File.read(path_to_trace) - elsif File.file?(old_path_to_trace) + elsif project.ci_id && File.file?(old_path_to_trace) # Temporary fix for build trace data integrity File.read(old_path_to_trace) else @@ -215,8 +215,8 @@ module Ci end def trace=(trace) - unless Dir.exists? dir_to_trace - FileUtils.mkdir_p dir_to_trace + unless Dir.exists?(dir_to_trace) + FileUtils.mkdir_p(dir_to_trace) end File.write(path_to_trace, trace) @@ -237,6 +237,9 @@ module Ci ## # Deprecated # + # This is a hotfix for CI build data integrity, see #4246 + # Should be removed in 8.4, after CI files migration has been done. + # def old_dir_to_trace File.join( Settings.gitlab_ci.builds_path, @@ -248,10 +251,38 @@ module Ci ## # Deprecated # + # This is a hotfix for CI build data integrity, see #4246 + # Should be removed in 8.4, after CI files migration has been done. + # def old_path_to_trace "#{old_dir_to_trace}/#{id}.log" end + ## + # Deprecated + # + # This contains a hotfix for CI build data integrity, see #4246 + # + # This method is used by `ArtifactUploader` to create a store_dir. + # Warning: Uploader uses it after AND before file has been stored. + # + # This method returns old path to artifacts only if it already exists. + # + def artifacts_path + old = File.join(created_at.utc.strftime('%Y_%m'), + project.ci_id.to_s, + id.to_s) + + old_store = File.join(ArtifactUploader.artifacts_path, old) + return old if project.ci_id && File.directory?(old_store) + + File.join( + created_at.utc.strftime('%Y_%m'), + project.id.to_s, + id.to_s + ) + end + def token project.runners_token end diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb index 1dccc39e7e2..1b0ae6c0056 100644 --- a/app/uploaders/artifact_uploader.rb +++ b/app/uploaders/artifact_uploader.rb @@ -20,16 +20,12 @@ class ArtifactUploader < CarrierWave::Uploader::Base @build, @field = build, field end - def artifacts_path - File.join(build.created_at.utc.strftime('%Y_%m'), build.project.id.to_s, build.id.to_s) - end - def store_dir - File.join(ArtifactUploader.artifacts_path, artifacts_path) + File.join(self.class.artifacts_path, @build.artifacts_path) end def cache_dir - File.join(ArtifactUploader.artifacts_cache_path, artifacts_path) + File.join(self.class.artifacts_cache_path, @build.artifacts_path) end def file_storage? -- cgit v1.2.3 From dddb54f0b47f35ff6e42c1823c0f1385bb6ae30a Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 29 Dec 2015 12:58:32 +0100 Subject: Fix link, filename and crosslink to omnibus docs. --- doc/README.md | 2 +- doc/administration/enviroment_variables.md | 45 -------------------------- doc/administration/environment_variables.md | 50 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 46 deletions(-) delete mode 100644 doc/administration/enviroment_variables.md create mode 100644 doc/administration/environment_variables.md diff --git a/doc/README.md b/doc/README.md index d82ff8b908b..4162cde6dc1 100644 --- a/doc/README.md +++ b/doc/README.md @@ -56,7 +56,7 @@ - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Log system](logs/logs.md) Log system. -- [Environmental Variables](administration/environmental_variables.md) to configure GitLab. +- [Environment Variables](administration/environment_variables.md) to configure GitLab. - [Operations](operations/README.md) Keeping GitLab up and running - [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. diff --git a/doc/administration/enviroment_variables.md b/doc/administration/enviroment_variables.md deleted file mode 100644 index d7f5cb7c21f..00000000000 --- a/doc/administration/enviroment_variables.md +++ /dev/null @@ -1,45 +0,0 @@ -# Environment Variables - -## Introduction - -Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package. - -But if you prefer to use environment variables we allow that too. - -## Supported environment variables - -Variable | Type | Explanation ---- | --- | --- -GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation -GITLAB_HOST | url | hostname of the GitLab server includes http or https -RAILS_ENV | production/development/staging/test | Rails environment -DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5 - -## Complete database variables - -As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set: - -- adapter -- database -- username -- password -- host -- port - -To do so please `cp config/database.yml.env config/database.yml` and use the following variables: - -Variable | Default ---- | --- -GITLAB_DATABASE_ADAPTER | postgresql -GITLAB_DATABASE_ENCODING | unicode -GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV'] -GITLAB_DATABASE_POOL | 10 -GITLAB_DATABASE_USERNAME | root -GITLAB_DATABASE_PASSWORD | -GITLAB_DATABASE_HOST | localhost -GITLAB_DATABASE_PORT | 5432 - -## Other variables - -We welcome merge requests to make more settings configurable via variables. -Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}". diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md new file mode 100644 index 00000000000..5043e69db8d --- /dev/null +++ b/doc/administration/environment_variables.md @@ -0,0 +1,50 @@ +# Environment Variables + +## Introduction + +Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package. + +But if you prefer to use environment variables we allow that too. + +## Supported environment variables + +Variable | Type | Explanation +--- | --- | --- +GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation +GITLAB_HOST | url | hostname of the GitLab server includes http or https +RAILS_ENV | production/development/staging/test | Rails environment +DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5 + +## Complete database variables + +As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set: + +- adapter +- database +- username +- password +- host +- port + +To do so please `cp config/database.yml.env config/database.yml` and use the following variables: + +Variable | Default +--- | --- +GITLAB_DATABASE_ADAPTER | postgresql +GITLAB_DATABASE_ENCODING | unicode +GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV'] +GITLAB_DATABASE_POOL | 10 +GITLAB_DATABASE_USERNAME | root +GITLAB_DATABASE_PASSWORD | +GITLAB_DATABASE_HOST | localhost +GITLAB_DATABASE_PORT | 5432 + +## Adding more variables + +We welcome merge requests to make more settings configurable via variables. +Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}". + +## Omnibus configuration + +It's possible to preconfigure the GitLab image by adding the environment variable: `GITLAB_OMNIBUS_CONFIG` to docker run command. +For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container). -- cgit v1.2.3 From 03478e6d5b98a723fbb349dac2c8495f75909a08 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 29 Dec 2015 13:40:08 +0100 Subject: Strip newlines from obfuscated SQL Newlines aren't really needed and they may mess with InfluxDB's line protocol. --- lib/gitlab/metrics/obfuscated_sql.rb | 2 +- spec/lib/gitlab/metrics/obfuscated_sql_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb index 481aca56efb..2e932fb3049 100644 --- a/lib/gitlab/metrics/obfuscated_sql.rb +++ b/lib/gitlab/metrics/obfuscated_sql.rb @@ -40,7 +40,7 @@ module Gitlab sql = sql.delete('"') end - sql + sql.gsub("\n", ' ') end end end diff --git a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb index 0f01ee588c9..2b681c9fe34 100644 --- a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb +++ b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb @@ -2,6 +2,12 @@ require 'spec_helper' describe Gitlab::Metrics::ObfuscatedSQL do describe '#to_s' do + it 'replaces newlines with a space' do + sql = described_class.new("SELECT x\nFROM y") + + expect(sql.to_s).to eq('SELECT x FROM y') + end + describe 'using single values' do it 'replaces a single integer' do sql = described_class.new('SELECT x FROM y WHERE a = 10') -- cgit v1.2.3 From 620e7bb3d60c3685b494b26e256b793a47621da4 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 29 Dec 2015 13:40:42 +0100 Subject: Write to InfluxDB directly via UDP This removes the need for Sidekiq and any overhead/problems introduced by TCP. There are a few things to take into account: 1. When writing data to InfluxDB you may still get an error if the server becomes unavailable during the write. Because of this we're catching all exceptions and just ignore them (for now). 2. Writing via UDP apparently requires the timestamp to be in nanoseconds. Without this data either isn't written properly. 3. Due to the restrictions on UDP buffer sizes we're writing metrics one by one, instead of writing all of them at once. --- Procfile | 2 +- .../admin/application_settings_controller.rb | 2 +- .../admin/application_settings/_form.html.haml | 10 +-- app/workers/metrics_worker.rb | 33 --------- .../20151229102248_influxdb_udp_port_setting.rb | 5 ++ ...51229112614_influxdb_remote_database_setting.rb | 5 ++ db/schema.rb | 82 +++++++++++----------- lib/gitlab/metrics.rb | 38 +++++++++- lib/gitlab/metrics/metric.rb | 2 +- lib/gitlab/metrics/obfuscated_sql.rb | 2 +- lib/gitlab/metrics/sampler.rb | 2 +- lib/gitlab/metrics/sidekiq_middleware.rb | 7 -- lib/gitlab/metrics/transaction.rb | 2 +- spec/lib/gitlab/metrics/sampler_spec.rb | 2 +- spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb | 8 --- spec/lib/gitlab/metrics/transaction_spec.rb | 2 +- spec/lib/gitlab/metrics_spec.rb | 48 +++++++++++++ spec/workers/metrics_worker_spec.rb | 52 -------------- 18 files changed, 149 insertions(+), 155 deletions(-) delete mode 100644 app/workers/metrics_worker.rb create mode 100644 db/migrate/20151229102248_influxdb_udp_port_setting.rb create mode 100644 db/migrate/20151229112614_influxdb_remote_database_setting.rb delete mode 100644 spec/workers/metrics_worker_spec.rb diff --git a/Procfile b/Procfile index bbafdd33a2d..9cfdee7040f 100644 --- a/Procfile +++ b/Procfile @@ -3,5 +3,5 @@ # lib/support/init.d, which call scripts in bin/ . # web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} -worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -q metrics +worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default # mail_room: bundle exec mail_room -q -c config/mail_room.yml diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 005db13fb9b..10e736fd362 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -69,7 +69,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :max_artifacts_size, :metrics_enabled, :metrics_host, - :metrics_database, + :metrics_port, :metrics_username, :metrics_password, :metrics_pool_size, diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 6b240ffc97b..214e0209bb7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -171,12 +171,14 @@ .col-sm-10 = f.text_field :metrics_host, class: 'form-control', placeholder: 'influxdb.example.com' .form-group - = f.label :metrics_database, 'InfluxDB database', class: 'control-label col-sm-2' + = f.label :metrics_port, 'InfluxDB port', class: 'control-label col-sm-2' .col-sm-10 - = f.text_field :metrics_database, class: 'form-control', placeholder: 'gitlab' + = f.text_field :metrics_port, class: 'form-control', placeholder: '8089' .help-block - The name of the InfluxDB database to store data in. Users will have to - create this database manually, GitLab does not do so automatically. + The UDP port to use for connecting to InfluxDB. InfluxDB requires that + your server configuration specifies a database to store data in when + sending messages to this port, without it metrics data will not be + saved. .form-group = f.label :metrics_username, 'InfluxDB username', class: 'control-label col-sm-2' .col-sm-10 diff --git a/app/workers/metrics_worker.rb b/app/workers/metrics_worker.rb deleted file mode 100644 index b15dc819c5c..00000000000 --- a/app/workers/metrics_worker.rb +++ /dev/null @@ -1,33 +0,0 @@ -class MetricsWorker - include Sidekiq::Worker - - sidekiq_options queue: :metrics - - def perform(metrics) - prepared = prepare_metrics(metrics) - - Gitlab::Metrics.pool.with do |connection| - connection.write_points(prepared) - end - end - - def prepare_metrics(metrics) - metrics.map do |hash| - new_hash = hash.symbolize_keys - - new_hash[:tags].each do |key, value| - if value.blank? - new_hash[:tags].delete(key) - else - new_hash[:tags][key] = escape_value(value) - end - end - - new_hash - end - end - - def escape_value(value) - value.to_s.gsub('=', '\\=') - end -end diff --git a/db/migrate/20151229102248_influxdb_udp_port_setting.rb b/db/migrate/20151229102248_influxdb_udp_port_setting.rb new file mode 100644 index 00000000000..ae0499f936d --- /dev/null +++ b/db/migrate/20151229102248_influxdb_udp_port_setting.rb @@ -0,0 +1,5 @@ +class InfluxdbUdpPortSetting < ActiveRecord::Migration + def change + add_column :application_settings, :metrics_port, :integer, default: 8089 + end +end diff --git a/db/migrate/20151229112614_influxdb_remote_database_setting.rb b/db/migrate/20151229112614_influxdb_remote_database_setting.rb new file mode 100644 index 00000000000..f0e1ee1e7a7 --- /dev/null +++ b/db/migrate/20151229112614_influxdb_remote_database_setting.rb @@ -0,0 +1,5 @@ +class InfluxdbRemoteDatabaseSetting < ActiveRecord::Migration + def change + remove_column :application_settings, :metrics_database + end +end diff --git a/db/schema.rb b/db/schema.rb index ac6bd905eea..df7f72d5ad4 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: 20151228175719) do +ActiveRecord::Schema.define(version: 20151229112614) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -33,36 +33,36 @@ ActiveRecord::Schema.define(version: 20151228175719) do t.datetime "created_at" t.datetime "updated_at" t.string "home_page_url" - t.integer "default_branch_protection", default: 2 - t.boolean "twitter_sharing_enabled", default: true + t.integer "default_branch_protection", default: 2 + t.boolean "twitter_sharing_enabled", default: true t.text "restricted_visibility_levels" - t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false + t.boolean "version_check_enabled", default: true + t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" - t.boolean "user_oauth_applications", default: true + t.boolean "user_oauth_applications", default: true t.string "after_sign_out_path" - t.integer "session_expire_delay", default: 10080, null: false + t.integer "session_expire_delay", default: 10080, null: false t.text "import_sources" t.text "help_page_text" t.string "admin_notification_email" - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.string "metrics_database", default: "gitlab" + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" t.string "metrics_username" t.string "metrics_password" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 t.boolean "recaptcha_enabled", default: false t.string "recaptcha_site_key" t.string "recaptcha_private_key" + t.integer "metrics_port", default: 8089 end create_table "audit_events", force: :cascade do |t| @@ -796,12 +796,12 @@ ActiveRecord::Schema.define(version: 20151228175719) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -809,22 +809,22 @@ ActiveRecord::Schema.define(version: 20151228175719) do t.datetime "created_at" t.datetime "updated_at" t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", default: 1, null: false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", default: 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" @@ -833,23 +833,23 @@ ActiveRecord::Schema.define(version: 20151228175719) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false t.string "location" t.string "encrypted_otp_secret" t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false + t.boolean "otp_required_for_login", default: false, null: false t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false t.string "unlock_token" t.datetime "otp_grace_period_started_at" end diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 9470633b065..0869d007ca5 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -66,6 +66,39 @@ module Gitlab end end + def self.submit_metrics(metrics) + prepared = prepare_metrics(metrics) + + pool.with do |connection| + prepared.each do |metric| + begin + connection.write_points([metric]) + rescue StandardError + end + end + end + end + + def self.prepare_metrics(metrics) + metrics.map do |hash| + new_hash = hash.symbolize_keys + + new_hash[:tags].each do |key, value| + if value.blank? + new_hash[:tags].delete(key) + else + new_hash[:tags][key] = escape_value(value) + end + end + + new_hash + end + end + + def self.escape_value(value) + value.to_s.gsub('=', '\\=') + end + @hostname = Socket.gethostname # When enabled this should be set before being used as the usual pattern @@ -73,11 +106,12 @@ module Gitlab if enabled? @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do host = settings[:metrics_host] - db = settings[:metrics_database] user = settings[:metrics_username] pw = settings[:metrics_password] + port = settings[:metrics_port] - InfluxDB::Client.new(db, host: host, username: user, password: pw) + InfluxDB::Client. + new(udp: { host: host, port: port }, username: user, password: pw) end end end diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb index f592f4e571f..79241f56874 100644 --- a/lib/gitlab/metrics/metric.rb +++ b/lib/gitlab/metrics/metric.rb @@ -26,7 +26,7 @@ module Gitlab process_type: Sidekiq.server? ? 'sidekiq' : 'rails' ), values: @values, - timestamp: @created_at.to_i + timestamp: @created_at.to_i * 1_000_000_000 } end end diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb index 2e932fb3049..fe97d7a0534 100644 --- a/lib/gitlab/metrics/obfuscated_sql.rb +++ b/lib/gitlab/metrics/obfuscated_sql.rb @@ -40,7 +40,7 @@ module Gitlab sql = sql.delete('"') end - sql.gsub("\n", ' ') + sql.tr("\n", ' ') end end end diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/sampler.rb index 828ee1f8c62..998578e1c0a 100644 --- a/lib/gitlab/metrics/sampler.rb +++ b/lib/gitlab/metrics/sampler.rb @@ -46,7 +46,7 @@ module Gitlab end def flush - MetricsWorker.perform_async(@metrics.map(&:to_hash)) + Metrics.submit_metrics(@metrics.map(&:to_hash)) end def sample_memory_usage diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb index ec10707d1fb..ad441decfa2 100644 --- a/lib/gitlab/metrics/sidekiq_middleware.rb +++ b/lib/gitlab/metrics/sidekiq_middleware.rb @@ -5,13 +5,6 @@ module Gitlab # This middleware is intended to be used as a server-side middleware. class SidekiqMiddleware def call(worker, message, queue) - # We don't want to track the MetricsWorker itself as otherwise we'll end - # up in an infinite loop. - if worker.class == MetricsWorker - yield - return - end - trans = Transaction.new begin diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index 568f9d6ae0c..a61dbd989e7 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -59,7 +59,7 @@ module Gitlab end def submit - MetricsWorker.perform_async(@metrics.map(&:to_hash)) + Metrics.submit_metrics(@metrics.map(&:to_hash)) end end end diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb index 69376c0b79b..51a941c48cd 100644 --- a/spec/lib/gitlab/metrics/sampler_spec.rb +++ b/spec/lib/gitlab/metrics/sampler_spec.rb @@ -38,7 +38,7 @@ describe Gitlab::Metrics::Sampler do describe '#flush' do it 'schedules the metrics using Sidekiq' do - expect(MetricsWorker).to receive(:perform_async). + expect(Gitlab::Metrics).to receive(:submit_metrics). with([an_instance_of(Hash)]) sampler.sample_memory_usage diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb index 05214efc565..5882e7d81c7 100644 --- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb @@ -11,14 +11,6 @@ describe Gitlab::Metrics::SidekiqMiddleware do middleware.call(worker, 'test', :test) { nil } end - - it 'does not track jobs of the MetricsWorker' do - worker = MetricsWorker.new - - expect(Gitlab::Metrics::Transaction).to_not receive(:new) - - middleware.call(worker, 'test', :test) { nil } - end end describe '#tag_worker' do diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb index 5f17ff8ee75..6862fc9e2d1 100644 --- a/spec/lib/gitlab/metrics/transaction_spec.rb +++ b/spec/lib/gitlab/metrics/transaction_spec.rb @@ -68,7 +68,7 @@ describe Gitlab::Metrics::Transaction do it 'submits the metrics to Sidekiq' do transaction.track_self - expect(MetricsWorker).to receive(:perform_async). + expect(Gitlab::Metrics).to receive(:submit_metrics). with([an_instance_of(Hash)]) transaction.submit diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 944642909aa..6c0682cac4d 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -33,4 +33,52 @@ describe Gitlab::Metrics do expect(file).to eq('spec/lib/gitlab/metrics_spec.rb') end end + + describe '#submit_metrics' do + it 'prepares and writes the metrics to InfluxDB' do + connection = double(:connection) + pool = double(:pool) + + expect(pool).to receive(:with).and_yield(connection) + expect(connection).to receive(:write_points).with(an_instance_of(Array)) + expect(Gitlab::Metrics).to receive(:pool).and_return(pool) + + described_class.submit_metrics([{ 'series' => 'kittens', 'tags' => {} }]) + end + end + + describe '#prepare_metrics' do + it 'returns a Hash with the keys as Symbols' do + metrics = described_class. + prepare_metrics([{ 'values' => {}, 'tags' => {} }]) + + expect(metrics).to eq([{ values: {}, tags: {} }]) + end + + it 'escapes tag values' do + metrics = described_class.prepare_metrics([ + { 'values' => {}, 'tags' => { 'foo' => 'bar=' } } + ]) + + expect(metrics).to eq([{ values: {}, tags: { 'foo' => 'bar\\=' } }]) + end + + it 'drops empty tags' do + metrics = described_class.prepare_metrics([ + { 'values' => {}, 'tags' => { 'cats' => '', 'dogs' => nil } } + ]) + + expect(metrics).to eq([{ values: {}, tags: {} }]) + end + end + + describe '#escape_value' do + it 'escapes an equals sign' do + expect(described_class.escape_value('foo=')).to eq('foo\\=') + end + + it 'casts values to Strings' do + expect(described_class.escape_value(10)).to eq('10') + end + end end diff --git a/spec/workers/metrics_worker_spec.rb b/spec/workers/metrics_worker_spec.rb deleted file mode 100644 index 18260ea0c24..00000000000 --- a/spec/workers/metrics_worker_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'spec_helper' - -describe MetricsWorker do - let(:worker) { described_class.new } - - describe '#perform' do - it 'prepares and writes the metrics to InfluxDB' do - connection = double(:connection) - pool = double(:pool) - - expect(pool).to receive(:with).and_yield(connection) - expect(connection).to receive(:write_points).with(an_instance_of(Array)) - expect(Gitlab::Metrics).to receive(:pool).and_return(pool) - - worker.perform([{ 'series' => 'kittens', 'tags' => {} }]) - end - end - - describe '#prepare_metrics' do - it 'returns a Hash with the keys as Symbols' do - metrics = worker.prepare_metrics([{ 'values' => {}, 'tags' => {} }]) - - expect(metrics).to eq([{ values: {}, tags: {} }]) - end - - it 'escapes tag values' do - metrics = worker.prepare_metrics([ - { 'values' => {}, 'tags' => { 'foo' => 'bar=' } } - ]) - - expect(metrics).to eq([{ values: {}, tags: { 'foo' => 'bar\\=' } }]) - end - - it 'drops empty tags' do - metrics = worker.prepare_metrics([ - { 'values' => {}, 'tags' => { 'cats' => '', 'dogs' => nil } } - ]) - - expect(metrics).to eq([{ values: {}, tags: {} }]) - end - end - - describe '#escape_value' do - it 'escapes an equals sign' do - expect(worker.escape_value('foo=')).to eq('foo\\=') - end - - it 'casts values to Strings' do - expect(worker.escape_value(10)).to eq('10') - end - end -end -- cgit v1.2.3 From 701e5de9108faa65a118e8822560d39fd8ea9996 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 29 Dec 2015 15:49:12 +0100 Subject: Use Gitlab::CurrentSettings for InfluxDB This ensures we can still start up even when not connecting to a database. --- lib/gitlab/metrics.rb | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 0869d007ca5..2d266ccfe9e 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -1,36 +1,21 @@ module Gitlab module Metrics + extend Gitlab::CurrentSettings + RAILS_ROOT = Rails.root.to_s METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s PATH_REGEX = /^#{RAILS_ROOT}\/?/ - # Returns the current settings, ensuring we _always_ have a default set of - # metrics settings (even during tests, when the migrations are lacking, - # etc). This ensures the application is able to boot up even when the - # migrations have not been executed. - def self.settings - if ApplicationSetting.table_exists? and curr = ApplicationSetting.current - curr - else - { - metrics_pool_size: 16, - metrics_timeout: 10, - metrics_enabled: false, - metrics_method_call_threshold: 10 - } - end - end - def self.pool_size - settings[:metrics_pool_size] + current_application_settings[:metrics_pool_size] || 16 end def self.timeout - settings[:metrics_timeout] + current_application_settings[:metrics_timeout] || 10 end def self.enabled? - settings[:metrics_enabled] + current_application_settings[:metrics_enabled] || false end def self.mri? @@ -41,7 +26,8 @@ module Gitlab # This is memoized since this method is called for every instrumented # method. Loading data from an external cache on every method call slows # things down too much. - @method_call_threshold ||= settings[:metrics_method_call_threshold] + @method_call_threshold ||= + (current_application_settings[:metrics_method_call_threshold] || 10) end def self.pool @@ -105,10 +91,10 @@ module Gitlab # "@foo ||= bar" is _not_ thread-safe. if enabled? @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do - host = settings[:metrics_host] - user = settings[:metrics_username] - pw = settings[:metrics_password] - port = settings[:metrics_port] + host = current_application_settings[:metrics_host] + user = current_application_settings[:metrics_username] + pw = current_application_settings[:metrics_password] + port = current_application_settings[:metrics_port] InfluxDB::Client. new(udp: { host: host, port: port }, username: user, password: pw) -- cgit v1.2.3 From a80f2b792cd2bdd6626bfe712750a58b863cc037 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 29 Dec 2015 16:29:45 -0500 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 57f0b9f30d5..5d0726a52ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.4.0 (unreleased) - - Add support for Google reCAPTCHA in user registration to prevent spammers (Stan Hu) - Implement new UI for group page - Implement search inside emoji picker - Add API support for looking up a user by username (Stan Hu) @@ -10,12 +9,13 @@ v 8.4.0 (unreleased) - Expose Git's version in the admin area - Add "Frequently used" category to emoji picker - Add CAS support (tduehr) - - Add link to merge request on build detail page. + - Add link to merge request on build detail page - Revert back upvote and downvote button to the issue and MR pages + - Enable "Add key" button when user fills in a proper key (Stan Hu) -v 8.3.2 (unreleased) +v 8.3.2 - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) - - Enable "Add key" button when user fills in a proper key + - Add support for Google reCAPTCHA in user registration v 8.3.1 - Fix Error 500 when global milestones have slashes (Stan Hu) -- cgit v1.2.3 From 59533d47dd901a1b4a7e4eda382a0a25ce9a1eef Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 28 Dec 2015 18:10:46 -0800 Subject: Fix project transfer e-mail sending incorrect paths in e-mail notification The introduction of ActiveJob and `deliver_now` in 7f214cee7 caused a race condition where the mailer would be invoked before the project was committed to the database, causing the transfer e-mail notification to show the old path instead of the new one. Closes #4670 --- CHANGELOG | 3 +++ app/models/project.rb | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5d0726a52ce..425beb17743 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,9 @@ v 8.4.0 (unreleased) - Revert back upvote and downvote button to the issue and MR pages - Enable "Add key" button when user fills in a proper key (Stan Hu) +v 8.3.3 (unreleased) + - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) + v 8.3.2 - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) - Add support for Google reCAPTCHA in user registration diff --git a/app/models/project.rb b/app/models/project.rb index 75f85310d5f..017471995ec 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -555,7 +555,9 @@ class Project < ActiveRecord::Base end def send_move_instructions(old_path_with_namespace) - NotificationService.new.project_was_moved(self, old_path_with_namespace) + # New project path needs to be committed to the DB or notification will + # retrieve stale information + run_after_commit { NotificationService.new.project_was_moved(self, old_path_with_namespace) } end def owner -- cgit v1.2.3 From 8662f4737b8d5eeebbe8e5e527f6e2148fea2ddf Mon Sep 17 00:00:00 2001 From: Nathan Lowe Date: Tue, 29 Dec 2015 20:58:18 -0500 Subject: docs: raketasks: Add documentation on uploading to mounted shares --- doc/raketasks/backup_restore.md | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 093450a6de3..cdd6652b7b0 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -153,6 +153,49 @@ with the name of your bucket: } ``` +### Uploading to locally mounted shares + +You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by +using the [`Local`](https://github.com/fog/fog-local#usage) storage provider. +The directory pointed to by the `local_root` key **must** be owned by the `git` +user **when mounted** (mounting with the `uid=` of the `git` user for `CIFS` and +`SMB`) or the user that you are executing the backup tasks under (for omnibus +packages, this is the `git` user). + +The `backup_upload_remote_directory` **must** be set in addition to the +`local_root` key. This is the sub directory inside the mounted directory that +backups will be copied to, and will be created if it does not exist. If the +directory that you want to copy the tarballs to is the root of your mounted +directory, just use `.` instead. + +For omnibus packages: + +```ruby +gitlab_rails['backup_upload_connection'] = { + :provider => 'Local', + :local_root => '/mnt/backups' +} + +# The directory inside the mounted folder to copy backups to +# Use '.' to store them in the root directory +gitlab_rails['backup_upload_remote_directory'] = 'gitlab_backups' +``` + +For installations from source: + +```yaml + backup: + # snip + upload: + # Fog storage connection settings, see http://fog.io/storage/ . + connection: + provider: Local + local_root: '/mnt/backups' + # The directory inside the mounted folder to copy backups to + # Use '.' to store them in the root directory + remote_directory: 'gitlab_backups' +``` + ## Backup archive permissions The backup archives created by GitLab (123456_gitlab_backup.tar) will have owner/group git:git and 0600 permissions by default. -- cgit v1.2.3 From a6fd7b74ee21c830ac65a1e77696dcc8284dce7c Mon Sep 17 00:00:00 2001 From: Jeroen van Baarsen Date: Wed, 30 Dec 2015 13:42:57 +0100 Subject: We don't use whenever anymore. Lets remove the schedule file **Why is this needed?** Not really needed, but having code that is not used around is confusing, so lets remove it Signed-off-by: Jeroen van Baarsen --- config/schedule.rb | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 config/schedule.rb diff --git a/config/schedule.rb b/config/schedule.rb deleted file mode 100644 index 8122f7cc69c..00000000000 --- a/config/schedule.rb +++ /dev/null @@ -1,8 +0,0 @@ -# Use this file to easily define all of your cron jobs. -# -# If you make changes to this file, please create also an issue on -# https://gitlab.com/gitlab-org/omnibus-gitlab/issues . This is necessary -# because the omnibus packages manage cron jobs using Chef instead of Whenever. -every 1.hour do - rake "ci:schedule_builds" -end -- cgit v1.2.3 From 82bfa8f40c9ee7d292e8c4f8543cca27ca2c3c25 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Wed, 30 Dec 2015 12:45:24 -0200 Subject: Added additional config environmental variables to help Debian packaging * GITLAB_EMAIL_FROM * GITLAB_EMAIL_DISPLAY_NAME * GITLAB_EMAIL_REPLY_TO --- config/initializers/1_settings.rb | 6 +++--- doc/README.md | 2 +- doc/administration/enviroment_variables.md | 7 +++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index dea59f4fec8..4fbd84ee890 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -152,9 +152,9 @@ Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? -Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" -Settings.gitlab['email_display_name'] ||= "GitLab" -Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}" +Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}" +Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab' +Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}" Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url) Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' diff --git a/doc/README.md b/doc/README.md index d82ff8b908b..8a297f8267f 100644 --- a/doc/README.md +++ b/doc/README.md @@ -56,7 +56,7 @@ - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Log system](logs/logs.md) Log system. -- [Environmental Variables](administration/environmental_variables.md) to configure GitLab. +- [Environmental Variables](administration/environment_variables.md) to configure GitLab. - [Operations](operations/README.md) Keeping GitLab up and running - [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. diff --git a/doc/administration/enviroment_variables.md b/doc/administration/enviroment_variables.md index d7f5cb7c21f..7d8f9d29eef 100644 --- a/doc/administration/enviroment_variables.md +++ b/doc/administration/enviroment_variables.md @@ -9,11 +9,14 @@ But if you prefer to use environment variables we allow that too. ## Supported environment variables Variable | Type | Explanation ---- | --- | --- +-------- | ---- | ----------- GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation GITLAB_HOST | url | hostname of the GitLab server includes http or https -RAILS_ENV | production/development/staging/test | Rails environment +RAILS_ENV | production / development / staging / test | Rails environment DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5 +GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab +GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab +GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab ## Complete database variables -- cgit v1.2.3 From ff98c631c1004247656677568989e5ed68fc88f3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 30 Dec 2015 14:26:54 -0200 Subject: Make sure that is no pending migrations in Gitlab::CurrentSettings --- lib/gitlab/current_settings.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 46a4ef0e31f..7a86c09158e 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -38,7 +38,9 @@ module Gitlab true end - use_db && ActiveRecord::Base.connection.active? && ActiveRecord::Base.connection.table_exists?('application_settings') + use_db && ActiveRecord::Base.connection.active? && + !ActiveRecord::Migrator.needs_migration? && + ActiveRecord::Base.connection.table_exists?('application_settings') end end end -- cgit v1.2.3 From d9d2e8a3e8de62beff685457e4e305f93122b206 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 29 Dec 2015 21:58:16 -0500 Subject: Support a single directory traversal in RelativeLinkFilter Prior, if we were viewing a blob at `https://example.com/namespace/project/blob/master/doc/some-file.md` and it contained a relative link such as `[README](../README.md)`, the resulting link when viewing the blob would be: `https://example.com/namespace/project/blob/README.md` which omits the `master` ref, resulting in a 404. --- lib/banzai/filter/relative_link_filter.rb | 2 +- spec/lib/banzai/filter/relative_link_filter_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 5a081125f21..66f166939e4 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -91,7 +91,7 @@ module Banzai parts = request_path.split('/') parts.pop if path_type(request_path) != 'tree' - while parts.length > 1 && path.start_with?('../') + while path.start_with?('../') parts.pop path.sub!('../', '') end diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 0b3e5ecbc9f..0e6685f0ffb 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -92,6 +92,14 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" end + it 'rebuilds relative URL for a file in the repository root' do + relative_link = link('../README.md') + doc = filter(relative_link, requested_path: 'doc/some-file.md') + + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/blob/#{ref}/README.md" + end + it 'rebuilds relative URL for a file in the repo with an anchor' do doc = filter(link('README.md#section')) expect(doc.at_css('a')['href']). -- cgit v1.2.3 From c479dbae225728234848467c5451f48f27462a05 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 29 Dec 2015 20:42:26 -0500 Subject: Add js-requires-input and js-quick-submit to abuse report form --- app/views/abuse_reports/new.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml index cffd7684008..3e5cdd2ce4a 100644 --- a/app/views/abuse_reports/new.html.haml +++ b/app/views/abuse_reports/new.html.haml @@ -2,7 +2,7 @@ %h3.page-title Report abuse %p Please use this form to report users who create spam issues, comments or behave inappropriately. %hr -= form_for @abuse_report, html: { class: 'form-horizontal'} do |f| += form_for @abuse_report, html: { class: 'form-horizontal js-requires-input'} do |f| = f.hidden_field :user_id - if @abuse_report.errors.any? .alert.alert-danger @@ -16,7 +16,7 @@ .form-group = f.label :message, class: 'control-label' .col-sm-10 - = f.text_area :message, class: "form-control", rows: 2, required: true + = f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true .help-block Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment. -- cgit v1.2.3 From 5c6b9e8c57a1a8d117cafe0c11eda1950d66159e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 30 Dec 2015 16:51:47 -0500 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 425beb17743..712563e5f06 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,10 +11,10 @@ v 8.4.0 (unreleased) - Add CAS support (tduehr) - Add link to merge request on build detail page - Revert back upvote and downvote button to the issue and MR pages - - Enable "Add key" button when user fills in a proper key (Stan Hu) v 8.3.3 (unreleased) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) + - Enable "Add key" button when user fills in a proper key (Stan Hu) v 8.3.2 - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) -- cgit v1.2.3 From 1be406d741a3096f831fbb85b09a24749d8bcfcb Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Thu, 17 Dec 2015 09:32:22 +0100 Subject: Swap Author and Assignee Selectors on issuable index view Closes #4039 --- CHANGELOG | 2 ++ app/views/shared/issuable/_filter.html.haml | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 712563e5f06..fc8ae9f8eb7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,8 @@ v 8.4.0 (unreleased) - Add CAS support (tduehr) - Add link to merge request on build detail page - Revert back upvote and downvote button to the issue and MR pages + - Enable "Add key" button when user fills in a proper key (Stan Hu) + - Swap position of Assignee and Author selector on Issueables (Zeger-Jan van de Weg) v 8.3.3 (unreleased) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index ac6c248ccf1..be06738eac9 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -29,14 +29,14 @@ = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left" .issues-other-filters - .filter-item.inline - = users_select_tag(:assignee_id, selected: params[:assignee_id], - placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true) - .filter-item.inline = users_select_tag(:author_id, selected: params[:author_id], placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true) + .filter-item.inline + = users_select_tag(:assignee_id, selected: params[:assignee_id], + placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true) + .filter-item.inline.milestone-filter = select_tag('milestone_title', projects_milestones_options, class: 'select2 trigger-submit', include_blank: true, -- cgit v1.2.3 From efc679e4aa02954a8df1de6ed4359a09190f6221 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 30 Dec 2015 17:03:51 -0500 Subject: Fix "I see current user as the first user" step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why did this break? `¯\_(ツ)_/¯` --- features/steps/project/issues/issues.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index 4a7ff21d385..8e8c9c57452 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -59,15 +59,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'I click "author" dropdown' do - first('.ajax-users-select').click + first('#s2id_author_id').click end step 'I see current user as the first user' do - expect(page).to have_selector('.user-result', visible: true, count: 4) + expect(page).to have_selector('.user-result', visible: true, count: 3) users = page.all('.user-name') - expect(users[0].text).to eq 'Any Assignee' - expect(users[1].text).to eq 'Unassigned' - expect(users[2].text).to eq current_user.name + expect(users[0].text).to eq 'Any Author' + expect(users[1].text).to eq current_user.name end step 'I submit new issue "500 error on profile"' do -- cgit v1.2.3 From 00dc5f1b3ea738c602246bda11fc607042b3e76d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 30 Dec 2015 18:22:06 -0500 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fc8ae9f8eb7..353d5060eba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,7 +11,6 @@ v 8.4.0 (unreleased) - Add CAS support (tduehr) - Add link to merge request on build detail page - Revert back upvote and downvote button to the issue and MR pages - - Enable "Add key" button when user fills in a proper key (Stan Hu) - Swap position of Assignee and Author selector on Issueables (Zeger-Jan van de Weg) v 8.3.3 (unreleased) -- cgit v1.2.3 From c936e4e3c8282df17f2dc89b305233b7608003ca Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 30 Dec 2015 15:53:52 +0100 Subject: Removed various default metrics tags While it's useful to keep track of the different versions (Ruby, GitLab, etc) doing so for every point wastes disk space and possibly also RAM (which InfluxDB is all to eager to gobble up). If we want to see the performance differences between different GitLab versions simply looking at the performance since the last release date should suffice. --- lib/gitlab/metrics/metric.rb | 7 ++----- spec/lib/gitlab/metrics/metric_spec.rb | 3 --- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb index 79241f56874..753008df99a 100644 --- a/lib/gitlab/metrics/metric.rb +++ b/lib/gitlab/metrics/metric.rb @@ -19,11 +19,8 @@ module Gitlab { series: @series, tags: @tags.merge( - hostname: Metrics.hostname, - ruby_engine: RUBY_ENGINE, - ruby_version: RUBY_VERSION, - gitlab_version: Gitlab::VERSION, - process_type: Sidekiq.server? ? 'sidekiq' : 'rails' + hostname: Metrics.hostname, + process_type: Sidekiq.server? ? 'sidekiq' : 'rails' ), values: @values, timestamp: @created_at.to_i * 1_000_000_000 diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb index ec39bc9cce8..aa76315c79c 100644 --- a/spec/lib/gitlab/metrics/metric_spec.rb +++ b/spec/lib/gitlab/metrics/metric_spec.rb @@ -39,9 +39,6 @@ describe Gitlab::Metrics::Metric do expect(hash[:tags]).to be_an_instance_of(Hash) expect(hash[:tags][:hostname]).to be_an_instance_of(String) - expect(hash[:tags][:ruby_engine]).to be_an_instance_of(String) - expect(hash[:tags][:ruby_version]).to be_an_instance_of(String) - expect(hash[:tags][:gitlab_version]).to be_an_instance_of(String) expect(hash[:tags][:process_type]).to be_an_instance_of(String) end -- cgit v1.2.3 From 6d5202e2b6bcb563d6998ed530b6ecd9f4748156 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 31 Dec 2015 12:27:23 +0100 Subject: Updated allocations Gem to version 1.0.3 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index c4cadbafa26..9769ae80a7d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -49,7 +49,7 @@ GEM addressable (2.3.8) after_commit_queue (1.3.0) activerecord (>= 3.0) - allocations (1.0.1) + allocations (1.0.3) annotate (2.6.10) activerecord (>= 3.2, <= 4.3) rake (~> 10.4) -- cgit v1.2.3 From bf03acfa259169e8a060d2c3fc55c8042226557c Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 31 Dec 2015 15:58:52 +0200 Subject: spinach fix --- features/steps/project/issues/award_emoji.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 1404f34cfe0..2c2ed08655e 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -24,7 +24,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps page.within '.awards' do expect do page.find('.award.active').click - sleep 0.1 + sleep 0.3 end.to change{ page.all(".award").size }.from(3).to(2) end end -- cgit v1.2.3 From 3077cb52d904154b98ee3e9aced5b3aadae86941 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 30 Dec 2015 17:16:02 +0100 Subject: Use XPath for searching link nodes This is a tad faster than letting Nokogiri figure out whether it should evaluate the query as CSS or XPath and then actually evaluating it. --- lib/banzai/filter/reference_filter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index 8ca05ace88c..7198a8b03e2 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -124,7 +124,7 @@ module Banzai def replace_link_nodes_with_text(pattern) return doc if project.nil? - doc.search('a').each do |node| + doc.xpath('descendant-or-self::a').each do |node| klass = node.attr('class') next if klass && klass.include?('gfm') @@ -162,7 +162,7 @@ module Banzai def replace_link_nodes_with_href(pattern) return doc if project.nil? - doc.search('a').each do |node| + doc.xpath('descendant-or-self::a').each do |node| klass = node.attr('class') next if klass && klass.include?('gfm') -- cgit v1.2.3 From d3951dfaa13b9aaf11695ef10fa63456ac75cc48 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 30 Dec 2015 17:16:59 +0100 Subject: Don't use delegate to delegate trivial methods Around 300 ms (in total) would be spent in these delegated methods due to the extra stuff ActiveSupport adds to the compiled methods. Because these delegations are so simple we can just manually define the methods, saving around 275 milliseconds. --- lib/banzai/filter/abstract_reference_filter.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 63ad8910c0f..230387c8383 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -47,7 +47,17 @@ module Banzai { object_sym => LazyReference.new(object_class, node.attr(data_reference)) } end - delegate :object_class, :object_sym, :references_in, to: :class + def object_class + self.class.object_class + end + + def object_sym + self.class.object_sym + end + + def references_in(*args, &block) + self.class.references_in(*args, &block) + end def find_object(project, id) # Implement in child class -- cgit v1.2.3 From 054df415f94abe1e517a729e53cdb325d592d31b Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 30 Dec 2015 18:16:53 +0100 Subject: Optimize CSS expressions produced by Nokogiri Nokogiri produces inefficient XPath expressions when given CSS expressions such as "a.gfm". Luckily these expressions can be optimized quite easily while still achieving the same results. In the two cases where this optimization is applied the run time has been reduced from around 170 ms to around 15 ms. --- lib/banzai/filter/redactor_filter.rb | 2 +- lib/banzai/filter/reference_gatherer_filter.rb | 2 +- lib/banzai/querying.rb | 18 ++++++++++++++++++ spec/lib/banzai/querying_spec.rb | 13 +++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 lib/banzai/querying.rb create mode 100644 spec/lib/banzai/querying_spec.rb diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb index f01a32b5ae5..66f77902319 100644 --- a/lib/banzai/filter/redactor_filter.rb +++ b/lib/banzai/filter/redactor_filter.rb @@ -10,7 +10,7 @@ module Banzai # class RedactorFilter < HTML::Pipeline::Filter def call - doc.css('a.gfm').each do |node| + Querying.css(doc, 'a.gfm').each do |node| unless user_can_see_reference?(node) # The reference should be replaced by the original text, # which is not always the same as the rendered text. diff --git a/lib/banzai/filter/reference_gatherer_filter.rb b/lib/banzai/filter/reference_gatherer_filter.rb index 12412ff7ea9..bef04112919 100644 --- a/lib/banzai/filter/reference_gatherer_filter.rb +++ b/lib/banzai/filter/reference_gatherer_filter.rb @@ -16,7 +16,7 @@ module Banzai end def call - doc.css('a.gfm').each do |node| + Querying.css(doc, 'a.gfm').each do |node| gather_references(node) end diff --git a/lib/banzai/querying.rb b/lib/banzai/querying.rb new file mode 100644 index 00000000000..1e1b51e683e --- /dev/null +++ b/lib/banzai/querying.rb @@ -0,0 +1,18 @@ +module Banzai + module Querying + # Searches a Nokogiri document using a CSS query, optionally optimizing it + # whenever possible. + # + # document - A document/element to search. + # query - The CSS query to use. + # + # Returns a Nokogiri::XML::NodeSet. + def self.css(document, query) + # When using "a.foo" Nokogiri compiles this to "//a[...]" but + # "descendant::a[...]" is quite a bit faster and achieves the same result. + xpath = Nokogiri::CSS.xpath_for(query)[0].gsub(%r{^//}, 'descendant::') + + document.xpath(xpath) + end + end +end diff --git a/spec/lib/banzai/querying_spec.rb b/spec/lib/banzai/querying_spec.rb new file mode 100644 index 00000000000..27da2a7439c --- /dev/null +++ b/spec/lib/banzai/querying_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Banzai::Querying do + describe '.css' do + it 'optimizes queries for elements with classes' do + document = double(:document) + + expect(document).to receive(:xpath).with(/^descendant::a/) + + described_class.css(document, 'a.gfm') + end + end +end -- cgit v1.2.3 From a6c60127e3e2966b2f29fa6e6e79503b130c2b02 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 31 Dec 2015 17:14:02 +0100 Subject: Removed tracking of raw SQL queries This particular setup had 3 problems: 1. Storing SQL queries as tags is very inefficient as InfluxDB ends up indexing every query (and they can get pretty large). Storing these as values instead means we can't always display the SQL as easily. 2. We already instrument ActiveRecord query methods, thus we already have timing information about database queries. 3. SQL obfuscation is difficult to get right and I'd rather not expose sensitive data by accident. --- config/initializers/metrics.rb | 1 - lib/gitlab/metrics/obfuscated_sql.rb | 47 ----------- lib/gitlab/metrics/subscribers/active_record.rb | 48 ----------- spec/lib/gitlab/metrics/obfuscated_sql_spec.rb | 93 ---------------------- .../metrics/subscribers/active_record_spec.rb | 32 -------- 5 files changed, 221 deletions(-) delete mode 100644 lib/gitlab/metrics/obfuscated_sql.rb delete mode 100644 lib/gitlab/metrics/subscribers/active_record.rb delete mode 100644 spec/lib/gitlab/metrics/obfuscated_sql_spec.rb delete mode 100644 spec/lib/gitlab/metrics/subscribers/active_record_spec.rb diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 2e4908192a1..94c535dc562 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -7,7 +7,6 @@ if Gitlab::Metrics.enabled? # These are manually require'd so the classes are registered properly with # ActiveSupport. require 'gitlab/metrics/subscribers/action_view' - require 'gitlab/metrics/subscribers/active_record' Gitlab::Application.configure do |config| config.middleware.use(Gitlab::Metrics::RackMiddleware) diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb deleted file mode 100644 index fe97d7a0534..00000000000 --- a/lib/gitlab/metrics/obfuscated_sql.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Gitlab - module Metrics - # Class for producing SQL queries with sensitive data stripped out. - class ObfuscatedSQL - REPLACEMENT = / - \d+(\.\d+)? # integers, floats - | '.+?' # single quoted strings - | \/.+?(? Date: Thu, 31 Dec 2015 17:47:07 +0100 Subject: Cache InfluxDB settings after the first use This ensures we don't need to load anything from either PostgreSQL or the Rails cache whenever creating new InfluxDB connections. --- lib/gitlab/metrics.rb | 32 ++++++++++++++++++-------------- spec/lib/gitlab/metrics_spec.rb | 12 +++--------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 2d266ccfe9e..c5b98a0115e 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -6,16 +6,21 @@ module Gitlab METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s PATH_REGEX = /^#{RAILS_ROOT}\/?/ - def self.pool_size - current_application_settings[:metrics_pool_size] || 16 - end - - def self.timeout - current_application_settings[:metrics_timeout] || 10 + def self.settings + @settings ||= { + enabled: current_application_settings[:metrics_enabled], + pool_size: current_application_settings[:metrics_pool_size], + timeout: current_application_settings[:metrics_timeout], + method_call_threshold: current_application_settings[:metrics_method_call_threshold], + host: current_application_settings[:metrics_host], + username: current_application_settings[:metrics_username], + password: current_application_settings[:metrics_password], + port: current_application_settings[:metrics_port] + } end def self.enabled? - current_application_settings[:metrics_enabled] || false + settings[:enabled] || false end def self.mri? @@ -26,8 +31,7 @@ module Gitlab # This is memoized since this method is called for every instrumented # method. Loading data from an external cache on every method call slows # things down too much. - @method_call_threshold ||= - (current_application_settings[:metrics_method_call_threshold] || 10) + @method_call_threshold ||= settings[:method_call_threshold] end def self.pool @@ -90,11 +94,11 @@ module Gitlab # When enabled this should be set before being used as the usual pattern # "@foo ||= bar" is _not_ thread-safe. if enabled? - @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do - host = current_application_settings[:metrics_host] - user = current_application_settings[:metrics_username] - pw = current_application_settings[:metrics_password] - port = current_application_settings[:metrics_port] + @pool = ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do + host = settings[:host] + user = settings[:username] + pw = settings[:password] + port = settings[:port] InfluxDB::Client. new(udp: { host: host, port: port }, username: user, password: pw) diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 6c0682cac4d..c2924708f44 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -1,15 +1,9 @@ require 'spec_helper' describe Gitlab::Metrics do - describe '.pool_size' do - it 'returns a Fixnum' do - expect(described_class.pool_size).to be_an_instance_of(Fixnum) - end - end - - describe '.timeout' do - it 'returns a Fixnum' do - expect(described_class.timeout).to be_an_instance_of(Fixnum) + describe '.settings' do + it 'returns a Hash' do + expect(described_class.settings).to be_an_instance_of(Hash) end end -- cgit v1.2.3 From bd9f86bb8abb4759a0c72f94fb0492b1ff8619b5 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 31 Dec 2015 17:51:12 +0100 Subject: Use separate series for Rails/Sidekiq transactions This removes the need for tagging all metrics with a "process_type" tag. --- lib/gitlab/metrics/metric.rb | 3 +-- lib/gitlab/metrics/rack_middleware.rb | 4 +++- lib/gitlab/metrics/sidekiq_middleware.rb | 4 +++- lib/gitlab/metrics/transaction.rb | 7 +++---- spec/lib/gitlab/metrics/instrumentation_spec.rb | 2 +- spec/lib/gitlab/metrics/metric_spec.rb | 1 - spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb | 2 +- spec/lib/gitlab/metrics/subscribers/action_view_spec.rb | 2 +- spec/lib/gitlab/metrics/transaction_spec.rb | 4 ++-- 9 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb index 753008df99a..8319e628a40 100644 --- a/lib/gitlab/metrics/metric.rb +++ b/lib/gitlab/metrics/metric.rb @@ -19,8 +19,7 @@ module Gitlab { series: @series, tags: @tags.merge( - hostname: Metrics.hostname, - process_type: Sidekiq.server? ? 'sidekiq' : 'rails' + hostname: Metrics.hostname ), values: @values, timestamp: @created_at.to_i * 1_000_000_000 diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb index 5c0587c4c51..bb9e4fcb918 100644 --- a/lib/gitlab/metrics/rack_middleware.rb +++ b/lib/gitlab/metrics/rack_middleware.rb @@ -4,6 +4,8 @@ module Gitlab class RackMiddleware CONTROLLER_KEY = 'action_controller.instance' + SERIES = 'rails_transactions' + def initialize(app) @app = app end @@ -30,7 +32,7 @@ module Gitlab end def transaction_from_env(env) - trans = Transaction.new + trans = Transaction.new(SERIES) trans.add_tag(:request_method, env['REQUEST_METHOD']) trans.add_tag(:request_uri, env['REQUEST_URI']) diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb index ad441decfa2..6e804dd2562 100644 --- a/lib/gitlab/metrics/sidekiq_middleware.rb +++ b/lib/gitlab/metrics/sidekiq_middleware.rb @@ -4,8 +4,10 @@ module Gitlab # # This middleware is intended to be used as a server-side middleware. class SidekiqMiddleware + SERIES = 'sidekiq_transactions' + def call(worker, message, queue) - trans = Transaction.new + trans = Transaction.new(SERIES) begin trans.run { yield } diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index a61dbd989e7..43a7dab5323 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -4,8 +4,6 @@ module Gitlab class Transaction THREAD_KEY = :_gitlab_metrics_transaction - SERIES = 'transactions' - attr_reader :uuid, :tags def self.current @@ -13,7 +11,8 @@ module Gitlab end # name - The name of this transaction as a String. - def initialize + def initialize(series) + @series = series @metrics = [] @uuid = SecureRandom.uuid @@ -55,7 +54,7 @@ module Gitlab end def track_self - add_metric(SERIES, { duration: duration }, @tags) + add_metric(@series, { duration: duration }, @tags) end def submit diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index a7eab9d11cc..a9003d8796b 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Metrics::Instrumentation do - let(:transaction) { Gitlab::Metrics::Transaction.new } + let(:transaction) { Gitlab::Metrics::Transaction.new('rspec') } before do @dummy = Class.new do diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb index aa76315c79c..9b942855140 100644 --- a/spec/lib/gitlab/metrics/metric_spec.rb +++ b/spec/lib/gitlab/metrics/metric_spec.rb @@ -39,7 +39,6 @@ describe Gitlab::Metrics::Metric do expect(hash[:tags]).to be_an_instance_of(Hash) expect(hash[:tags][:hostname]).to be_an_instance_of(String) - expect(hash[:tags][:process_type]).to be_an_instance_of(String) end it 'includes the values' do diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb index 5882e7d81c7..5fda6de52f4 100644 --- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::Metrics::SidekiqMiddleware do describe '#tag_worker' do it 'adds the worker class and action to the transaction' do - trans = Gitlab::Metrics::Transaction.new + trans = Gitlab::Metrics::Transaction.new('rspec') worker = double(:worker, class: double(:class, name: 'TestWorker')) expect(trans).to receive(:add_tag).with(:action, 'TestWorker#perform') diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb index c6cd584663f..bca76ca5a69 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Metrics::Subscribers::ActionView do - let(:transaction) { Gitlab::Metrics::Transaction.new } + let(:transaction) { Gitlab::Metrics::Transaction.new('rspec') } let(:subscriber) { described_class.new } diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb index 6862fc9e2d1..345163bfbea 100644 --- a/spec/lib/gitlab/metrics/transaction_spec.rb +++ b/spec/lib/gitlab/metrics/transaction_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Metrics::Transaction do - let(:transaction) { described_class.new } + let(:transaction) { described_class.new('rspec') } describe '#duration' do it 'returns the duration of a transaction in seconds' do @@ -58,7 +58,7 @@ describe Gitlab::Metrics::Transaction do describe '#track_self' do it 'adds a metric for the transaction itself' do expect(transaction).to receive(:add_metric). - with(described_class::SERIES, { duration: transaction.duration }, {}) + with('rspec', { duration: transaction.duration }, {}) transaction.track_self end -- cgit v1.2.3 From cafc784ee1d5d0a0279077272af8ee435bb110e4 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 31 Dec 2015 17:55:10 +0100 Subject: Removed tracking of hostnames for metrics This isn't hugely useful and mostly wastes InfluxDB space. We can re-add this whenever needed (but only once we really need it). --- config/initializers/metrics.rb | 1 - lib/gitlab/metrics.rb | 6 ------ lib/gitlab/metrics/metric.rb | 6 ++---- spec/lib/gitlab/metrics/metric_spec.rb | 2 -- spec/lib/gitlab/metrics_spec.rb | 6 ------ 5 files changed, 2 insertions(+), 19 deletions(-) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 94c535dc562..ebb20be7283 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -1,6 +1,5 @@ if Gitlab::Metrics.enabled? require 'influxdb' - require 'socket' require 'connection_pool' require 'method_source' diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index c5b98a0115e..ee88ab34d6c 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -38,10 +38,6 @@ module Gitlab @pool end - def self.hostname - @hostname - end - # Returns a relative path and line number based on the last application call # frame. def self.last_relative_application_frame @@ -89,8 +85,6 @@ module Gitlab value.to_s.gsub('=', '\\=') end - @hostname = Socket.gethostname - # When enabled this should be set before being used as the usual pattern # "@foo ||= bar" is _not_ thread-safe. if enabled? diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb index 8319e628a40..7ea9555cc8c 100644 --- a/lib/gitlab/metrics/metric.rb +++ b/lib/gitlab/metrics/metric.rb @@ -17,10 +17,8 @@ module Gitlab # Returns a Hash in a format that can be directly written to InfluxDB. def to_hash { - series: @series, - tags: @tags.merge( - hostname: Metrics.hostname - ), + series: @series, + tags: @tags, values: @values, timestamp: @created_at.to_i * 1_000_000_000 } diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb index 9b942855140..f718d536130 100644 --- a/spec/lib/gitlab/metrics/metric_spec.rb +++ b/spec/lib/gitlab/metrics/metric_spec.rb @@ -37,8 +37,6 @@ describe Gitlab::Metrics::Metric do it 'includes the tags' do expect(hash[:tags]).to be_an_instance_of(Hash) - - expect(hash[:tags][:hostname]).to be_an_instance_of(String) end it 'includes the values' do diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index c2924708f44..c2782f95c8e 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -13,12 +13,6 @@ describe Gitlab::Metrics do end end - describe '.hostname' do - it 'returns a String containing the hostname' do - expect(described_class.hostname).to eq(Socket.gethostname) - end - end - describe '.last_relative_application_frame' do it 'returns an Array containing a file path and line number' do file, line = described_class.last_relative_application_frame -- cgit v1.2.3 From d33cc4e53070e6afb576911aa4d76dc80eba78b7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 31 Dec 2015 14:21:31 -0500 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 353d5060eba..ea403642e32 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,12 +6,12 @@ v 8.4.0 (unreleased) - Add API support for looking up a user by username (Stan Hu) - Add project permissions to all project API endpoints (Stan Hu) - Only allow group/project members to mention `@all` - - Expose Git's version in the admin area + - Expose Git's version in the admin area (Trey Davis) - Add "Frequently used" category to emoji picker - Add CAS support (tduehr) - Add link to merge request on build detail page - Revert back upvote and downvote button to the issue and MR pages - - Swap position of Assignee and Author selector on Issueables (Zeger-Jan van de Weg) + - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) v 8.3.3 (unreleased) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) -- cgit v1.2.3 From bf249550d03a21d4aca3847b6d32b6d71de3956b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 30 Dec 2015 22:03:43 -0500 Subject: number_with_delimiter most of the things --- app/views/admin/builds/index.html.haml | 6 +++--- app/views/admin/dashboard/index.html.haml | 22 +++++++++++----------- app/views/admin/groups/index.html.haml | 2 +- app/views/admin/users/index.html.haml | 14 +++++++------- app/views/layouts/nav/_admin.html.haml | 6 +++--- app/views/layouts/nav/_dashboard.html.haml | 4 ++-- app/views/layouts/nav/_group.html.haml | 4 ++-- app/views/layouts/nav/_profile.html.haml | 4 ++-- app/views/layouts/nav/_project.html.haml | 6 +++--- app/views/projects/issues/_issues.html.haml | 2 +- .../merge_requests/_merge_requests.html.haml | 2 +- 11 files changed, 36 insertions(+), 36 deletions(-) diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 55da06a7fe9..52c36af6225 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -8,17 +8,17 @@ %li{class: ('active' if @scope.nil?)} = link_to admin_builds_path do Running - %span.badge.js-running-count= @all_builds.running_or_pending.count(:id) + %span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id)) %li{class: ('active' if @scope == 'finished')} = link_to admin_builds_path(scope: :finished) do Finished - %span.badge.js-running-count= @all_builds.finished.count(:id) + %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id)) %li{class: ('active' if @scope == 'all')} = link_to admin_builds_path(scope: :all) do All - %span.badge.js-totalbuilds-count= @all_builds.count(:id) + %span.badge.js-totalbuilds-count= number_with_delimiter(@all_builds.count(:id)) .gray-content-block #{(@scope || 'running').capitalize} builds diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 531247e9148..cc389c3ae08 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -6,35 +6,35 @@ %p Forks %span.light.pull-right - = ForkedProjectLink.count + = number_with_delimiter(ForkedProjectLink.count) %p Issues %span.light.pull-right - = Issue.count + = number_with_delimiter(Issue.count) %p Merge Requests %span.light.pull-right - = MergeRequest.count + = number_with_delimiter(MergeRequest.count) %p Notes %span.light.pull-right - = Note.count + = number_with_delimiter(Note.count) %p Snippets %span.light.pull-right - = Snippet.count + = number_with_delimiter(Snippet.count) %p SSH Keys %span.light.pull-right - = Key.count + = number_with_delimiter(Key.count) %p Milestones %span.light.pull-right - = Milestone.count + = number_with_delimiter(Milestone.count) %p Active Users %span.light.pull-right - = User.active.count + = number_with_delimiter(User.active.count) .col-md-4 %h4 Features @@ -99,7 +99,7 @@ %h4 Projects .data = link_to admin_namespaces_projects_path do - %h1= Project.count + %h1= number_with_delimiter(Project.count) %hr = link_to('New Project', new_project_path, class: "btn btn-new") .col-sm-4 @@ -107,7 +107,7 @@ %h4 Users .data = link_to admin_users_path do - %h1= User.count + %h1= number_with_delimiter(User.count) %hr = link_to 'New User', new_admin_user_path, class: "btn btn-new" .col-sm-4 @@ -115,7 +115,7 @@ %h4 Groups .data = link_to admin_groups_path do - %h1= Group.count + %h1= number_with_delimiter(Group.count) %hr = link_to 'New Group', new_admin_group_path, class: "btn btn-new" diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 5ce7cdf2f8d..3940210e19b 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,6 +1,6 @@ - page_title "Groups" %h3.page-title - Groups (#{@groups.total_count}) + Groups (#{number_with_delimiter(@groups.total_count)}) = link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right" %p.light diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index bc08458312c..a92c9c152b9 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -8,27 +8,27 @@ %li{class: "#{'active' unless params[:filter]}"} = link_to admin_users_path do Active - %small.pull-right= User.active.count + %small.pull-right= number_with_delimiter(User.active.count) %li{class: "#{'active' if params[:filter] == "admins"}"} = link_to admin_users_path(filter: "admins") do Admins - %small.pull-right= User.admins.count + %small.pull-right= number_with_delimiter(User.admins.count) %li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"} = link_to admin_users_path(filter: 'two_factor_enabled') do 2FA Enabled - %small.pull-right= User.with_two_factor.count + %small.pull-right= number_with_delimiter(User.with_two_factor.count) %li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"} = link_to admin_users_path(filter: 'two_factor_disabled') do 2FA Disabled - %small.pull-right= User.without_two_factor.count + %small.pull-right= number_with_delimiter(User.without_two_factor.count) %li{class: "#{'active' if params[:filter] == "blocked"}"} = link_to admin_users_path(filter: "blocked") do Blocked - %small.pull-right= User.blocked.count + %small.pull-right= number_with_delimiter(User.blocked.count) %li{class: "#{'active' if params[:filter] == "wop"}"} = link_to admin_users_path(filter: "wop") do Without projects - %small.pull-right= User.without_projects.count + %small.pull-right= number_with_delimiter(User.without_projects.count) %hr = form_tag admin_users_path, method: :get, class: 'form-inline' do .form-group @@ -42,7 +42,7 @@ %section.col-md-9 .panel.panel-default .panel-heading - Users (#{@users.total_count}) + Users (#{number_with_delimiter(@users.total_count)}) .panel-head-actions .dropdown.inline %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"} diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index c60ac5eefac..cffdb52cc23 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -29,13 +29,13 @@ = icon('cog fw') %span Runners - %span.count= Ci::Runner.count(:all) + %span.count= number_with_delimiter(Ci::Runner.count(:all)) = nav_link path: 'builds#index' do = link_to admin_builds_path do = icon('link fw') %span Builds - %span.count= Ci::Build.count(:all) + %span.count= number_with_delimiter(Ci::Build.count(:all)) = nav_link(controller: :logs) do = link_to admin_logs_path, title: 'Logs' do = icon('file-text fw') @@ -80,7 +80,7 @@ = icon('exclamation-circle fw') %span Abuse Reports - %span.count= AbuseReport.count(:all) + %span.count= number_with_delimiter(AbuseReport.count(:all)) = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = link_to admin_application_settings_path, title: 'Settings' do diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index da698831300..106abd24a56 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -24,13 +24,13 @@ = icon('exclamation-circle fw') %span Issues - %span.count= current_user.assigned_issues.opened.count + %span.count= number_with_delimiter(current_user.assigned_issues.opened.count) = nav_link(path: 'dashboard#merge_requests') do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do = icon('tasks fw') %span Merge Requests - %span.count= current_user.assigned_merge_requests.opened.count + %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) = nav_link(controller: :snippets) do = link_to dashboard_snippets_path, title: 'Snippets' do = icon('clipboard fw') diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 68da8d5de2a..e5e2a59eaed 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -25,14 +25,14 @@ %span Issues - if current_user - %span.count= Issue.opened.of_group(@group).count + %span.count= number_with_delimiter(Issue.opened.of_group(@group).count) = nav_link(path: 'groups#merge_requests') do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do = icon('tasks fw') %span Merge Requests - if current_user - %span.count= MergeRequest.opened.of_group(@group).count + %span.count= number_with_delimiter(MergeRequest.opened.of_group(@group).count) = nav_link(controller: [:group_members]) do = link_to group_group_members_path(@group), title: 'Members' do = icon('users fw') diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 64b30783c05..f3ded04419b 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -27,7 +27,7 @@ = icon('envelope-o fw') %span Emails - %span.count= current_user.emails.count + 1 + %span.count= number_with_delimiter(current_user.emails.count + 1) - unless current_user.ldap_user? = nav_link(controller: :passwords) do = link_to edit_profile_password_path, title: 'Password' do @@ -45,7 +45,7 @@ = icon('key fw') %span SSH Keys - %span.count= current_user.keys.count + %span.count= number_with_delimiter(current_user.keys.count) = nav_link(controller: :preferences) do = link_to profile_preferences_path, title: 'Preferences' do -# TODO (rspeicher): Better icon? diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index c0d62028639..d3eaf0f3209 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -44,7 +44,7 @@ = icon('cubes fw') %span Builds - %span.count.builds_counter= @project.builds.running_or_pending.count(:all) + %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do @@ -67,7 +67,7 @@ %span Issues - if @project.default_issues_tracker? - %span.count.issue_counter= @project.issues.opened.count + %span.count.issue_counter= number_with_delimiter(@project.issues.opened.count) - if project_nav_tab? :merge_requests = nav_link(controller: :merge_requests) do @@ -75,7 +75,7 @@ = icon('tasks fw') %span Merge Requests - %span.count.merge_counter= @project.merge_requests.opened.count + %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) - if project_nav_tab? :settings = nav_link(controller: [:project_members, :teams]) do diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index ca5b1a8386d..e0e89b764d5 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -7,7 +7,7 @@ - if @issues.present? .issuable-filter-count %span.pull-right - = @issues.total_count + = number_with_delimiter(@issues.total_count) issues for this filter = paginate @issues, theme: "gitlab" diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml index 0af970e4b92..29d09d0a652 100644 --- a/app/views/projects/merge_requests/_merge_requests.html.haml +++ b/app/views/projects/merge_requests/_merge_requests.html.haml @@ -7,7 +7,7 @@ - if @merge_requests.present? .issuable-filter-count %span.pull-right - = @merge_requests.total_count + = number_with_delimiter(@merge_requests.total_count) merge requests for this filter = paginate @merge_requests, theme: "gitlab" -- cgit v1.2.3 From 9a9a81bea0bf4e68b3e1ed7ec619947a37d2dbff Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 31 Dec 2015 20:42:46 -0500 Subject: Don't attempt to set Referrer policy in Safari While Safari supports the policy, it does not (currently, as of 9.x) recognize `origin-when-cross-origin` as a valid value, so we omit the policy entirely under Safari. Closes #5609 --- CHANGELOG | 1 + app/views/layouts/_head.html.haml | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ea403642e32..2b7d5808e7e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.4.0 (unreleased) - Add link to merge request on build detail page - Revert back upvote and downvote button to the issue and MR pages - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) + - Fix version check image in Safari v 8.3.3 (unreleased) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 2e0bd2007a3..dd133ee8b56 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,9 +1,6 @@ %head{prefix: "og: http://ogp.me/ns#"} %meta{charset: "utf-8"} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} - %meta{name: 'referrer', content: 'origin-when-cross-origin'} - - %meta{name: "description", content: page_description} -# Open Graph - http://ogp.me/ %meta{property: 'og:type', content: "object"} @@ -20,8 +17,8 @@ %meta{property: 'twitter:image', content: page_image} = page_card_meta_tags - - page_title "GitLab" - %title= page_title + %title= page_title('GitLab') + %meta{name: "description", content: page_description} = favicon_link_tag 'favicon.ico' @@ -34,6 +31,8 @@ = include_gon + - unless browser.safari? + %meta{name: 'referrer', content: 'origin-when-cross-origin'} %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} %meta{name: 'theme-color', content: '#474D57'} -- cgit v1.2.3 From 74969021efed0ae15f19fef5bfc8e9ed7d7fa4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20K=C3=B6hler?= Date: Fri, 1 Jan 2016 13:32:24 +0100 Subject: Fix typo in CI settings [ci skip] --- app/views/admin/application_settings/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 214e0209bb7..89b38a0dad0 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -149,7 +149,7 @@ .checkbox = f.label :shared_runners_enabled do = f.check_box :shared_runners_enabled - Enable shared runners for a new projects + Enable shared runners for new projects .form-group = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' -- cgit v1.2.3 From 26de72a233c0b20937c4d86baab69946d385e147 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 1 Jan 2016 13:52:26 +0100 Subject: Fix broken SVN migration link [ci skip] --- doc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index f40a257b42f..78644505df5 100644 --- a/doc/README.md +++ b/doc/README.md @@ -7,7 +7,7 @@ - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). - [Markdown](markdown/markdown.md) GitLab's advanced formatting system. -- [Migrating from SVN](migration/README.md) Convert a SVN repository to Git and GitLab +- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. - [Profile Settings](profile/README.md) - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat. -- cgit v1.2.3 From f3ea2f29a5b18f176ed0bbfde55e9cc7d429ebe6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 17:40:10 -0500 Subject: Remove jquery.history.js plugin We're not concerning ourselves with non-HTML5 browser compatibility, and this removes 21 KB from our compiled JavaScript. Bonus fix: There was an extra space after the query string in the URLs that has now been removed. --- app/assets/javascripts/application.js.coffee | 1 - app/assets/javascripts/issues.js.coffee | 4 ++-- app/assets/javascripts/merge_requests.js.coffee | 4 ++-- vendor/assets/javascripts/jquery.history.js | 1 - 4 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 vendor/assets/javascripts/jquery.history.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index affab5bb030..fe76d11ab58 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -10,7 +10,6 @@ #= require jquery.cookie #= require jquery.endless-scroll #= require jquery.highlight -#= require jquery.history #= require jquery.waitforimages #= require jquery.atwho #= require jquery.scrollTo diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index ac9e022e727..35d34b20fae 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -54,7 +54,7 @@ form = $("#issue_search_form") search = $("#issue_search").val() $('.issues-holder').css("opacity", '0.5') - issues_url = form.attr('action') + '? '+ form.serialize() + issues_url = form.attr('action') + '?' + form.serialize() $.ajax type: "GET" @@ -65,7 +65,7 @@ success: (data) -> $('.issues-holder').html(data.html) # Change url so if user reload a page - search results are saved - History.replaceState {page: issues_url}, document.title, issues_url + history.replaceState {page: issues_url}, document.title, issues_url Issues.reload() dataType: "json" diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee index 83434c1b9ba..b3c73ffce5d 100644 --- a/app/assets/javascripts/merge_requests.js.coffee +++ b/app/assets/javascripts/merge_requests.js.coffee @@ -16,7 +16,7 @@ form = $("#issue_search_form") search = $("#issue_search").val() $('.merge-requests-holder').css("opacity", '0.5') - issues_url = form.attr('action') + '? '+ form.serialize() + issues_url = form.attr('action') + '?' + form.serialize() $.ajax type: "GET" @@ -27,7 +27,7 @@ success: (data) -> $('.merge-requests-holder').html(data.html) # Change url so if user reload a page - search results are saved - History.replaceState {page: issues_url}, document.title, issues_url + history.replaceState {page: issues_url}, document.title, issues_url MergeRequests.reload() dataType: "json" diff --git a/vendor/assets/javascripts/jquery.history.js b/vendor/assets/javascripts/jquery.history.js deleted file mode 100644 index 8d4edcd210e..00000000000 --- a/vendor/assets/javascripts/jquery.history.js +++ /dev/null @@ -1 +0,0 @@ -window.JSON||(window.JSON={}),function(){function f(a){return a<10?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c")&&c[0]);return a>4?a:!1}();return a},m.isInternetExplorer=function(){var a=m.isInternetExplorer.cached=typeof m.isInternetExplorer.cached!="undefined"?m.isInternetExplorer.cached:Boolean(m.getInternetExplorerMajorVersion());return a},m.emulated={pushState:!Boolean(a.history&&a.history.pushState&&a.history.replaceState&&!/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i.test(e.userAgent)&&!/AppleWebKit\/5([0-2]|3[0-2])/i.test(e.userAgent)),hashChange:Boolean(!("onhashchange"in a||"onhashchange"in d)||m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8)},m.enabled=!m.emulated.pushState,m.bugs={setHash:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),safariPoll:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),ieDoubleCheck:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8),hashEscape:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<7)},m.isEmptyObject=function(a){for(var b in a)return!1;return!0},m.cloneObject=function(a){var b,c;return a?(b=k.stringify(a),c=k.parse(b)):c={},c},m.getRootUrl=function(){var a=d.location.protocol+"//"+(d.location.hostname||d.location.host);if(d.location.port||!1)a+=":"+d.location.port;return a+="/",a},m.getBaseHref=function(){var a=d.getElementsByTagName("base"),b=null,c="";return a.length===1&&(b=a[0],c=b.href.replace(/[^\/]+$/,"")),c=c.replace(/\/+$/,""),c&&(c+="/"),c},m.getBaseUrl=function(){var a=m.getBaseHref()||m.getBasePageUrl()||m.getRootUrl();return a},m.getPageUrl=function(){var a=m.getState(!1,!1),b=(a||{}).url||d.location.href,c;return c=b.replace(/\/+$/,"").replace(/[^\/]+$/,function(a,b,c){return/\./.test(a)?a:a+"/"}),c},m.getBasePageUrl=function(){var a=d.location.href.replace(/[#\?].*/,"").replace(/[^\/]+$/,function(a,b,c){return/[^\/]$/.test(a)?"":a}).replace(/\/+$/,"")+"/";return a},m.getFullUrl=function(a,b){var c=a,d=a.substring(0,1);return b=typeof b=="undefined"?!0:b,/[a-z]+\:\/\//.test(a)||(d==="/"?c=m.getRootUrl()+a.replace(/^\/+/,""):d==="#"?c=m.getPageUrl().replace(/#.*/,"")+a:d==="?"?c=m.getPageUrl().replace(/[\?#].*/,"")+a:b?c=m.getBaseUrl()+a.replace(/^(\.\/)+/,""):c=m.getBasePageUrl()+a.replace(/^(\.\/)+/,"")),c.replace(/\#$/,"")},m.getShortUrl=function(a){var b=a,c=m.getBaseUrl(),d=m.getRootUrl();return m.emulated.pushState&&(b=b.replace(c,"")),b=b.replace(d,"/"),m.isTraditionalAnchor(b)&&(b="./"+b),b=b.replace(/^(\.\/)+/g,"./").replace(/\#$/,""),b},m.store={},m.idToState=m.idToState||{},m.stateToId=m.stateToId||{},m.urlToId=m.urlToId||{},m.storedStates=m.storedStates||[],m.savedStates=m.savedStates||[],m.normalizeStore=function(){m.store.idToState=m.store.idToState||{},m.store.urlToId=m.store.urlToId||{},m.store.stateToId=m.store.stateToId||{}},m.getState=function(a,b){typeof a=="undefined"&&(a=!0),typeof b=="undefined"&&(b=!0);var c=m.getLastSavedState();return!c&&b&&(c=m.createStateObject()),a&&(c=m.cloneObject(c),c.url=c.cleanUrl||c.url),c},m.getIdByState=function(a){var b=m.extractId(a.url),c;if(!b){c=m.getStateString(a);if(typeof m.stateToId[c]!="undefined")b=m.stateToId[c];else if(typeof m.store.stateToId[c]!="undefined")b=m.store.stateToId[c];else{for(;;){b=(new Date).getTime()+String(Math.random()).replace(/\D/g,"");if(typeof m.idToState[b]=="undefined"&&typeof m.store.idToState[b]=="undefined")break}m.stateToId[c]=b,m.idToState[b]=a}}return b},m.normalizeState=function(a){var b,c;if(!a||typeof a!="object")a={};if(typeof a.normalized!="undefined")return a;if(!a.data||typeof a.data!="object")a.data={};b={},b.normalized=!0,b.title=a.title||"",b.url=m.getFullUrl(m.unescapeString(a.url||d.location.href)),b.hash=m.getShortUrl(b.url),b.data=m.cloneObject(a.data),b.id=m.getIdByState(b),b.cleanUrl=b.url.replace(/\??\&_suid.*/,""),b.url=b.cleanUrl,c=!m.isEmptyObject(b.data);if(b.title||c)b.hash=m.getShortUrl(b.url).replace(/\??\&_suid.*/,""),/\?/.test(b.hash)||(b.hash+="?"),b.hash+="&_suid="+b.id;return b.hashedUrl=m.getFullUrl(b.hash),(m.emulated.pushState||m.bugs.safariPoll)&&m.hasUrlDuplicate(b)&&(b.url=b.hashedUrl),b},m.createStateObject=function(a,b,c){var d={data:a,title:b,url:c};return d=m.normalizeState(d),d},m.getStateById=function(a){a=String(a);var c=m.idToState[a]||m.store.idToState[a]||b;return c},m.getStateString=function(a){var b,c,d;return b=m.normalizeState(a),c={data:b.data,title:a.title,url:a.url},d=k.stringify(c),d},m.getStateId=function(a){var b,c;return b=m.normalizeState(a),c=b.id,c},m.getHashByState=function(a){var b,c;return b=m.normalizeState(a),c=b.hash,c},m.extractId=function(a){var b,c,d;return c=/(.*)\&_suid=([0-9]+)$/.exec(a),d=c?c[1]||a:a,b=c?String(c[2]||""):"",b||!1},m.isTraditionalAnchor=function(a){var b=!/[\/\?\.]/.test(a);return b},m.extractState=function(a,b){var c=null,d,e;return b=b||!1,d=m.extractId(a),d&&(c=m.getStateById(d)),c||(e=m.getFullUrl(a),d=m.getIdByUrl(e)||!1,d&&(c=m.getStateById(d)),!c&&b&&!m.isTraditionalAnchor(a)&&(c=m.createStateObject(null,null,e))),c},m.getIdByUrl=function(a){var c=m.urlToId[a]||m.store.urlToId[a]||b;return c},m.getLastSavedState=function(){return m.savedStates[m.savedStates.length-1]||b},m.getLastStoredState=function(){return m.storedStates[m.storedStates.length-1]||b},m.hasUrlDuplicate=function(a){var b=!1,c;return c=m.extractState(a.url),b=c&&c.id!==a.id,b},m.storeState=function(a){return m.urlToId[a.url]=a.id,m.storedStates.push(m.cloneObject(a)),a},m.isLastSavedState=function(a){var b=!1,c,d,e;return m.savedStates.length&&(c=a.id,d=m.getLastSavedState(),e=d.id,b=c===e),b},m.saveState=function(a){return m.isLastSavedState(a)?!1:(m.savedStates.push(m.cloneObject(a)),!0)},m.getStateByIndex=function(a){var b=null;return typeof a=="undefined"?b=m.savedStates[m.savedStates.length-1]:a<0?b=m.savedStates[m.savedStates.length+a]:b=m.savedStates[a],b},m.getHash=function(){var a=m.unescapeHash(d.location.hash);return a},m.unescapeString=function(b){var c=b,d;for(;;){d=a.unescape(c);if(d===c)break;c=d}return c},m.unescapeHash=function(a){var b=m.normalizeHash(a);return b=m.unescapeString(b),b},m.normalizeHash=function(a){var b=a.replace(/[^#]*#/,"").replace(/#.*/,"");return b},m.setHash=function(a,b){var c,e,f;return b!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.setHash,args:arguments,queue:b}),!1):(c=m.escapeHash(a),m.busy(!0),e=m.extractState(a,!0),e&&!m.emulated.pushState?m.pushState(e.data,e.title,e.url,!1):d.location.hash!==c&&(m.bugs.setHash?(f=m.getPageUrl(),m.pushState(null,null,f+"#"+c,!1)):d.location.hash=c),m)},m.escapeHash=function(b){var c=m.normalizeHash(b);return c=a.escape(c),m.bugs.hashEscape||(c=c.replace(/\%21/g,"!").replace(/\%26/g,"&").replace(/\%3D/g,"=").replace(/\%3F/g,"?")),c},m.getHashByUrl=function(a){var b=String(a).replace(/([^#]*)#?([^#]*)#?(.*)/,"$2");return b=m.unescapeHash(b),b},m.setTitle=function(a){var b=a.title,c;b||(c=m.getStateByIndex(0),c&&c.url===a.url&&(b=c.title||m.options.initialTitle));try{d.getElementsByTagName("title")[0].innerHTML=b.replace("<","<").replace(">",">").replace(" & "," & ")}catch(e){}return d.title=b,m},m.queues=[],m.busy=function(a){typeof a!="undefined"?m.busy.flag=a:typeof m.busy.flag=="undefined"&&(m.busy.flag=!1);if(!m.busy.flag){h(m.busy.timeout);var b=function(){var a,c,d;if(m.busy.flag)return;for(a=m.queues.length-1;a>=0;--a){c=m.queues[a];if(c.length===0)continue;d=c.shift(),m.fireQueueItem(d),m.busy.timeout=g(b,m.options.busyDelay)}};m.busy.timeout=g(b,m.options.busyDelay)}return m.busy.flag},m.busy.flag=!1,m.fireQueueItem=function(a){return a.callback.apply(a.scope||m,a.args||[])},m.pushQueue=function(a){return m.queues[a.queue||0]=m.queues[a.queue||0]||[],m.queues[a.queue||0].push(a),m},m.queue=function(a,b){return typeof a=="function"&&(a={callback:a}),typeof b!="undefined"&&(a.queue=b),m.busy()?m.pushQueue(a):m.fireQueueItem(a),m},m.clearQueue=function(){return m.busy.flag=!1,m.queues=[],m},m.stateChanged=!1,m.doubleChecker=!1,m.doubleCheckComplete=function(){return m.stateChanged=!0,m.doubleCheckClear(),m},m.doubleCheckClear=function(){return m.doubleChecker&&(h(m.doubleChecker),m.doubleChecker=!1),m},m.doubleCheck=function(a){return m.stateChanged=!1,m.doubleCheckClear(),m.bugs.ieDoubleCheck&&(m.doubleChecker=g(function(){return m.doubleCheckClear(),m.stateChanged||a(),!0},m.options.doubleCheckInterval)),m},m.safariStatePoll=function(){var b=m.extractState(d.location.href),c;if(!m.isLastSavedState(b))c=b;else return;return c||(c=m.createStateObject()),m.Adapter.trigger(a,"popstate"),m},m.back=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.back,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.back(!1)}),n.go(-1),!0)},m.forward=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.forward,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.forward(!1)}),n.go(1),!0)},m.go=function(a,b){var c;if(a>0)for(c=1;c<=a;++c)m.forward(b);else{if(!(a<0))throw new Error("History.go: History.go requires a positive or negative integer passed.");for(c=-1;c>=a;--c)m.back(b)}return m};if(m.emulated.pushState){var o=function(){};m.pushState=m.pushState||o,m.replaceState=m.replaceState||o}else m.onPopState=function(b,c){var e=!1,f=!1,g,h;return m.doubleCheckComplete(),g=m.getHash(),g?(h=m.extractState(g||d.location.href,!0),h?m.replaceState(h.data,h.title,h.url,!1):(m.Adapter.trigger(a,"anchorchange"),m.busy(!1)),m.expectedStateId=!1,!1):(e=m.Adapter.extractEventData("state",b,c)||!1,e?f=m.getStateById(e):m.expectedStateId?f=m.getStateById(m.expectedStateId):f=m.extractState(d.location.href),f||(f=m.createStateObject(null,null,d.location.href)),m.expectedStateId=!1,m.isLastSavedState(f)?(m.busy(!1),!1):(m.storeState(f),m.saveState(f),m.setTitle(f),m.Adapter.trigger(a,"statechange"),m.busy(!1),!0))},m.Adapter.bind(a,"popstate",m.onPopState),m.pushState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.pushState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.pushState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0},m.replaceState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.replaceState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.replaceState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0};if(f){try{m.store=k.parse(f.getItem("History.store"))||{}}catch(p){m.store={}}m.normalizeStore()}else m.store={},m.normalizeStore();m.Adapter.bind(a,"beforeunload",m.clearAllIntervals),m.Adapter.bind(a,"unload",m.clearAllIntervals),m.saveState(m.storeState(m.extractState(d.location.href,!0))),f&&(m.onUnload=function(){var a,b;try{a=k.parse(f.getItem("History.store"))||{}}catch(c){a={}}a.idToState=a.idToState||{},a.urlToId=a.urlToId||{},a.stateToId=a.stateToId||{};for(b in m.idToState){if(!m.idToState.hasOwnProperty(b))continue;a.idToState[b]=m.idToState[b]}for(b in m.urlToId){if(!m.urlToId.hasOwnProperty(b))continue;a.urlToId[b]=m.urlToId[b]}for(b in m.stateToId){if(!m.stateToId.hasOwnProperty(b))continue;a.stateToId[b]=m.stateToId[b]}m.store=a,m.normalizeStore(),f.setItem("History.store",k.stringify(a))},m.intervalList.push(i(m.onUnload,m.options.storeInterval)),m.Adapter.bind(a,"beforeunload",m.onUnload),m.Adapter.bind(a,"unload",m.onUnload));if(!m.emulated.pushState){m.bugs.safariPoll&&m.intervalList.push(i(m.safariStatePoll,m.options.safariPollInterval));if(e.vendor==="Apple Computer, Inc."||(e.appCodeName||"")==="Mozilla")m.Adapter.bind(a,"hashchange",function(){m.Adapter.trigger(a,"popstate")}),m.getHash()&&m.Adapter.onDomLoad(function(){m.Adapter.trigger(a,"hashchange")})}},m.init()}(window) \ No newline at end of file -- cgit v1.2.3 From 3c4d24b71ed08310c3704547601ff3b9ebaceb03 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 17:52:40 -0500 Subject: Remove jquery.blockUI.js plugin It was required but never used. --- app/assets/javascripts/application.js.coffee | 1 - vendor/assets/javascripts/jquery.blockUI.js | 590 --------------------------- 2 files changed, 591 deletions(-) delete mode 100644 vendor/assets/javascripts/jquery.blockUI.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index affab5bb030..65ca1367724 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -14,7 +14,6 @@ #= require jquery.waitforimages #= require jquery.atwho #= require jquery.scrollTo -#= require jquery.blockUI #= require jquery.turbolinks #= require turbolinks #= require autosave diff --git a/vendor/assets/javascripts/jquery.blockUI.js b/vendor/assets/javascripts/jquery.blockUI.js deleted file mode 100644 index c8702d79b65..00000000000 --- a/vendor/assets/javascripts/jquery.blockUI.js +++ /dev/null @@ -1,590 +0,0 @@ -/*! - * jQuery blockUI plugin - * Version 2.60.0-2013.04.05 - * @requires jQuery v1.7 or later - * - * Examples at: http://malsup.com/jquery/block/ - * Copyright (c) 2007-2013 M. Alsup - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * Thanks to Amir-Hossein Sobhi for some excellent contributions! - */ - -;(function() { -/*jshint eqeqeq:false curly:false latedef:false */ -"use strict"; - - function setup($) { - $.fn._fadeIn = $.fn.fadeIn; - - var noOp = $.noop || function() {}; - - // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle - // retarded userAgent strings on Vista) - var msie = /MSIE/.test(navigator.userAgent); - var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent); - var mode = document.documentMode || 0; - var setExpr = $.isFunction( document.createElement('div').style.setExpression ); - - // global $ methods for blocking/unblocking the entire page - $.blockUI = function(opts) { install(window, opts); }; - $.unblockUI = function(opts) { remove(window, opts); }; - - // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl) - $.growlUI = function(title, message, timeout, onClose) { - var $m = $('
      '); - if (title) $m.append('

      '+title+'

      '); - if (message) $m.append('

      '+message+'

      '); - if (timeout === undefined) timeout = 3000; - $.blockUI({ - message: $m, fadeIn: 700, fadeOut: 1000, centerY: false, - timeout: timeout, showOverlay: false, - onUnblock: onClose, - css: $.blockUI.defaults.growlCSS - }); - }; - - // plugin method for blocking element content - $.fn.block = function(opts) { - if ( this[0] === window ) { - $.blockUI( opts ); - return this; - } - var fullOpts = $.extend({}, $.blockUI.defaults, opts || {}); - this.each(function() { - var $el = $(this); - if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked')) - return; - $el.unblock({ fadeOut: 0 }); - }); - - return this.each(function() { - if ($.css(this,'position') == 'static') { - this.style.position = 'relative'; - $(this).data('blockUI.static', true); - } - this.style.zoom = 1; // force 'hasLayout' in ie - install(this, opts); - }); - }; - - // plugin method for unblocking element content - $.fn.unblock = function(opts) { - if ( this[0] === window ) { - $.unblockUI( opts ); - return this; - } - return this.each(function() { - remove(this, opts); - }); - }; - - $.blockUI.version = 2.60; // 2nd generation blocking at no extra cost! - - // override these in your code to change the default behavior and style - $.blockUI.defaults = { - // message displayed when blocking (use null for no message) - message: '

      Please wait...

      ', - - title: null, // title string; only used when theme == true - draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) - - theme: false, // set to true to use with jQuery UI themes - - // styles for the message when blocking; if you wish to disable - // these and use an external stylesheet then do this in your code: - // $.blockUI.defaults.css = {}; - css: { - padding: 0, - margin: 0, - width: '30%', - top: '40%', - left: '35%', - textAlign: 'center', - color: '#000', - border: '3px solid #aaa', - backgroundColor:'#fff', - cursor: 'wait' - }, - - // minimal style set used when themes are used - themedCSS: { - width: '30%', - top: '40%', - left: '35%' - }, - - // styles for the overlay - overlayCSS: { - backgroundColor: '#000', - opacity: 0.6, - cursor: 'wait' - }, - - // style to replace wait cursor before unblocking to correct issue - // of lingering wait cursor - cursorReset: 'default', - - // styles applied when using $.growlUI - growlCSS: { - width: '350px', - top: '10px', - left: '', - right: '10px', - border: 'none', - padding: '5px', - opacity: 0.6, - cursor: 'default', - color: '#fff', - backgroundColor: '#000', - '-webkit-border-radius':'10px', - '-moz-border-radius': '10px', - 'border-radius': '10px' - }, - - // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w - // (hat tip to Jorge H. N. de Vasconcelos) - /*jshint scripturl:true */ - iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', - - // force usage of iframe in non-IE browsers (handy for blocking applets) - forceIframe: false, - - // z-index for the blocking overlay - baseZ: 1000, - - // set these to true to have the message automatically centered - centerX: true, // <-- only effects element blocking (page block controlled via css above) - centerY: true, - - // allow body element to be stetched in ie6; this makes blocking look better - // on "short" pages. disable if you wish to prevent changes to the body height - allowBodyStretch: true, - - // enable if you want key and mouse events to be disabled for content that is blocked - bindEvents: true, - - // be default blockUI will supress tab navigation from leaving blocking content - // (if bindEvents is true) - constrainTabKey: true, - - // fadeIn time in millis; set to 0 to disable fadeIn on block - fadeIn: 200, - - // fadeOut time in millis; set to 0 to disable fadeOut on unblock - fadeOut: 400, - - // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock - timeout: 0, - - // disable if you don't want to show the overlay - showOverlay: true, - - // if true, focus will be placed in the first available input field when - // page blocking - focusInput: true, - - // elements that can receive focus - focusableElements: ':input:enabled:visible', - - // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) - // no longer needed in 2012 - // applyPlatformOpacityRules: true, - - // callback method invoked when fadeIn has completed and blocking message is visible - onBlock: null, - - // callback method invoked when unblocking has completed; the callback is - // passed the element that has been unblocked (which is the window object for page - // blocks) and the options that were passed to the unblock call: - // onUnblock(element, options) - onUnblock: null, - - // callback method invoked when the overlay area is clicked. - // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used. - onOverlayClick: null, - - // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 - quirksmodeOffsetHack: 4, - - // class name of the message block - blockMsgClass: 'blockMsg', - - // if it is already blocked, then ignore it (don't unblock and reblock) - ignoreIfBlocked: false - }; - - // private data and functions follow... - - var pageBlock = null; - var pageBlockEls = []; - - function install(el, opts) { - var css, themedCSS; - var full = (el == window); - var msg = (opts && opts.message !== undefined ? opts.message : undefined); - opts = $.extend({}, $.blockUI.defaults, opts || {}); - - if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked')) - return; - - opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); - css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); - if (opts.onOverlayClick) - opts.overlayCSS.cursor = 'pointer'; - - themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); - msg = msg === undefined ? opts.message : msg; - - // remove the current block (if there is one) - if (full && pageBlock) - remove(window, {fadeOut:0}); - - // if an existing element is being used as the blocking content then we capture - // its current place in the DOM (and current display style) so we can restore - // it when we unblock - if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { - var node = msg.jquery ? msg[0] : msg; - var data = {}; - $(el).data('blockUI.history', data); - data.el = node; - data.parent = node.parentNode; - data.display = node.style.display; - data.position = node.style.position; - if (data.parent) - data.parent.removeChild(node); - } - - $(el).data('blockUI.onUnblock', opts.onUnblock); - var z = opts.baseZ; - - // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; - // layer1 is the iframe layer which is used to supress bleed through of underlying content - // layer2 is the overlay layer which has opacity and a wait cursor (by default) - // layer3 is the message content that is displayed while blocking - var lyr1, lyr2, lyr3, s; - if (msie || opts.forceIframe) - lyr1 = $(''); - else - lyr1 = $(''); - - if (opts.theme) - lyr2 = $(''); - else - lyr2 = $(''); - - if (opts.theme && full) { - s = ''; - } - else if (opts.theme) { - s = ''; - } - else if (full) { - s = ''; - } - else { - s = ''; - } - lyr3 = $(s); - - // if we have a message, style it - if (msg) { - if (opts.theme) { - lyr3.css(themedCSS); - lyr3.addClass('ui-widget-content'); - } - else - lyr3.css(css); - } - - // style the overlay - if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/) - lyr2.css(opts.overlayCSS); - lyr2.css('position', full ? 'fixed' : 'absolute'); - - // make iframe layer transparent in IE - if (msie || opts.forceIframe) - lyr1.css('opacity',0.0); - - //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); - var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); - $.each(layers, function() { - this.appendTo($par); - }); - - if (opts.theme && opts.draggable && $.fn.draggable) { - lyr3.draggable({ - handle: '.ui-dialog-titlebar', - cancel: 'li' - }); - } - - // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) - var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0); - if (ie6 || expr) { - // give body 100% height - if (full && opts.allowBodyStretch && $.support.boxModel) - $('html,body').css('height','100%'); - - // fix ie6 issue when blocked element has a border width - if ((ie6 || !$.support.boxModel) && !full) { - var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); - var fixT = t ? '(0 - '+t+')' : 0; - var fixL = l ? '(0 - '+l+')' : 0; - } - - // simulate fixed position - $.each(layers, function(i,o) { - var s = o[0].style; - s.position = 'absolute'; - if (i < 2) { - if (full) - s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"'); - else - s.setExpression('height','this.parentNode.offsetHeight + "px"'); - if (full) - s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'); - else - s.setExpression('width','this.parentNode.offsetWidth + "px"'); - if (fixL) s.setExpression('left', fixL); - if (fixT) s.setExpression('top', fixT); - } - else if (opts.centerY) { - if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); - s.marginTop = 0; - } - else if (!opts.centerY && full) { - var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0; - var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; - s.setExpression('top',expression); - } - }); - } - - // show the message - if (msg) { - if (opts.theme) - lyr3.find('.ui-widget-content').append(msg); - else - lyr3.append(msg); - if (msg.jquery || msg.nodeType) - $(msg).show(); - } - - if ((msie || opts.forceIframe) && opts.showOverlay) - lyr1.show(); // opacity is zero - if (opts.fadeIn) { - var cb = opts.onBlock ? opts.onBlock : noOp; - var cb1 = (opts.showOverlay && !msg) ? cb : noOp; - var cb2 = msg ? cb : noOp; - if (opts.showOverlay) - lyr2._fadeIn(opts.fadeIn, cb1); - if (msg) - lyr3._fadeIn(opts.fadeIn, cb2); - } - else { - if (opts.showOverlay) - lyr2.show(); - if (msg) - lyr3.show(); - if (opts.onBlock) - opts.onBlock(); - } - - // bind key and mouse events - bind(1, el, opts); - - if (full) { - pageBlock = lyr3[0]; - pageBlockEls = $(opts.focusableElements,pageBlock); - if (opts.focusInput) - setTimeout(focus, 20); - } - else - center(lyr3[0], opts.centerX, opts.centerY); - - if (opts.timeout) { - // auto-unblock - var to = setTimeout(function() { - if (full) - $.unblockUI(opts); - else - $(el).unblock(opts); - }, opts.timeout); - $(el).data('blockUI.timeout', to); - } - } - - // remove the block - function remove(el, opts) { - var count; - var full = (el == window); - var $el = $(el); - var data = $el.data('blockUI.history'); - var to = $el.data('blockUI.timeout'); - if (to) { - clearTimeout(to); - $el.removeData('blockUI.timeout'); - } - opts = $.extend({}, $.blockUI.defaults, opts || {}); - bind(0, el, opts); // unbind events - - if (opts.onUnblock === null) { - opts.onUnblock = $el.data('blockUI.onUnblock'); - $el.removeData('blockUI.onUnblock'); - } - - var els; - if (full) // crazy selector to handle odd field errors in ie6/7 - els = $('body').children().filter('.blockUI').add('body > .blockUI'); - else - els = $el.find('>.blockUI'); - - // fix cursor issue - if ( opts.cursorReset ) { - if ( els.length > 1 ) - els[1].style.cursor = opts.cursorReset; - if ( els.length > 2 ) - els[2].style.cursor = opts.cursorReset; - } - - if (full) - pageBlock = pageBlockEls = null; - - if (opts.fadeOut) { - count = els.length; - els.fadeOut(opts.fadeOut, function() { - if ( --count === 0) - reset(els,data,opts,el); - }); - } - else - reset(els, data, opts, el); - } - - // move blocking element back into the DOM where it started - function reset(els,data,opts,el) { - var $el = $(el); - els.each(function(i,o) { - // remove via DOM calls so we don't lose event handlers - if (this.parentNode) - this.parentNode.removeChild(this); - }); - - if (data && data.el) { - data.el.style.display = data.display; - data.el.style.position = data.position; - if (data.parent) - data.parent.appendChild(data.el); - $el.removeData('blockUI.history'); - } - - if ($el.data('blockUI.static')) { - $el.css('position', 'static'); // #22 - } - - if (typeof opts.onUnblock == 'function') - opts.onUnblock(el,opts); - - // fix issue in Safari 6 where block artifacts remain until reflow - var body = $(document.body), w = body.width(), cssW = body[0].style.width; - body.width(w-1).width(w); - body[0].style.width = cssW; - } - - // bind/unbind the handler - function bind(b, el, opts) { - var full = el == window, $el = $(el); - - // don't bother unbinding if there is nothing to unbind - if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) - return; - - $el.data('blockUI.isBlocked', b); - - // don't bind events when overlay is not in use or if bindEvents is false - if (!full || !opts.bindEvents || (b && !opts.showOverlay)) - return; - - // bind anchors and inputs for mouse and key events - var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove'; - if (b) - $(document).bind(events, opts, handler); - else - $(document).unbind(events, handler); - - // former impl... - // var $e = $('a,:input'); - // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); - } - - // event handler to suppress keyboard/mouse events when blocking - function handler(e) { - // allow tab navigation (conditionally) - if (e.keyCode && e.keyCode == 9) { - if (pageBlock && e.data.constrainTabKey) { - var els = pageBlockEls; - var fwd = !e.shiftKey && e.target === els[els.length-1]; - var back = e.shiftKey && e.target === els[0]; - if (fwd || back) { - setTimeout(function(){focus(back);},10); - return false; - } - } - } - var opts = e.data; - var target = $(e.target); - if (target.hasClass('blockOverlay') && opts.onOverlayClick) - opts.onOverlayClick(); - - // allow events within the message content - if (target.parents('div.' + opts.blockMsgClass).length > 0) - return true; - - // allow events for content that is not being blocked - return target.parents().children().filter('div.blockUI').length === 0; - } - - function focus(back) { - if (!pageBlockEls) - return; - var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; - if (e) - e.focus(); - } - - function center(el, x, y) { - var p = el.parentNode, s = el.style; - var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); - var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); - if (x) s.left = l > 0 ? (l+'px') : '0'; - if (y) s.top = t > 0 ? (t+'px') : '0'; - } - - function sz(el, p) { - return parseInt($.css(el,p),10)||0; - } - - } - - - /*global define:true */ - if (typeof define === 'function' && define.amd && define.amd.jQuery) { - define(['jquery'], setup); - } else { - setup(jQuery); - } - -})(); -- cgit v1.2.3 From 93ef00850001962955a2f9c8f34a530a9cf8972f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 21:11:39 -0500 Subject: Bump bootstrap-sass to ~> 3.3.0 --- Gemfile | 2 +- Gemfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 2a1c4f7d73a..f77b7d25bb2 100644 --- a/Gemfile +++ b/Gemfile @@ -200,7 +200,7 @@ gem 'turbolinks', '~> 2.5.0' gem 'jquery-turbolinks', '~> 2.1.0' gem 'addressable', '~> 2.3.8' -gem 'bootstrap-sass', '~> 3.0' +gem 'bootstrap-sass', '~> 3.3.0' gem 'font-awesome-rails', '~> 4.2' gem 'gitlab_emoji', '~> 0.2.0' gem 'gon', '~> 6.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 9769ae80a7d..3b977944eec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,7 +66,7 @@ GEM attr_encrypted (1.3.4) encryptor (>= 1.3.0) attr_required (1.0.0) - autoprefixer-rails (6.1.2) + autoprefixer-rails (6.2.3) execjs json awesome_print (1.2.0) @@ -82,9 +82,9 @@ GEM erubis (>= 2.6.6) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootstrap-sass (3.3.5) - autoprefixer-rails (>= 5.0.0.1) - sass (>= 3.2.19) + bootstrap-sass (3.3.6) + autoprefixer-rails (>= 5.2.1) + sass (>= 3.3.4) brakeman (3.1.4) erubis (~> 2.6) fastercsv (~> 1.5) @@ -843,7 +843,7 @@ DEPENDENCIES benchmark-ips better_errors (~> 1.0.1) binding_of_caller (~> 0.7.2) - bootstrap-sass (~> 3.0) + bootstrap-sass (~> 3.3.0) brakeman (~> 3.1.0) browser (~> 1.0.0) bullet -- cgit v1.2.3 From 762210db6d3a16307c032d84454b55474221e50c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 21:29:21 -0500 Subject: Bump cal-heatmap-rails to ~> 3.5.0 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 2a1c4f7d73a..2418a02a451 100644 --- a/Gemfile +++ b/Gemfile @@ -172,7 +172,7 @@ gem 'ruby-fogbugz', '~> 0.2.1' gem 'd3_rails', '~> 3.5.5' #cal-heatmap -gem "cal-heatmap-rails", "~> 0.0.1" +gem 'cal-heatmap-rails', '~> 3.5.0' # underscore-rails gem "underscore-rails", "~> 1.8.0" diff --git a/Gemfile.lock b/Gemfile.lock index 9769ae80a7d..ddd31e4802d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,7 +106,7 @@ GEM bundler (~> 1.2) thor (~> 0.18) byebug (8.2.1) - cal-heatmap-rails (0.0.1) + cal-heatmap-rails (3.5.1) capybara (2.4.4) mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -849,7 +849,7 @@ DEPENDENCIES bullet bundler-audit byebug - cal-heatmap-rails (~> 0.0.1) + cal-heatmap-rails (~> 3.5.0) capybara (~> 2.4.0) capybara-screenshot (~> 1.0.0) carrierwave (~> 0.9.0) -- cgit v1.2.3 From 2417f55467247471d2b39de0387b30617ecf59de Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 21:34:49 -0500 Subject: Bump d3_rails to ~> 3.5.0 --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 2418a02a451..1a628a68310 100644 --- a/Gemfile +++ b/Gemfile @@ -169,7 +169,7 @@ gem 'asana', '~> 0.4.0' gem 'ruby-fogbugz', '~> 0.2.1' # d3 -gem 'd3_rails', '~> 3.5.5' +gem 'd3_rails', '~> 3.5.0' #cal-heatmap gem 'cal-heatmap-rails', '~> 3.5.0' diff --git a/Gemfile.lock b/Gemfile.lock index ddd31e4802d..7b70c05bc48 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -859,7 +859,7 @@ DEPENDENCIES connection_pool (~> 2.0) coveralls (~> 0.8.2) creole (~> 0.5.0) - d3_rails (~> 3.5.5) + d3_rails (~> 3.5.0) database_cleaner (~> 1.4.0) default_value_for (~> 3.0.0) devise (~> 3.5.3) -- cgit v1.2.3 From f8c3b69fb675f33607315acfacb4fdac170a3635 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 22:10:16 -0500 Subject: Remove unused "options" object from Calendar JS --- app/assets/javascripts/calendar.js.coffee | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/assets/javascripts/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee index 97621236924..d80e0e716ce 100644 --- a/app/assets/javascripts/calendar.js.coffee +++ b/app/assets/javascripts/calendar.js.coffee @@ -1,9 +1,4 @@ class @Calendar - options = - month: "short" - day: "numeric" - year: "numeric" - constructor: (timestamps, starting_year, starting_month, calendar_activities_path) -> cal = new CalHeatMap() cal.init -- cgit v1.2.3 From 4caf0433b7a06427c82eb1b2830d2c2dd830f144 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 22:10:48 -0500 Subject: Reorder JS requires The old way broke d3, for some reason. --- app/assets/javascripts/application.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index affab5bb030..e7e997640d5 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -16,6 +16,8 @@ #= require jquery.scrollTo #= require jquery.blockUI #= require jquery.turbolinks +#= require d3 +#= require cal-heatmap #= require turbolinks #= require autosave #= require bootstrap @@ -27,7 +29,6 @@ #= require branch-graph #= require ace/ace #= require ace/ext-searchbox -#= require d3 #= require underscore #= require nprogress #= require nprogress-turbolinks @@ -39,7 +40,6 @@ #= require shortcuts_dashboard_navigation #= require shortcuts_issuable #= require shortcuts_network -#= require cal-heatmap #= require jquery.nicescroll.min #= require_tree . -- cgit v1.2.3 From 1a1113f7c4ad6e5bceb898593cba6caecbdf0097 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 22:11:34 -0500 Subject: Simplify `ContributionsCalendar#starting_year` and `#starting_month` --- lib/gitlab/contributions_calendar.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index 8a7f8dc5003..85583dce9ee 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -45,11 +45,11 @@ module Gitlab end def starting_year - (Time.now - 1.year).strftime("%Y") + 1.year.ago.year end def starting_month - Date.today.strftime("%m").to_i + Date.today.month end end end -- cgit v1.2.3 From d1873f16b4cc954b1417fa84517d44b22a05821f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 22:12:50 -0500 Subject: Update cal-heatmap style overrides Some were no longer needed, others needed `!important`. --- app/assets/stylesheets/framework/calendar.scss | 42 ++++---------------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index a36fefe22c5..580012abd77 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -19,38 +19,33 @@ } } } + /** * This overwrites the default values of the cal-heatmap gem */ .calendar { .qi { - background-color: #999; fill: #fff; } .q1 { - background-color: #dae289; - fill: #ededed; + fill: #ededed !important; } .q2 { - background-color: #cedb9c; - fill: #ACD5F2; + fill: #ACD5F2 !important; } .q3 { - background-color: #b5cf6b; - fill: #7FA8D1; + fill: #7FA8D1 !important; } .q4 { - background-color: #637939; - fill: #49729B; + fill: #49729B !important; } .q5 { - background-color: #3b6427; - fill: #254E77; + fill: #254E77 !important; } .domain-background { @@ -59,32 +54,7 @@ } .ch-tooltip { - position: absolute; - display: none; - margin-top: 22px; - margin-left: 1px; - font-size: 13px; padding: 3px; font-weight: 550; - background-color: #222; - span { - position: absolute; - width: 200px; - text-align: center; - visibility: hidden; - border-radius: 10px; - &:after { - content: ''; - position: absolute; - top: 100%; - left: 50%; - margin-left: -8px; - width: 0; - height: 0; - border-top: 8px solid #000000; - border-right: 8px solid transparent; - border-left: 8px solid transparent; - } - } } } -- cgit v1.2.3 From c80f75ea1fc1dda4001ab32d52136445abb09b53 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 Jan 2016 15:29:55 -0500 Subject: Give the logo shapes meaningful IDs --- app/views/shared/_logo.svg | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg index da49c48acd3..90f5f4e672b 100644 --- a/app/views/shared/_logo.svg +++ b/app/views/shared/_logo.svg @@ -5,13 +5,13 @@ - - - - - - - + + + + + + + -- cgit v1.2.3 From e5800d65de66aded6178252c4ae5025633eccc5e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 Jan 2016 15:32:40 -0500 Subject: Use the logo as a loading indicator Closes #5616 --- app/assets/javascripts/logo.js.coffee | 45 +++++++++++++++++++++++++++ app/assets/stylesheets/framework/sidebar.scss | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/logo.js.coffee diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee new file mode 100644 index 00000000000..8cd9f865e37 --- /dev/null +++ b/app/assets/javascripts/logo.js.coffee @@ -0,0 +1,45 @@ +NProgress.configure(showSpinner: false) + +defaultClass = 'tanuki-shape' +highlightClass = 'highlight' +pieces = [ + 'path#tanuki-right-cheek', + 'path#tanuki-right-eye, path#tanuki-right-ear', + 'path#tanuki-nose', + 'path#tanuki-left-eye, path#tanuki-left-ear', + 'path#tanuki-left-cheek', +] +timeout = null + +clearHighlights = -> + $(".#{defaultClass}").attr('class', defaultClass) + +start = -> + clearHighlights() + work(0) + +stop = -> + window.clearTimeout(timeout) + clearHighlights() + +work = (pieceIndex) => + # jQuery's addClass won't work on an SVG. Who knew! + $piece = $(pieces[pieceIndex]) + $piece.attr('class', "#{defaultClass} #{highlightClass}") + + timeout = setTimeout(=> + $piece.attr('class', defaultClass) + + # If we hit the last piece, reset the index and then reverse the array to + # get a nice back-and-forth sweeping look + if pieceIndex + 1 >= pieces.length + nextIndex = 0 + pieces.reverse() + else + nextIndex = pieceIndex + 1 + + work(nextIndex) + , 200) + +$(document).on 'page:fetch', start +$(document).on 'page:change', stop diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 458af76cb75..83243dd2457 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -105,7 +105,7 @@ .tanuki-shape { transition: all 0.8s; - &:hover { + &:hover, &.highlight { fill: rgb(255, 255, 255); transition: all 0.1s; } -- cgit v1.2.3 From 71c31ecf731ff9f6f1d7107ae03397ec98c9f61f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 Jan 2016 19:57:21 -0500 Subject: Ensure the sweep always starts from the left --- app/assets/javascripts/logo.js.coffee | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee index 8cd9f865e37..47135a6c5eb 100644 --- a/app/assets/javascripts/logo.js.coffee +++ b/app/assets/javascripts/logo.js.coffee @@ -1,14 +1,14 @@ NProgress.configure(showSpinner: false) defaultClass = 'tanuki-shape' -highlightClass = 'highlight' pieces = [ - 'path#tanuki-right-cheek', - 'path#tanuki-right-eye, path#tanuki-right-ear', - 'path#tanuki-nose', - 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek', + 'path#tanuki-left-eye, path#tanuki-left-ear', + 'path#tanuki-nose', + 'path#tanuki-right-eye, path#tanuki-right-ear', + 'path#tanuki-right-cheek', ] +firstPiece = pieces[0] timeout = null clearHighlights = -> @@ -16,18 +16,19 @@ clearHighlights = -> start = -> clearHighlights() + pieces.reverse() unless pieces[0] == firstPiece work(0) stop = -> window.clearTimeout(timeout) clearHighlights() -work = (pieceIndex) => +work = (pieceIndex) -> # jQuery's addClass won't work on an SVG. Who knew! $piece = $(pieces[pieceIndex]) - $piece.attr('class', "#{defaultClass} #{highlightClass}") + $piece.attr('class', "#{defaultClass} highlight") - timeout = setTimeout(=> + timeout = setTimeout(-> $piece.attr('class', defaultClass) # If we hit the last piece, reset the index and then reverse the array to -- cgit v1.2.3 From fd178c1e7d23b0bf96565ae5177485e847c9271d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 Jan 2016 19:53:45 -0500 Subject: Prevent duplicate "username has already been taken" validation message Closes #201 - two-year-old bug, woo! :boom: :tada: --- app/models/user.rb | 5 ++++- spec/models/user_spec.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index df87f3b79bd..20f907e4347 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -352,10 +352,13 @@ class User < ActiveRecord::Base end def namespace_uniq + # Return early if username already failed the first uniqueness validation + return if self.errors[:username].include?('has already been taken') + namespace_name = self.username existing_namespace = Namespace.by_path(namespace_name) if existing_namespace && existing_namespace != self.namespace - self.errors.add :username, "already exists" + self.errors.add(:username, 'has already been taken') end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 2f184bbaf92..a16161e673e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -106,7 +106,7 @@ describe User, models: true do end it 'validates uniqueness' do - expect(subject).to validate_uniqueness_of(:username) + expect(subject).to validate_uniqueness_of(:username).case_insensitive end end -- cgit v1.2.3 From befdc6770799735bde5eeb26a1170cedd18a00de Mon Sep 17 00:00:00 2001 From: Atul Bhosale Date: Sun, 3 Jan 2016 18:28:52 +0530 Subject: Update copyright notice to 2016 [ci skip] --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d8cb29f3638..1dc1bdb7411 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2015 GitLab B.V. +Copyright (c) 2011-2016 GitLab B.V. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal -- cgit v1.2.3 From 2802d4992d064e63cf2015c7bb002318eaa84c41 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 3 Jan 2016 11:20:14 -0800 Subject: Expire view caches when application settings change Closes #5728 --- CHANGELOG | 1 + app/views/events/_event.html.haml | 2 +- app/views/projects/commits/_commit.html.haml | 2 +- app/views/shared/projects/_project.html.haml | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ea403642e32..4c1148bc4dc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.4.0 (unreleased) + - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu) - Implement new UI for group page - Implement search inside emoji picker - Add API support for looking up a user by username (Stan Hu) diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 9aacc79d686..46432a92348 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -3,7 +3,7 @@ .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} - = cache [event, "v2.1"] do + = cache [event, current_application_settings, "v2.1"] do = image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:'' - if event.created_project? = render "events/event/created_project", event: event diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 28b82dd31f3..012825f0fdb 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -5,7 +5,7 @@ - note_count = notes.user.count - ci_commit = project.ci_commit(commit.sha) -- cache_key = [project.path_with_namespace, commit.id, note_count] +- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count] - cache_key.push(ci_commit.status) if ci_commit = cache(cache_key) do diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index c36995b94d7..86249851a82 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -5,7 +5,7 @@ - css_class = '' unless local_assigns[:css_class] - css_class += " no-description" unless project.description.present? %li.project-row{ class: css_class } - = cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2.2'] do + = cache [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2'] do = link_to project_path(project), class: dom_class(project) do - if avatar .dash-project-avatar -- cgit v1.2.3 From 086cfc8685a6489ca032899307c77f828f515fbb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 26 Aug 2015 23:36:17 -0700 Subject: Fix API project lookups when querying with a namespace with dots Attempting to use the /projects/:id API by specifying :id in "namespace/project" format would always result in a 404 if the namespace contained a dot. The reason? From http://guides.rubyonrails.org/routing.html#specifying-constraints: "By default the :id parameter doesn't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within an :id add a constraint which overrides this - for example id: /[^\/]+/ allows anything except a slash." Closes https://github.com/gitlabhq/gitlabhq/issues/9573 --- CHANGELOG | 1 + lib/api/projects.rb | 2 +- spec/requests/api/projects_spec.rb | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2b7d5808e7e..e651aece696 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.4.0 (unreleased) - Revert back upvote and downvote button to the issue and MR pages - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) - Fix version check image in Safari + - Fix API project lookups when querying with a namespace with dots (Stan Hu) v 8.3.3 (unreleased) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index a9e0960872a..0781236cf6d 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -3,7 +3,7 @@ module API class Projects < Grape::API before { authenticate! } - resource :projects do + resource :projects, requirements: { id: /[^\/]+/ } do helpers do def map_public_to_visibility_level(attrs) publik = attrs.delete(:public) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 7f0f9454b10..ab2530859ea 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -382,6 +382,15 @@ describe API::API, api: true do expect(response.status).to eq(404) end + it 'should handle users with dots' do + dot_user = create(:user, username: 'dot.user') + project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) + + get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) + expect(response.status).to eq(200) + expect(json_response['name']).to eq(project.name) + end + describe 'permissions' do context 'all projects' do it 'Contains permission information' do -- cgit v1.2.3 From 8bfc46451eae5bd92380b9fbdf91b9636d55066c Mon Sep 17 00:00:00 2001 From: Josh Frye Date: Wed, 23 Dec 2015 10:20:07 -0500 Subject: Show 'New Merge Request' buttons on canonical repo. --- CHANGELOG | 2 ++ app/views/projects/buttons/_dropdown.html.haml | 7 ++++--- app/views/projects/merge_requests/index.html.haml | 5 +++-- features/project/fork.feature | 10 ++++++++++ features/steps/project/fork.rb | 10 ++++++++++ 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2b7d5808e7e..e4245060489 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -91,6 +91,8 @@ v 8.3.0 - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present - Persist runners registration token in database - Fix online editor should not remove newlines at the end of the file + - Expose Git's version in the admin area + - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 1f639fecc30..459e6da2fe2 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -8,11 +8,12 @@ = link_to url_for_new_issue(@project, only_path: true) do = icon('exclamation-circle fw') New issue - - if can?(current_user, :create_merge_request, @project) + - merge_project = can?(current_user, :create_merge_request, @project) ? @project : current_user.fork_of(@project) + - if merge_project %li - = link_to new_namespace_project_merge_request_path(@project.namespace, @project) do + = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do = icon('tasks fw') - New merge request + New Merge Request - if can?(current_user, :create_snippet, @project) %li = link_to new_namespace_project_snippet_path(@project.namespace, @project) do diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 086298e5af1..972fce9ad3d 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -6,9 +6,10 @@ .controls = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) - - if can? current_user, :create_merge_request, @project + - merge_project = can?(current_user, :create_merge_request, @project) ? @project : current_user.fork_of(@project) + - if merge_project .pull-left.hidden-xs - = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new", title: "New Merge Request" do + = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do %i.fa.fa-plus New Merge Request = render 'shared/issuable/filter', type: :merge_requests diff --git a/features/project/fork.feature b/features/project/fork.feature index 22f68e5b340..40849352370 100644 --- a/features/project/fork.feature +++ b/features/project/fork.feature @@ -1,3 +1,4 @@ +@forks Feature: Project Fork Background: Given I sign in as a user @@ -14,3 +15,12 @@ Feature: Project Fork And I click link "Fork" When I fork to my namespace Then I should see a "Name has already been taken" warning + + Scenario: Merge request on canonical repo goes to fork merge request page + Given I click link "Fork" + And I fork to my namespace + Then I should see the forked project page + When I visit project "Shop" page + Then I should see "New merge request" + And I goto the Merge Requests page + Then I should see "New merge request" diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index b0230add34f..878ddea46ff 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -30,4 +30,14 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps click_link current_user.name end end + + step 'I should see "New Merge Request"' do + expect(page).to have_content "New Merge Request" + end + + step 'I goto the Merge Requests page' do + page.within '.page-sidebar-expanded' do + click_link "Merge Requests" + end + end end -- cgit v1.2.3 From 95e6327b85b8230dc834c781203c72b318551b5d Mon Sep 17 00:00:00 2001 From: Josh Frye Date: Wed, 23 Dec 2015 12:24:25 -0500 Subject: Fix tests --- app/views/projects/buttons/_dropdown.html.haml | 2 +- app/views/projects/merge_requests/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 459e6da2fe2..35a9d3223a6 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -8,7 +8,7 @@ = link_to url_for_new_issue(@project, only_path: true) do = icon('exclamation-circle fw') New issue - - merge_project = can?(current_user, :create_merge_request, @project) ? @project : current_user.fork_of(@project) + - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - if merge_project %li = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 972fce9ad3d..8d5d0394a82 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -6,7 +6,7 @@ .controls = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) - - merge_project = can?(current_user, :create_merge_request, @project) ? @project : current_user.fork_of(@project) + - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - if merge_project .pull-left.hidden-xs = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do -- cgit v1.2.3 From 796c11e9a7d955ac40c3d1f8427f3f65063ac8c4 Mon Sep 17 00:00:00 2001 From: Josh Frye Date: Wed, 23 Dec 2015 12:36:49 -0500 Subject: Remove feature tag from testing locally --- features/project/fork.feature | 1 - 1 file changed, 1 deletion(-) diff --git a/features/project/fork.feature b/features/project/fork.feature index 40849352370..1182f493e34 100644 --- a/features/project/fork.feature +++ b/features/project/fork.feature @@ -1,4 +1,3 @@ -@forks Feature: Project Fork Background: Given I sign in as a user -- cgit v1.2.3 From edd2ce38369e5a332b1b9932647d670862ffddbf Mon Sep 17 00:00:00 2001 From: Josh Frye Date: Fri, 25 Dec 2015 11:30:48 -0500 Subject: Change text back. Add additional tests. --- app/views/projects/buttons/_dropdown.html.haml | 2 +- features/project/fork.feature | 2 ++ features/steps/project/fork.rb | 13 +++++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 35a9d3223a6..f9ab78e7874 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -13,7 +13,7 @@ %li = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do = icon('tasks fw') - New Merge Request + New merge request - if can?(current_user, :create_snippet, @project) %li = link_to new_namespace_project_snippet_path(@project.namespace, @project) do diff --git a/features/project/fork.feature b/features/project/fork.feature index 1182f493e34..37cd53ee977 100644 --- a/features/project/fork.feature +++ b/features/project/fork.feature @@ -23,3 +23,5 @@ Feature: Project Fork Then I should see "New merge request" And I goto the Merge Requests page Then I should see "New merge request" + And I click link "New merge request" + Then I should see the new merge request page for my namespace diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index 878ddea46ff..e98bd51ca89 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -31,8 +31,8 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps end end - step 'I should see "New Merge Request"' do - expect(page).to have_content "New Merge Request" + step 'I should see "New merge request"' do + expect(page).to have_content(/new merge request/i) end step 'I goto the Merge Requests page' do @@ -40,4 +40,13 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps click_link "Merge Requests" end end + + step 'I click link "New merge request"' do + expect(page).to have_content(/new merge request/i) + click_link "New Merge Request" + end + + step 'I should see the new merge request page for my namespace' do + current_path.should have_content(/#{current_user.namespace.name}/i) + end end -- cgit v1.2.3 From 96075be6f4688a59335130dc796132ad4f232442 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 4 Jan 2016 11:37:46 +0100 Subject: Ability to increment custom transaction values This will be used to store/increment the total query/view rendering timings on a per transaction basis. This in turn can greatly reduce the amount of metrics stored. --- lib/gitlab/metrics/transaction.rb | 15 +++++++++++++-- spec/lib/gitlab/metrics/transaction_spec.rb | 12 ++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index 43a7dab5323..0aaebf262d4 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -19,7 +19,8 @@ module Gitlab @started_at = nil @finished_at = nil - @tags = {} + @values = Hash.new(0) + @tags = {} end def duration @@ -44,6 +45,10 @@ module Gitlab @metrics << Metric.new(series, values, tags) end + def increment(name, value) + @values[name] += value + end + def add_tag(key, value) @tags[key] = value end @@ -54,7 +59,13 @@ module Gitlab end def track_self - add_metric(@series, { duration: duration }, @tags) + values = { duration: duration } + + @values.each do |name, value| + values[name] = value + end + + add_metric(@series, values, @tags) end def submit diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb index 345163bfbea..18d63bdbdb9 100644 --- a/spec/lib/gitlab/metrics/transaction_spec.rb +++ b/spec/lib/gitlab/metrics/transaction_spec.rb @@ -38,6 +38,18 @@ describe Gitlab::Metrics::Transaction do end end + describe '#increment' do + it 'increments a counter' do + transaction.increment(:time, 1) + transaction.increment(:time, 2) + + expect(transaction).to receive(:add_metric). + with('rspec', { duration: 0.0, time: 3 }, {}) + + transaction.track_self + end + end + describe '#add_tag' do it 'adds a tag' do transaction.add_tag(:foo, 'bar') -- cgit v1.2.3 From 66a997a91403eef62ffd9fb789e899619d021a26 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 4 Jan 2016 12:14:36 +0100 Subject: Track total query/view timings in transactions --- config/initializers/metrics.rb | 1 + lib/gitlab/metrics/subscribers/action_view.rb | 1 + lib/gitlab/metrics/subscribers/active_record.rb | 22 ++++++++++++++ .../gitlab/metrics/subscribers/action_view_spec.rb | 3 ++ .../metrics/subscribers/active_record_spec.rb | 35 ++++++++++++++++++++++ 5 files changed, 62 insertions(+) create mode 100644 lib/gitlab/metrics/subscribers/active_record.rb create mode 100644 spec/lib/gitlab/metrics/subscribers/active_record_spec.rb diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index ebb20be7283..52ace27b7ae 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -6,6 +6,7 @@ if Gitlab::Metrics.enabled? # These are manually require'd so the classes are registered properly with # ActiveSupport. require 'gitlab/metrics/subscribers/action_view' + require 'gitlab/metrics/subscribers/active_record' Gitlab::Application.configure do |config| config.middleware.use(Gitlab::Metrics::RackMiddleware) diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb index 7e0dcf99d92..7c0105d543a 100644 --- a/lib/gitlab/metrics/subscribers/action_view.rb +++ b/lib/gitlab/metrics/subscribers/action_view.rb @@ -19,6 +19,7 @@ module Gitlab values = values_for(event) tags = tags_for(event) + current_transaction.increment(:view_duration, event.duration) current_transaction.add_metric(SERIES, values, tags) end diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb new file mode 100644 index 00000000000..8008b3bc895 --- /dev/null +++ b/lib/gitlab/metrics/subscribers/active_record.rb @@ -0,0 +1,22 @@ +module Gitlab + module Metrics + module Subscribers + # Class for tracking the total query duration of a transaction. + class ActiveRecord < ActiveSupport::Subscriber + attach_to :active_record + + def sql(event) + return unless current_transaction + + current_transaction.increment(:sql_duration, event.duration) + end + + private + + def current_transaction + Transaction.current + end + end + end + end +end diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb index bca76ca5a69..699d50f770a 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb @@ -28,6 +28,9 @@ describe Gitlab::Metrics::Subscribers::ActionView do line: 4 } + expect(transaction).to receive(:increment). + with(:view_duration, 2.1) + expect(transaction).to receive(:add_metric). with(described_class::SERIES, values, tags) diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb new file mode 100644 index 00000000000..9ecedd934c6 --- /dev/null +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Gitlab::Metrics::Subscribers::ActiveRecord do + let(:transaction) { Gitlab::Metrics::Transaction.new('rspec') } + let(:subscriber) { described_class.new } + + let(:event) do + double(:event, duration: 0.2, + payload: { sql: 'SELECT * FROM users WHERE id = 10' }) + end + + describe '#sql' do + describe 'without a current transaction' do + it 'simply returns' do + expect_any_instance_of(Gitlab::Metrics::Transaction). + to_not receive(:increment) + + subscriber.sql(event) + end + end + + describe 'with a current transaction' do + it 'increments the :sql_duration value' do + expect(subscriber).to receive(:current_transaction). + at_least(:once). + and_return(transaction) + + expect(transaction).to receive(:increment). + with(:sql_duration, 0.2) + + subscriber.sql(event) + end + end + end +end -- cgit v1.2.3 From 825b46f8a3eb620f99192217d414b72dffe597d7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 4 Jan 2016 12:19:45 +0100 Subject: Track total method call times per transaction This makes it easier to see where time is spent without having to aggregate all the individual points in the method_calls series. --- lib/gitlab/metrics/instrumentation.rb | 2 ++ spec/lib/gitlab/metrics/instrumentation_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 06fc2f25948..d9fce2e6758 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -123,6 +123,8 @@ module Gitlab duration = (Time.now - start) * 1000.0 if duration >= Gitlab::Metrics.method_call_threshold + trans.increment(:method_duration, duration) + trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, { duration: duration }, method: #{label.inspect}) diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index a9003d8796b..af020f652be 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -48,6 +48,9 @@ describe Gitlab::Metrics::Instrumentation do allow(described_class).to receive(:transaction). and_return(transaction) + expect(transaction).to receive(:increment). + with(:method_duration, a_kind_of(Numeric)) + expect(transaction).to receive(:add_metric). with(described_class::SERIES, an_instance_of(Hash), method: 'Dummy.foo') @@ -102,6 +105,9 @@ describe Gitlab::Metrics::Instrumentation do allow(described_class).to receive(:transaction). and_return(transaction) + expect(transaction).to receive(:increment). + with(:method_duration, a_kind_of(Numeric)) + expect(transaction).to receive(:add_metric). with(described_class::SERIES, an_instance_of(Hash), method: 'Dummy#bar') -- cgit v1.2.3 From 2ea464bb272fa52ff34a188a921f0bc90811ca45 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 4 Jan 2016 12:45:31 +0100 Subject: Use separate series for Rails/Sidekiq sample stats This removes the need for any tags to differentiate between Sidekiq and Rails statistics while still being able to separate the two. --- lib/gitlab/metrics/sampler.rb | 19 ++++++++++++----- spec/lib/gitlab/metrics/sampler_spec.rb | 38 ++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/sampler.rb index 998578e1c0a..1ea425bc904 100644 --- a/lib/gitlab/metrics/sampler.rb +++ b/lib/gitlab/metrics/sampler.rb @@ -50,12 +50,11 @@ module Gitlab end def sample_memory_usage - @metrics << Metric.new('memory_usage', value: System.memory_usage) + add_metric('memory_usage', value: System.memory_usage) end def sample_file_descriptors - @metrics << Metric. - new('file_descriptors', value: System.file_descriptor_count) + add_metric('file_descriptors', value: System.file_descriptor_count) end if Metrics.mri? @@ -69,7 +68,7 @@ module Gitlab counts['Symbol'] = Symbol.all_symbols.length counts.each do |name, count| - @metrics << Metric.new('object_counts', { count: count }, type: name) + add_metric('object_counts', { count: count }, type: name) end end else @@ -91,7 +90,17 @@ module Gitlab stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count] - @metrics << Metric.new('gc_statistics', stats) + add_metric('gc_statistics', stats) + end + + def add_metric(series, values, tags = {}) + prefix = sidekiq? ? 'sidekiq_' : 'rails_' + + @metrics << Metric.new("#{prefix}#{series}", values, tags) + end + + def sidekiq? + Sidekiq.server? end end end diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb index 51a941c48cd..27211350fbe 100644 --- a/spec/lib/gitlab/metrics/sampler_spec.rb +++ b/spec/lib/gitlab/metrics/sampler_spec.rb @@ -51,8 +51,8 @@ describe Gitlab::Metrics::Sampler do expect(Gitlab::Metrics::System).to receive(:memory_usage). and_return(9000) - expect(Gitlab::Metrics::Metric).to receive(:new). - with('memory_usage', value: 9000). + expect(sampler).to receive(:add_metric). + with(/memory_usage/, value: 9000). and_call_original sampler.sample_memory_usage @@ -64,8 +64,8 @@ describe Gitlab::Metrics::Sampler do expect(Gitlab::Metrics::System).to receive(:file_descriptor_count). and_return(4) - expect(Gitlab::Metrics::Metric).to receive(:new). - with('file_descriptors', value: 4). + expect(sampler).to receive(:add_metric). + with(/file_descriptors/, value: 4). and_call_original sampler.sample_file_descriptors @@ -74,8 +74,8 @@ describe Gitlab::Metrics::Sampler do describe '#sample_objects' do it 'adds a metric containing the amount of allocated objects' do - expect(Gitlab::Metrics::Metric).to receive(:new). - with('object_counts', an_instance_of(Hash), an_instance_of(Hash)). + expect(sampler).to receive(:add_metric). + with(/object_counts/, an_instance_of(Hash), an_instance_of(Hash)). at_least(:once). and_call_original @@ -87,11 +87,33 @@ describe Gitlab::Metrics::Sampler do it 'adds a metric containing garbage collection statistics' do expect(GC::Profiler).to receive(:total_time).and_return(0.24) - expect(Gitlab::Metrics::Metric).to receive(:new). - with('gc_statistics', an_instance_of(Hash)). + expect(sampler).to receive(:add_metric). + with(/gc_statistics/, an_instance_of(Hash)). and_call_original sampler.sample_gc end end + + describe '#add_metric' do + it 'prefixes the series name for a Rails process' do + expect(sampler).to receive(:sidekiq?).and_return(false) + + expect(Gitlab::Metrics::Metric).to receive(:new). + with('rails_cats', { value: 10 }, {}). + and_call_original + + sampler.add_metric('cats', value: 10) + end + + it 'prefixes the series name for a Sidekiq process' do + expect(sampler).to receive(:sidekiq?).and_return(true) + + expect(Gitlab::Metrics::Metric).to receive(:new). + with('sidekiq_cats', { value: 10 }, {}). + and_call_original + + sampler.add_metric('cats', value: 10) + end + end end -- cgit v1.2.3 From 4c5be69583a61b9cce40b43dc93f00e1ae909680 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 4 Jan 2016 14:06:48 +0200 Subject: catch Emoji encode error --- app/helpers/issues_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 80e2741b09a..c12456a187f 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -99,7 +99,7 @@ module IssuesHelper end def emoji_icon(name, unicode = nil, aliases = []) - unicode ||= Emoji.emoji_filename(name) + unicode ||= Emoji.emoji_filename(name) rescue "" content_tag :div, "", class: "icon emoji-icon emoji-#{unicode}", -- cgit v1.2.3 From 2ee8f555996baca6b470d223ffad65419b730398 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 4 Jan 2016 13:17:02 +0100 Subject: Automatically prefix transaction series names This ensures Rails and Sidekiq transactions are split into the series "rails_transactions" and "sidekiq_transactions" respectively. --- lib/gitlab/metrics/rack_middleware.rb | 4 +--- lib/gitlab/metrics/sidekiq_middleware.rb | 4 +--- lib/gitlab/metrics/transaction.rb | 15 +++++++++------ spec/lib/gitlab/metrics/instrumentation_spec.rb | 2 +- spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb | 2 +- spec/lib/gitlab/metrics/subscribers/action_view_spec.rb | 2 +- spec/lib/gitlab/metrics/subscribers/active_record_spec.rb | 2 +- spec/lib/gitlab/metrics/transaction_spec.rb | 8 ++++---- 8 files changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb index bb9e4fcb918..5c0587c4c51 100644 --- a/lib/gitlab/metrics/rack_middleware.rb +++ b/lib/gitlab/metrics/rack_middleware.rb @@ -4,8 +4,6 @@ module Gitlab class RackMiddleware CONTROLLER_KEY = 'action_controller.instance' - SERIES = 'rails_transactions' - def initialize(app) @app = app end @@ -32,7 +30,7 @@ module Gitlab end def transaction_from_env(env) - trans = Transaction.new(SERIES) + trans = Transaction.new trans.add_tag(:request_method, env['REQUEST_METHOD']) trans.add_tag(:request_uri, env['REQUEST_URI']) diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb index 6e804dd2562..ad441decfa2 100644 --- a/lib/gitlab/metrics/sidekiq_middleware.rb +++ b/lib/gitlab/metrics/sidekiq_middleware.rb @@ -4,10 +4,8 @@ module Gitlab # # This middleware is intended to be used as a server-side middleware. class SidekiqMiddleware - SERIES = 'sidekiq_transactions' - def call(worker, message, queue) - trans = Transaction.new(SERIES) + trans = Transaction.new begin trans.run { yield } diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index 0aaebf262d4..68b86de0655 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -10,9 +10,7 @@ module Gitlab Thread.current[THREAD_KEY] end - # name - The name of this transaction as a String. - def initialize(series) - @series = series + def initialize @metrics = [] @uuid = SecureRandom.uuid @@ -40,9 +38,10 @@ module Gitlab end def add_metric(series, values, tags = {}) - tags = tags.merge(transaction_id: @uuid) + tags = tags.merge(transaction_id: @uuid) + prefix = sidekiq? ? 'sidekiq_' : 'rails_' - @metrics << Metric.new(series, values, tags) + @metrics << Metric.new("#{prefix}#{series}", values, tags) end def increment(name, value) @@ -65,12 +64,16 @@ module Gitlab values[name] = value end - add_metric(@series, values, @tags) + add_metric('transactions', values, @tags) end def submit Metrics.submit_metrics(@metrics.map(&:to_hash)) end + + def sidekiq? + Sidekiq.server? + end end end end diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index af020f652be..2a37cd40dde 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Metrics::Instrumentation do - let(:transaction) { Gitlab::Metrics::Transaction.new('rspec') } + let(:transaction) { Gitlab::Metrics::Transaction.new } before do @dummy = Class.new do diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb index 5fda6de52f4..5882e7d81c7 100644 --- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::Metrics::SidekiqMiddleware do describe '#tag_worker' do it 'adds the worker class and action to the transaction' do - trans = Gitlab::Metrics::Transaction.new('rspec') + trans = Gitlab::Metrics::Transaction.new worker = double(:worker, class: double(:class, name: 'TestWorker')) expect(trans).to receive(:add_tag).with(:action, 'TestWorker#perform') diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb index 699d50f770a..05e4fbbeb51 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Metrics::Subscribers::ActionView do - let(:transaction) { Gitlab::Metrics::Transaction.new('rspec') } + let(:transaction) { Gitlab::Metrics::Transaction.new } let(:subscriber) { described_class.new } diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb index 9ecedd934c6..81f35ba5d40 100644 --- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Metrics::Subscribers::ActiveRecord do - let(:transaction) { Gitlab::Metrics::Transaction.new('rspec') } + let(:transaction) { Gitlab::Metrics::Transaction.new } let(:subscriber) { described_class.new } let(:event) do diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb index 18d63bdbdb9..b9b94947afa 100644 --- a/spec/lib/gitlab/metrics/transaction_spec.rb +++ b/spec/lib/gitlab/metrics/transaction_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Metrics::Transaction do - let(:transaction) { described_class.new('rspec') } + let(:transaction) { described_class.new } describe '#duration' do it 'returns the duration of a transaction in seconds' do @@ -32,7 +32,7 @@ describe Gitlab::Metrics::Transaction do describe '#add_metric' do it 'adds a metric tagged with the transaction UUID' do expect(Gitlab::Metrics::Metric).to receive(:new). - with('foo', { number: 10 }, { transaction_id: transaction.uuid }) + with('rails_foo', { number: 10 }, { transaction_id: transaction.uuid }) transaction.add_metric('foo', number: 10) end @@ -44,7 +44,7 @@ describe Gitlab::Metrics::Transaction do transaction.increment(:time, 2) expect(transaction).to receive(:add_metric). - with('rspec', { duration: 0.0, time: 3 }, {}) + with('transactions', { duration: 0.0, time: 3 }, {}) transaction.track_self end @@ -70,7 +70,7 @@ describe Gitlab::Metrics::Transaction do describe '#track_self' do it 'adds a metric for the transaction itself' do expect(transaction).to receive(:add_metric). - with('rspec', { duration: transaction.duration }, {}) + with('transactions', { duration: transaction.duration }, {}) transaction.track_self end -- cgit v1.2.3 From 567dc62b6dd114ac129eb2f45baa8155f5f11a51 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 30 Dec 2015 12:25:42 -0200 Subject: Show 'All' tab by default in the builds page --- CHANGELOG | 2 + app/controllers/admin/builds_controller.rb | 6 +- app/controllers/projects/builds_controller.rb | 6 +- app/views/admin/builds/index.html.haml | 10 +-- app/views/projects/builds/index.html.haml | 12 +-- spec/features/admin/admin_builds_spec.rb | 119 ++++++++++++++++---------- spec/features/builds_spec.rb | 23 +++-- 7 files changed, 107 insertions(+), 71 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b25e0eabd7d..a0d5039905b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,8 @@ v 8.4.0 (unreleased) - Revert back upvote and downvote button to the issue and MR pages - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) - Fix version check image in Safari + - Enable "Add key" button when user fills in a proper key (Stan Hu) + - Show 'All' tab by default in the builds page v 8.3.3 (unreleased) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) diff --git a/app/controllers/admin/builds_controller.rb b/app/controllers/admin/builds_controller.rb index 83d9684c706..0db91eaaf2e 100644 --- a/app/controllers/admin/builds_controller.rb +++ b/app/controllers/admin/builds_controller.rb @@ -5,12 +5,12 @@ class Admin::BuildsController < Admin::ApplicationController @builds = @all_builds.order('created_at DESC') @builds = case @scope - when 'all' - @builds + when 'running' + @builds.running_or_pending.reverse_order when 'finished' @builds.finished else - @builds.running_or_pending.reverse_order + @builds end @builds = @builds.page(params[:page]).per(30) end diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 26ba12520c7..39d3ba26ba2 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -12,12 +12,12 @@ class Projects::BuildsController < Projects::ApplicationController @builds = @all_builds.order('created_at DESC') @builds = case @scope - when 'all' - @builds + when 'running' + @builds.running_or_pending.reverse_order when 'finished' @builds.finished else - @builds.running_or_pending.reverse_order + @builds end @builds = @builds.page(params[:page]).per(30) end diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 52c36af6225..ddd4e1481eb 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -7,6 +7,11 @@ %ul.center-top-menu %li{class: ('active' if @scope.nil?)} = link_to admin_builds_path do + All + %span.badge.js-totalbuilds-count= @all_builds.count(:id) + + %li{class: ('active' if @scope == 'running')} + = link_to admin_builds_path(scope: :running) do Running %span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id)) @@ -15,11 +20,6 @@ Finished %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id)) - %li{class: ('active' if @scope == 'all')} - = link_to admin_builds_path(scope: :all) do - All - %span.badge.js-totalbuilds-count= number_with_delimiter(@all_builds.count(:id)) - .gray-content-block #{(@scope || 'running').capitalize} builds diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 1a26908ab11..2fa5ad80fda 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -11,6 +11,12 @@ %ul.center-top-menu %li{class: ('active' if @scope.nil?)} = link_to project_builds_path(@project) do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_builds.count(:id)) + + %li{class: ('active' if @scope == 'running')} + = link_to project_builds_path(@project, scope: :running) do Running %span.badge.js-running-count = number_with_delimiter(@all_builds.running_or_pending.count(:id)) @@ -21,12 +27,6 @@ %span.badge.js-running-count = number_with_delimiter(@all_builds.finished.count(:id)) - %li{class: ('active' if @scope == 'all')} - = link_to project_builds_path(@project, scope: :all) do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_builds.count(:id)) - .gray-content-block #{(@scope || 'running').capitalize} builds from this project diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb index 72764b1629d..b955d0b0c46 100644 --- a/spec/features/admin/admin_builds_spec.rb +++ b/spec/features/admin/admin_builds_spec.rb @@ -1,69 +1,98 @@ require 'spec_helper' -describe "Admin Builds" do - let(:commit) { FactoryGirl.create :ci_commit } - let(:build) { FactoryGirl.create :ci_build, commit: commit } - +describe 'Admin Builds' do before do login_as :admin end - describe "GET /admin/builds" do - before do - build - visit admin_builds_path - end - - it { expect(page).to have_content "Running" } - it { expect(page).to have_content build.short_sha } - end + describe 'GET /admin/builds' do + let(:commit) { create(:ci_commit) } - describe "Tabs" do - it "shows all builds" do - FactoryGirl.create :ci_build, commit: commit, status: "pending" - FactoryGirl.create :ci_build, commit: commit, status: "running" - FactoryGirl.create :ci_build, commit: commit, status: "success" - FactoryGirl.create :ci_build, commit: commit, status: "failed" + context 'All tab' do + context 'when have builds' do + it 'shows all builds' do + create(:ci_build, commit: commit, status: :pending) + create(:ci_build, commit: commit, status: :running) + create(:ci_build, commit: commit, status: :success) + create(:ci_build, commit: commit, status: :failed) - visit admin_builds_path + visit admin_builds_path - within ".center-top-menu" do - click_on "All" + expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') + expect(page.all('.build-link').size).to eq(4) + expect(page).to have_link 'Cancel all' + end end - expect(page.all(".build-link").size).to eq(4) + context 'when have no builds' do + it 'shows a message' do + visit admin_builds_path + + expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') + expect(page).to have_content 'No builds to show' + expect(page).not_to have_link 'Cancel all' + end + end end - it "shows finished builds" do - build = FactoryGirl.create :ci_build, commit: commit, status: "pending" - build1 = FactoryGirl.create :ci_build, commit: commit, status: "running" - build2 = FactoryGirl.create :ci_build, commit: commit, status: "success" + context 'Running tab' do + context 'when have running builds' do + it 'shows running builds' do + build1 = create(:ci_build, commit: commit, status: :pending) + build2 = create(:ci_build, commit: commit, status: :success) + build3 = create(:ci_build, commit: commit, status: :failed) + + visit admin_builds_path(scope: :running) + + expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') + expect(page.find('.build-link')).to have_content(build1.id) + expect(page.find('.build-link')).not_to have_content(build2.id) + expect(page.find('.build-link')).not_to have_content(build3.id) + expect(page).to have_link 'Cancel all' + end + end - visit admin_builds_path + context 'when have no builds running' do + it 'shows a message' do + create(:ci_build, commit: commit, status: :success) - within ".center-top-menu" do - click_on "Finished" - end + visit admin_builds_path(scope: :running) - expect(page.find(".build-link")).not_to have_content(build.id) - expect(page.find(".build-link")).not_to have_content(build1.id) - expect(page.find(".build-link")).to have_content(build2.id) + expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') + expect(page).to have_content 'No builds to show' + expect(page).not_to have_link 'Cancel all' + end + end end - it "shows running builds" do - build = FactoryGirl.create :ci_build, commit: commit, status: "pending" - build2 = FactoryGirl.create :ci_build, commit: commit, status: "success" - build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed" + context 'Finished tab' do + context 'when have finished builds' do + it 'shows finished builds' do + build1 = create(:ci_build, commit: commit, status: :pending) + build2 = create(:ci_build, commit: commit, status: :running) + build3 = create(:ci_build, commit: commit, status: :success) + + visit admin_builds_path(scope: :finished) + + expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') + expect(page.find('.build-link')).not_to have_content(build1.id) + expect(page.find('.build-link')).not_to have_content(build2.id) + expect(page.find('.build-link')).to have_content(build3.id) + expect(page).to have_link 'Cancel all' + end + end - visit admin_builds_path + context 'when have no builds finished' do + it 'shows a message' do + create(:ci_build, commit: commit, status: :running) - within ".center-top-menu" do - click_on "Running" - end + visit admin_builds_path(scope: :finished) - expect(page.find(".build-link")).to have_content(build.id) - expect(page.find(".build-link")).not_to have_content(build2.id) - expect(page.find(".build-link")).not_to have_content(build3.id) + expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') + expect(page).to have_content 'No builds to show' + expect(page).to have_link 'Cancel all' + end + end end end end diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index f0031a0a247..240e56839df 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -15,11 +15,11 @@ describe "Builds" do context "Running scope" do before do @build.run! - visit namespace_project_builds_path(@project.namespace, @project) + visit namespace_project_builds_path(@project.namespace, @project, scope: :running) end - it { expect(page).to have_content 'Running' } - it { expect(page).to have_content 'Cancel running' } + it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') } + it { expect(page).to have_link 'Cancel running' } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.name } @@ -31,21 +31,22 @@ describe "Builds" do visit namespace_project_builds_path(@project.namespace, @project, scope: :finished) end + it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') } it { expect(page).to have_content 'No builds to show' } - it { expect(page).to have_content 'Cancel running' } + it { expect(page).to have_link 'Cancel running' } end context "All builds" do before do @project.builds.running_or_pending.each(&:success) - visit namespace_project_builds_path(@project.namespace, @project, scope: :all) + visit namespace_project_builds_path(@project.namespace, @project) end - it { expect(page).to have_content 'All' } + it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.name } - it { expect(page).to_not have_content 'Cancel running' } + it { expect(page).to_not have_link 'Cancel running' } end end @@ -56,8 +57,12 @@ describe "Builds" do click_link "Cancel running" end - it { expect(page).to have_content 'No builds to show' } - it { expect(page).to_not have_content 'Cancel running' } + it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') } + it { expect(page).to have_content 'canceled' } + it { expect(page).to have_content @build.short_sha } + it { expect(page).to have_content @build.ref } + it { expect(page).to have_content @build.name } + it { expect(page).to_not have_link 'Cancel running' } end describe "GET /:project/builds/:id" do -- cgit v1.2.3 From 8de491a68fcb130d436d2c85c0fda900381875cf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 4 Jan 2016 14:21:39 +0100 Subject: Fix Rubocop styling in AR subscriber specs --- spec/lib/gitlab/metrics/subscribers/active_record_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb index 81f35ba5d40..7bc070a4d09 100644 --- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do let(:event) do double(:event, duration: 0.2, - payload: { sql: 'SELECT * FROM users WHERE id = 10' }) + payload: { sql: 'SELECT * FROM users WHERE id = 10' }) end describe '#sql' do -- cgit v1.2.3 From 4b027bc93a7875c3937f6b90ac1049b4a4d72da5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jan 2016 14:29:06 +0100 Subject: Add DEBUG_BANZAI_CACHE env var to debug Banzai cache issue. --- lib/banzai/renderer.rb | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index 115ae914524..910e1c6994e 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -1,7 +1,5 @@ module Banzai module Renderer - CACHE_ENABLED = false - # Convert a Markdown String into an HTML-safe String of HTML # # Note that while the returned HTML will have been sanitized of dangerous @@ -20,13 +18,22 @@ module Banzai cache_key = context.delete(:cache_key) cache_key = full_cache_key(cache_key, context[:pipeline]) - if cache_key && CACHE_ENABLED - Rails.cache.fetch(cache_key) do - cacheless_render(text, context) + cacheless = cacheless_render(text, context) + + if cache_key && ENV["DEBUG_BANZAI_CACHE"] + cached = Rails.cache.fetch(cache_key) { cacheless } + + if cached != cacheless + Rails.logger.warn "Banzai cache mismatch" + Rails.logger.warn "Text: #{text.inspect}" + Rails.logger.warn "Context: #{context.inspect}" + Rails.logger.warn "Cache key: #{cache_key.inspect}" + Rails.logger.warn "Cacheless: #{cacheless.inspect}" + Rails.logger.warn "With cache: #{cached.inspect}" end - else - cacheless_render(text, context) end + + cacheless end def self.render_result(text, context = {}) -- cgit v1.2.3 From 79ec7f289748ca5812d51c3a61e3a2f9c2464fda Mon Sep 17 00:00:00 2001 From: Steve Norman Date: Tue, 5 May 2015 15:13:11 +0000 Subject: Added system hooks messages for renaming and transferring a project --- CHANGELOG | 1 + app/models/project.rb | 6 ++++ app/services/projects/transfer_service.rb | 3 ++ app/services/system_hooks_service.rb | 11 ++++++- doc/system_hooks/system_hooks.md | 50 +++++++++++++++++++++++++++++- spec/services/system_hooks_service_spec.rb | 47 +++++++++++++++++++--------- 6 files changed, 102 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b25e0eabd7d..d0e065ac6c2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.4.0 (unreleased) - Add link to merge request on build detail page - Revert back upvote and downvote button to the issue and MR pages - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) + - Add system hook messages for project rename and transfer (Steve Norman) - Fix version check image in Safari v 8.3.3 (unreleased) diff --git a/app/models/project.rb b/app/models/project.rb index 017471995ec..eadc42d1da5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -81,6 +81,7 @@ class Project < ActiveRecord::Base acts_as_taggable_on :tags attr_accessor :new_default_branch + attr_accessor :old_path_with_namespace # Relations belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' @@ -701,6 +702,11 @@ class Project < ActiveRecord::Base gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") send_move_instructions(old_path_with_namespace) reset_events_cache + + @old_path_with_namespace = old_path_with_namespace + + SystemHooksService.new.execute_hooks_for(self, :rename) + @repository = nil rescue # Returning false does not rollback after_* transaction but gives diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 64ea6dd42eb..2e734654466 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -55,6 +55,9 @@ module Projects # Move uploads Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) + project.old_path_with_namespace = old_path + + SystemHooksService.new.execute_hooks_for(project, :transfer) true end end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 8b5143e1eb7..6dc854ec33d 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -18,7 +18,8 @@ class SystemHooksService def build_event_data(model, event) data = { event_name: build_event_name(model, event), - created_at: model.created_at.xmlschema + created_at: model.created_at.xmlschema, + updated_at: model.updated_at.xmlschema } case model @@ -34,6 +35,14 @@ class SystemHooksService end when Project data.merge!(project_data(model)) + + if event == :rename || event == :transfer + data.merge!({ + old_path_with_namespace: model.old_path_with_namespace + }) + end + + data when User data.merge!({ name: model.name, diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index 5cb05b13b3e..49f98ded046 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -1,6 +1,6 @@ # System hooks -Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`. +Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`. System hooks can be used, e.g. for logging or changing information in a LDAP server. @@ -17,6 +17,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:30:54Z", + "updated_at": "2012-07-21T07:38:22Z", "event_name": "project_create", "name": "StoreCloud", "owner_email": "johnsmith@gmail.com", @@ -33,6 +34,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:30:58Z", + "updated_at": "2012-07-21T07:38:22Z", "event_name": "project_destroy", "name": "Underscore", "owner_email": "johnsmith@gmail.com", @@ -44,11 +46,48 @@ X-Gitlab-Event: System Hook } ``` +**Project renamed:** + +```json +{ + "created_at": "2012-07-21T07:30:58Z", + "updated_at": "2012-07-21T07:38:22Z", + "event_name": "project_rename", + "name": "Underscore", + "path": "underscore", + "path_with_namespace": "jsmith/underscore", + "project_id": 73, + "owner_name": "John Smith", + "owner_email": "johnsmith@gmail.com", + "project_visibility": "internal", + "old_path_with_namespace": "jsmith/overscore", +} +``` + +**Project transferred:** + +```json +{ + "created_at": "2012-07-21T07:30:58Z", + "updated_at": "2012-07-21T07:38:22Z", + "event_name": "project_transfer", + "name": "Underscore", + "path": "underscore", + "path_with_namespace": "scores/underscore", + "project_id": 73, + "owner_name": "John Smith", + "owner_email": "johnsmith@gmail.com", + "project_visibility": "internal", + "old_path_with_namespace": "jsmith/overscore", +} +``` + **New Team Member:** ```json { "created_at": "2012-07-21T07:30:56Z", + "updated_at": "2012-07-21T07:38:22Z", "event_name": "user_add_to_team", "project_access": "Master", "project_id": 74, @@ -67,6 +106,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:30:56Z", + "updated_at": "2012-07-21T07:38:22Z", "event_name": "user_remove_from_team", "project_access": "Master", "project_id": 74, @@ -85,6 +125,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:44:07Z", + "updated_at": "2012-07-21T07:38:22Z", "email": "js@gitlabhq.com", "event_name": "user_create", "name": "John Smith", @@ -97,6 +138,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:44:07Z", + "updated_at": "2012-07-21T07:38:22Z", "email": "js@gitlabhq.com", "event_name": "user_destroy", "name": "John Smith", @@ -110,6 +152,7 @@ X-Gitlab-Event: System Hook { "event_name": "key_create", "created_at": "2014-08-18 18:45:16 UTC", + "updated_at": "2012-07-21T07:38:22Z", "username": "root", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost", "id": 4 @@ -122,6 +165,7 @@ X-Gitlab-Event: System Hook { "event_name": "key_destroy", "created_at": "2014-08-18 18:45:16 UTC", + "updated_at": "2012-07-21T07:38:22Z", "username": "root", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost", "id": 4 @@ -133,6 +177,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:30:54Z", + "updated_at": "2012-07-21T07:38:22Z", "event_name": "group_create", "name": "StoreCloud", "owner_email": "johnsmith@gmail.com", @@ -147,6 +192,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:30:54Z", + "updated_at": "2012-07-21T07:38:22Z", "event_name": "group_destroy", "name": "StoreCloud", "owner_email": "johnsmith@gmail.com", @@ -161,6 +207,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:30:56Z", + "updated_at": "2012-07-21T07:38:22Z", "event_name": "user_add_to_group", "group_access": "Master", "group_id": 78, @@ -176,6 +223,7 @@ X-Gitlab-Event: System Hook ```json { "created_at": "2012-07-21T07:30:56Z", + "updated_at": "2012-07-21T07:38:22Z", "event_name": "user_remove_from_group", "group_access": "Master", "group_id": 78, diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index febc78d2784..4455ae7b321 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -9,37 +9,54 @@ describe SystemHooksService, services: true do let(:group_member) { create(:group_member) } context 'event data' do - it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :email, :user_id) } - 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_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(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id) } + it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id) } + it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } + it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } + it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_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, :updated_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) } + it do + project.old_path_with_namespace = 'renamed_from_path' + expect(event_data(project, :rename)).to include( + :event_name, :name, :created_at, :updated_at, :path, :project_id, + :owner_name, :owner_email, :project_visibility, + :old_path_with_namespace + ) + end + it do + project.old_path_with_namespace = 'transfered_from_path' + expect(event_data(project, :transfer)).to include( + :event_name, :name, :created_at, :updated_at, :path, :project_id, + :owner_name, :owner_email, :project_visibility, + :old_path_with_namespace + ) + end + it do expect(event_data(group, :create)).to include( - :event_name, :name, :created_at, :path, :group_id, :owner_name, - :owner_email + :event_name, :name, :created_at, :updated_at, :path, :group_id, + :owner_name, :owner_email ) end it do expect(event_data(group, :destroy)).to include( - :event_name, :name, :created_at, :path, :group_id, :owner_name, - :owner_email + :event_name, :name, :created_at, :updated_at, :path, :group_id, + :owner_name, :owner_email ) end it do expect(event_data(group_member, :create)).to include( - :event_name, :created_at, :group_name, :group_path, :group_id, :user_id, - :user_name, :user_email, :group_access + :event_name, :created_at, :updated_at, :group_name, :group_path, + :group_id, :user_id, :user_name, :user_email, :group_access ) end it do expect(event_data(group_member, :destroy)).to include( - :event_name, :created_at, :group_name, :group_path, :group_id, :user_id, - :user_name, :user_email, :group_access + :event_name, :created_at, :updated_at, :group_name, :group_path, + :group_id, :user_id, :user_name, :user_email, :group_access ) end end @@ -49,6 +66,8 @@ describe SystemHooksService, services: true do it { expect(event_name(user, :destroy)).to eq "user_destroy" } it { expect(event_name(project, :create)).to eq "project_create" } it { expect(event_name(project, :destroy)).to eq "project_destroy" } + it { expect(event_name(project, :rename)).to eq "project_rename" } + it { expect(event_name(project, :transfer)).to eq "project_transfer" } it { expect(event_name(project_member, :create)).to eq "user_add_to_team" } it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" } it { expect(event_name(key, :create)).to eq 'key_create' } -- cgit v1.2.3 From 8b1844912561a7e6dd0cc361ea1514f2a340e275 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 28 Dec 2015 13:32:18 +0200 Subject: remove public field from namespace and refactoring --- app/controllers/explore/groups_controller.rb | 2 +- app/controllers/users_controller.rb | 2 +- app/finders/groups_finder.rb | 44 ------------------- app/finders/joined_groups_finder.rb | 49 ---------------------- app/helpers/search_helper.rb | 2 +- app/models/ability.rb | 2 +- app/models/group.rb | 8 ---- app/views/groups/edit.html.haml | 9 ---- app/views/groups/show.html.haml | 4 +- .../20151228111122_remove_public_from_namespace.rb | 6 +++ db/schema.rb | 8 ++-- features/explore/groups.feature | 15 ------- spec/finders/groups_finder_spec.rb | 48 --------------------- spec/finders/joined_groups_finder_spec.rb | 49 ---------------------- spec/helpers/search_helper_spec.rb | 2 +- spec/models/group_spec.rb | 27 ------------ 16 files changed, 16 insertions(+), 261 deletions(-) delete mode 100644 app/finders/groups_finder.rb delete mode 100644 app/finders/joined_groups_finder.rb create mode 100644 db/migrate/20151228111122_remove_public_from_namespace.rb delete mode 100644 spec/finders/groups_finder_spec.rb delete mode 100644 spec/finders/joined_groups_finder_spec.rb diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index 9575a87ee41..a9bf4321f73 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,6 +1,6 @@ class Explore::GroupsController < Explore::ApplicationController def index - @groups = GroupsFinder.new.execute(current_user) + @groups = Group.order_id_desc @groups = @groups.search(params[:search]) if params[:search].present? @groups = @groups.sort(@sort = params[:sort]) @groups = @groups.page(params[:page]).per(PER_PAGE) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 30cb869eb2a..280228dbcc0 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -7,7 +7,7 @@ class UsersController < ApplicationController @projects = PersonalProjectsFinder.new(@user).execute(current_user) - @groups = JoinedGroupsFinder.new(@user).execute(current_user) + @groups = @user.groups.order_id_desc respond_to do |format| format.html diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb deleted file mode 100644 index 91cb0f228f0..00000000000 --- a/app/finders/groups_finder.rb +++ /dev/null @@ -1,44 +0,0 @@ -class GroupsFinder - # Finds the groups available to the given user. - # - # current_user - The user to find the groups for. - # - # Returns an ActiveRecord::Relation. - def execute(current_user = nil) - if current_user - relation = groups_visible_to_user(current_user) - else - relation = public_groups - end - - relation.order_id_desc - end - - private - - # This method returns the groups "current_user" can see. - def groups_visible_to_user(current_user) - base = groups_for_projects(public_and_internal_projects) - - union = Gitlab::SQL::Union. - new([base.select(:id), current_user.authorized_groups.select(:id)]) - - Group.where("namespaces.id IN (#{union.to_sql})") - end - - def public_groups - groups_for_projects(public_projects) - end - - def groups_for_projects(projects) - Group.public_and_given_groups(projects.select(:namespace_id)) - end - - def public_projects - Project.unscoped.public_only - end - - def public_and_internal_projects - Project.unscoped.public_and_internal_only - end -end diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb deleted file mode 100644 index e7523136fea..00000000000 --- a/app/finders/joined_groups_finder.rb +++ /dev/null @@ -1,49 +0,0 @@ -# Class for finding the groups a user is a member of. -class JoinedGroupsFinder - def initialize(user = nil) - @user = user - end - - # Finds the groups of the source user, optionally limited to those visible to - # the current user. - # - # current_user - If given the groups of "@user" will only include the groups - # "current_user" can also see. - # - # Returns an ActiveRecord::Relation. - def execute(current_user = nil) - if current_user - relation = groups_visible_to_user(current_user) - else - relation = public_groups - end - - relation.order_id_desc - end - - private - - # Returns the groups the user in "current_user" can see. - # - # This list includes all public/internal projects as well as the projects of - # "@user" that "current_user" also has access to. - def groups_visible_to_user(current_user) - base = @user.authorized_groups.visible_to_user(current_user) - extra = public_and_internal_groups - union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)]) - - Group.where("namespaces.id IN (#{union.to_sql})") - end - - def public_groups - groups_for_projects(@user.authorized_projects.public_only) - end - - def public_and_internal_groups - groups_for_projects(@user.authorized_projects.public_and_internal_only) - end - - def groups_for_projects(projects) - @user.groups.public_and_given_groups(projects.select(:namespace_id)) - end -end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index a6ee6880247..d4f78258626 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -70,7 +70,7 @@ module SearchHelper # Autocomplete results for the current user's groups def groups_autocomplete(term, limit = 5) - GroupsFinder.new.execute(current_user).search(term).limit(limit).map do |group| + Group.search(term).limit(limit).map do |group| { label: "group: #{search_result_sanitize(group.name)}", url: group_path(group) diff --git a/app/models/ability.rb b/app/models/ability.rb index 1b3ee757040..5a1a67db8e1 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -69,7 +69,7 @@ class Ability subject.group end - if group && group.public_profile? + if group && group.projects.public_only.any? [:read_group] else [] diff --git a/app/models/group.rb b/app/models/group.rb index 1b5b875a19e..b8f2ab6ae5d 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -50,10 +50,6 @@ class Group < Namespace User.reference_pattern end - def public_and_given_groups(ids) - where('public IS TRUE OR namespaces.id IN (?)', ids) - end - def visible_to_user(user) where(id: user.authorized_groups.select(:id).reorder(nil)) end @@ -125,10 +121,6 @@ class Group < Namespace end end - def public_profile? - self.public || projects.public_only.any? - end - def post_create_hook Gitlab::AppLogger.info("Group \"#{name}\" was created") diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 1dea77c2e96..7e3e2e28bc9 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -24,15 +24,6 @@ %hr = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" - .form-group - %hr - = f.label :public, class: 'control-label' do - Public - .col-sm-10 - .checkbox - = f.check_box :public - %span.descr Make this group public (even if there are no public projects inside this group) - .form-actions = f.submit 'Save group', class: "btn btn-save" diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index c2c7c581b3e..a607d860d7d 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -47,5 +47,5 @@ = render "projects", projects: @projects - else - %p - This group does not have public projects + %p.center-top-menu.no-top + No projects to show diff --git a/db/migrate/20151228111122_remove_public_from_namespace.rb b/db/migrate/20151228111122_remove_public_from_namespace.rb new file mode 100644 index 00000000000..f4c848bbf47 --- /dev/null +++ b/db/migrate/20151228111122_remove_public_from_namespace.rb @@ -0,0 +1,6 @@ +# Migration type: online +class RemovePublicFromNamespace < ActiveRecord::Migration + def change + remove_column :namespaces, :public, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index df7f72d5ad4..7a6d34b8153 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -547,22 +547,20 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false + t.string "name", null: false + t.string "path", null: false t.integer "owner_id" t.datetime "created_at" t.datetime "updated_at" t.string "type" - t.string "description", default: "", null: false + t.string "description", default: "", null: false t.string "avatar" - t.boolean "public", default: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree - add_index "namespaces", ["public"], name: "index_namespaces_on_public", using: :btree add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: :cascade do |t| diff --git a/features/explore/groups.feature b/features/explore/groups.feature index a42e59c98f2..5fc9b135601 100644 --- a/features/explore/groups.feature +++ b/features/explore/groups.feature @@ -105,15 +105,6 @@ Feature: Explore Groups When I visit the public groups area Then I should see group "TestGroup" - Scenario: I should not see group with internal project in public groups area - Given group "TestGroup" has internal project "Internal" - When I visit the public groups area - Then I should not see group "TestGroup" - - Scenario: I should not see group with private project in public groups area - When I visit the public groups area - Then I should not see group "TestGroup" - Scenario: I should see group with public project in public groups area as user Given group "TestGroup" has public project "Community" When I sign in as a user @@ -125,9 +116,3 @@ Feature: Explore Groups When I sign in as a user And I visit the public groups area Then I should see group "TestGroup" - - Scenario: I should not see group with private project in public groups area as user - When I sign in as a user - And I visit the public groups area - Then I should not see group "TestGroup" - diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb deleted file mode 100644 index 4f6a000822e..00000000000 --- a/spec/finders/groups_finder_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe GroupsFinder do - describe '#execute' do - let(:user) { create(:user) } - - let(:group1) { create(:group) } - let(:group2) { create(:group) } - let(:group3) { create(:group) } - let(:group4) { create(:group, public: true) } - - let!(:public_project) { create(:project, :public, group: group1) } - let!(:internal_project) { create(:project, :internal, group: group2) } - let!(:private_project) { create(:project, :private, group: group3) } - - let(:finder) { described_class.new } - - describe 'with a user' do - subject { finder.execute(user) } - - describe 'when the user is not a member of any groups' do - it { is_expected.to eq([group4, group2, group1]) } - end - - describe 'when the user is a member of a group' do - before do - group3.add_user(user, Gitlab::Access::DEVELOPER) - end - - it { is_expected.to eq([group4, group3, group2, group1]) } - end - - describe 'when the user is a member of a private project' do - before do - private_project.team.add_user(user, Gitlab::Access::DEVELOPER) - end - - it { is_expected.to eq([group4, group3, group2, group1]) } - end - end - - describe 'without a user' do - subject { finder.execute } - - it { is_expected.to eq([group4, group1]) } - end - end -end diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb deleted file mode 100644 index 2d9068cc720..00000000000 --- a/spec/finders/joined_groups_finder_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'spec_helper' - -describe JoinedGroupsFinder do - describe '#execute' do - let(:source_user) { create(:user) } - let(:current_user) { create(:user) } - - let(:group1) { create(:group) } - let(:group2) { create(:group) } - let(:group3) { create(:group) } - let(:group4) { create(:group, public: true) } - - let!(:public_project) { create(:project, :public, group: group1) } - let!(:internal_project) { create(:project, :internal, group: group2) } - let!(:private_project) { create(:project, :private, group: group3) } - - let(:finder) { described_class.new(source_user) } - - before do - [group1, group2, group3, group4].each do |group| - group.add_user(source_user, Gitlab::Access::MASTER) - end - end - - describe 'with a current user' do - describe 'when the current user has access to the projects of the source user' do - before do - private_project.team.add_user(current_user, Gitlab::Access::DEVELOPER) - end - - subject { finder.execute(current_user) } - - it { is_expected.to eq([group4, group3, group2, group1]) } - end - - describe 'when the current user does not have access to the projects of the source user' do - subject { finder.execute(current_user) } - - it { is_expected.to eq([group4, group2, group1]) } - end - end - - describe 'without a current user' do - subject { finder.execute } - - it { is_expected.to eq([group4, group1]) } - end - end -end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index ebe9c29d91c..f0d553f5f1d 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -43,7 +43,7 @@ describe SearchHelper do end it "includes the public group" do - group = create(:group, public: true) + group = create(:group) expect(search_autocomplete_opts(group.name).size).to eq(1) end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 646f767e6fe..ba5acceadff 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -38,14 +38,6 @@ describe Group, models: true do it { is_expected.not_to validate_presence_of :owner } end - describe '.public_and_given_groups' do - let!(:public_group) { create(:group, public: true) } - - subject { described_class.public_and_given_groups([group.id]) } - - it { is_expected.to eq([public_group, group]) } - end - describe '.visible_to_user' do let!(:group) { create(:group) } let!(:user) { create(:user) } @@ -112,23 +104,4 @@ describe Group, models: true do expect(group.avatar_type).to eq(["only images allowed"]) end end - - describe "public_profile?" do - it "returns true for public group" do - group = create(:group, public: true) - expect(group.public_profile?).to be_truthy - end - - it "returns true for non-public group with public project" do - group = create(:group) - create(:project, :public, group: group) - expect(group.public_profile?).to be_truthy - end - - it "returns false for non-public group with no public projects" do - group = create(:group) - create(:project, group: group) - expect(group.public_profile?).to be_falsy - end - end end -- cgit v1.2.3 From 712af98e9c11b28a11fb31fc86af5ef83a5485cb Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 4 Jan 2016 09:07:50 -0500 Subject: removes footer message about access to project --- app/views/projects/show.html.haml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 5d04776f7f4..ffbe445b447 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -68,11 +68,4 @@ = render 'projects/last_commit', commit: @repository.commit, project: @project %div{class: "project-show-#{default_project_view}"} - = render default_project_view - -- if current_user - - access = user_max_access_in_project(current_user.id, @project) - - if access - .prepend-top-20.project-footer - .gray-content-block.footer-block.center - You have #{access} access to this project. \ No newline at end of file + = render default_project_view \ No newline at end of file -- cgit v1.2.3 From 770517d3e7f9926957b384084ab726651bfecaea Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 4 Jan 2016 09:42:10 -0500 Subject: Revert "Merge branch 'rs-remove-jquery-blockui' into 'master' " This reverts commit bc12750fcc7a8637771e1449493cdd3cee9ebd64, reversing changes made to 8a04b84e09c4318ac46808d0debc4997b52a2314. --- app/assets/javascripts/application.js.coffee | 1 + vendor/assets/javascripts/jquery.blockUI.js | 590 +++++++++++++++++++++++++++ 2 files changed, 591 insertions(+) create mode 100644 vendor/assets/javascripts/jquery.blockUI.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index b9b095e004a..7d3f18fcdbe 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -13,6 +13,7 @@ #= require jquery.waitforimages #= require jquery.atwho #= require jquery.scrollTo +#= require jquery.blockUI #= require jquery.turbolinks #= require d3 #= require cal-heatmap diff --git a/vendor/assets/javascripts/jquery.blockUI.js b/vendor/assets/javascripts/jquery.blockUI.js new file mode 100644 index 00000000000..c8702d79b65 --- /dev/null +++ b/vendor/assets/javascripts/jquery.blockUI.js @@ -0,0 +1,590 @@ +/*! + * jQuery blockUI plugin + * Version 2.60.0-2013.04.05 + * @requires jQuery v1.7 or later + * + * Examples at: http://malsup.com/jquery/block/ + * Copyright (c) 2007-2013 M. Alsup + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Thanks to Amir-Hossein Sobhi for some excellent contributions! + */ + +;(function() { +/*jshint eqeqeq:false curly:false latedef:false */ +"use strict"; + + function setup($) { + $.fn._fadeIn = $.fn.fadeIn; + + var noOp = $.noop || function() {}; + + // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle + // retarded userAgent strings on Vista) + var msie = /MSIE/.test(navigator.userAgent); + var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent); + var mode = document.documentMode || 0; + var setExpr = $.isFunction( document.createElement('div').style.setExpression ); + + // global $ methods for blocking/unblocking the entire page + $.blockUI = function(opts) { install(window, opts); }; + $.unblockUI = function(opts) { remove(window, opts); }; + + // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl) + $.growlUI = function(title, message, timeout, onClose) { + var $m = $('
      '); + if (title) $m.append('

      '+title+'

      '); + if (message) $m.append('

      '+message+'

      '); + if (timeout === undefined) timeout = 3000; + $.blockUI({ + message: $m, fadeIn: 700, fadeOut: 1000, centerY: false, + timeout: timeout, showOverlay: false, + onUnblock: onClose, + css: $.blockUI.defaults.growlCSS + }); + }; + + // plugin method for blocking element content + $.fn.block = function(opts) { + if ( this[0] === window ) { + $.blockUI( opts ); + return this; + } + var fullOpts = $.extend({}, $.blockUI.defaults, opts || {}); + this.each(function() { + var $el = $(this); + if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked')) + return; + $el.unblock({ fadeOut: 0 }); + }); + + return this.each(function() { + if ($.css(this,'position') == 'static') { + this.style.position = 'relative'; + $(this).data('blockUI.static', true); + } + this.style.zoom = 1; // force 'hasLayout' in ie + install(this, opts); + }); + }; + + // plugin method for unblocking element content + $.fn.unblock = function(opts) { + if ( this[0] === window ) { + $.unblockUI( opts ); + return this; + } + return this.each(function() { + remove(this, opts); + }); + }; + + $.blockUI.version = 2.60; // 2nd generation blocking at no extra cost! + + // override these in your code to change the default behavior and style + $.blockUI.defaults = { + // message displayed when blocking (use null for no message) + message: '

      Please wait...

      ', + + title: null, // title string; only used when theme == true + draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) + + theme: false, // set to true to use with jQuery UI themes + + // styles for the message when blocking; if you wish to disable + // these and use an external stylesheet then do this in your code: + // $.blockUI.defaults.css = {}; + css: { + padding: 0, + margin: 0, + width: '30%', + top: '40%', + left: '35%', + textAlign: 'center', + color: '#000', + border: '3px solid #aaa', + backgroundColor:'#fff', + cursor: 'wait' + }, + + // minimal style set used when themes are used + themedCSS: { + width: '30%', + top: '40%', + left: '35%' + }, + + // styles for the overlay + overlayCSS: { + backgroundColor: '#000', + opacity: 0.6, + cursor: 'wait' + }, + + // style to replace wait cursor before unblocking to correct issue + // of lingering wait cursor + cursorReset: 'default', + + // styles applied when using $.growlUI + growlCSS: { + width: '350px', + top: '10px', + left: '', + right: '10px', + border: 'none', + padding: '5px', + opacity: 0.6, + cursor: 'default', + color: '#fff', + backgroundColor: '#000', + '-webkit-border-radius':'10px', + '-moz-border-radius': '10px', + 'border-radius': '10px' + }, + + // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w + // (hat tip to Jorge H. N. de Vasconcelos) + /*jshint scripturl:true */ + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', + + // force usage of iframe in non-IE browsers (handy for blocking applets) + forceIframe: false, + + // z-index for the blocking overlay + baseZ: 1000, + + // set these to true to have the message automatically centered + centerX: true, // <-- only effects element blocking (page block controlled via css above) + centerY: true, + + // allow body element to be stetched in ie6; this makes blocking look better + // on "short" pages. disable if you wish to prevent changes to the body height + allowBodyStretch: true, + + // enable if you want key and mouse events to be disabled for content that is blocked + bindEvents: true, + + // be default blockUI will supress tab navigation from leaving blocking content + // (if bindEvents is true) + constrainTabKey: true, + + // fadeIn time in millis; set to 0 to disable fadeIn on block + fadeIn: 200, + + // fadeOut time in millis; set to 0 to disable fadeOut on unblock + fadeOut: 400, + + // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock + timeout: 0, + + // disable if you don't want to show the overlay + showOverlay: true, + + // if true, focus will be placed in the first available input field when + // page blocking + focusInput: true, + + // elements that can receive focus + focusableElements: ':input:enabled:visible', + + // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) + // no longer needed in 2012 + // applyPlatformOpacityRules: true, + + // callback method invoked when fadeIn has completed and blocking message is visible + onBlock: null, + + // callback method invoked when unblocking has completed; the callback is + // passed the element that has been unblocked (which is the window object for page + // blocks) and the options that were passed to the unblock call: + // onUnblock(element, options) + onUnblock: null, + + // callback method invoked when the overlay area is clicked. + // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used. + onOverlayClick: null, + + // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 + quirksmodeOffsetHack: 4, + + // class name of the message block + blockMsgClass: 'blockMsg', + + // if it is already blocked, then ignore it (don't unblock and reblock) + ignoreIfBlocked: false + }; + + // private data and functions follow... + + var pageBlock = null; + var pageBlockEls = []; + + function install(el, opts) { + var css, themedCSS; + var full = (el == window); + var msg = (opts && opts.message !== undefined ? opts.message : undefined); + opts = $.extend({}, $.blockUI.defaults, opts || {}); + + if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked')) + return; + + opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); + css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); + if (opts.onOverlayClick) + opts.overlayCSS.cursor = 'pointer'; + + themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); + msg = msg === undefined ? opts.message : msg; + + // remove the current block (if there is one) + if (full && pageBlock) + remove(window, {fadeOut:0}); + + // if an existing element is being used as the blocking content then we capture + // its current place in the DOM (and current display style) so we can restore + // it when we unblock + if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { + var node = msg.jquery ? msg[0] : msg; + var data = {}; + $(el).data('blockUI.history', data); + data.el = node; + data.parent = node.parentNode; + data.display = node.style.display; + data.position = node.style.position; + if (data.parent) + data.parent.removeChild(node); + } + + $(el).data('blockUI.onUnblock', opts.onUnblock); + var z = opts.baseZ; + + // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; + // layer1 is the iframe layer which is used to supress bleed through of underlying content + // layer2 is the overlay layer which has opacity and a wait cursor (by default) + // layer3 is the message content that is displayed while blocking + var lyr1, lyr2, lyr3, s; + if (msie || opts.forceIframe) + lyr1 = $(''); + else + lyr1 = $(''); + + if (opts.theme) + lyr2 = $(''); + else + lyr2 = $(''); + + if (opts.theme && full) { + s = ''; + } + else if (opts.theme) { + s = ''; + } + else if (full) { + s = ''; + } + else { + s = ''; + } + lyr3 = $(s); + + // if we have a message, style it + if (msg) { + if (opts.theme) { + lyr3.css(themedCSS); + lyr3.addClass('ui-widget-content'); + } + else + lyr3.css(css); + } + + // style the overlay + if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/) + lyr2.css(opts.overlayCSS); + lyr2.css('position', full ? 'fixed' : 'absolute'); + + // make iframe layer transparent in IE + if (msie || opts.forceIframe) + lyr1.css('opacity',0.0); + + //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); + var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); + $.each(layers, function() { + this.appendTo($par); + }); + + if (opts.theme && opts.draggable && $.fn.draggable) { + lyr3.draggable({ + handle: '.ui-dialog-titlebar', + cancel: 'li' + }); + } + + // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) + var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0); + if (ie6 || expr) { + // give body 100% height + if (full && opts.allowBodyStretch && $.support.boxModel) + $('html,body').css('height','100%'); + + // fix ie6 issue when blocked element has a border width + if ((ie6 || !$.support.boxModel) && !full) { + var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); + var fixT = t ? '(0 - '+t+')' : 0; + var fixL = l ? '(0 - '+l+')' : 0; + } + + // simulate fixed position + $.each(layers, function(i,o) { + var s = o[0].style; + s.position = 'absolute'; + if (i < 2) { + if (full) + s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"'); + else + s.setExpression('height','this.parentNode.offsetHeight + "px"'); + if (full) + s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'); + else + s.setExpression('width','this.parentNode.offsetWidth + "px"'); + if (fixL) s.setExpression('left', fixL); + if (fixT) s.setExpression('top', fixT); + } + else if (opts.centerY) { + if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); + s.marginTop = 0; + } + else if (!opts.centerY && full) { + var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0; + var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; + s.setExpression('top',expression); + } + }); + } + + // show the message + if (msg) { + if (opts.theme) + lyr3.find('.ui-widget-content').append(msg); + else + lyr3.append(msg); + if (msg.jquery || msg.nodeType) + $(msg).show(); + } + + if ((msie || opts.forceIframe) && opts.showOverlay) + lyr1.show(); // opacity is zero + if (opts.fadeIn) { + var cb = opts.onBlock ? opts.onBlock : noOp; + var cb1 = (opts.showOverlay && !msg) ? cb : noOp; + var cb2 = msg ? cb : noOp; + if (opts.showOverlay) + lyr2._fadeIn(opts.fadeIn, cb1); + if (msg) + lyr3._fadeIn(opts.fadeIn, cb2); + } + else { + if (opts.showOverlay) + lyr2.show(); + if (msg) + lyr3.show(); + if (opts.onBlock) + opts.onBlock(); + } + + // bind key and mouse events + bind(1, el, opts); + + if (full) { + pageBlock = lyr3[0]; + pageBlockEls = $(opts.focusableElements,pageBlock); + if (opts.focusInput) + setTimeout(focus, 20); + } + else + center(lyr3[0], opts.centerX, opts.centerY); + + if (opts.timeout) { + // auto-unblock + var to = setTimeout(function() { + if (full) + $.unblockUI(opts); + else + $(el).unblock(opts); + }, opts.timeout); + $(el).data('blockUI.timeout', to); + } + } + + // remove the block + function remove(el, opts) { + var count; + var full = (el == window); + var $el = $(el); + var data = $el.data('blockUI.history'); + var to = $el.data('blockUI.timeout'); + if (to) { + clearTimeout(to); + $el.removeData('blockUI.timeout'); + } + opts = $.extend({}, $.blockUI.defaults, opts || {}); + bind(0, el, opts); // unbind events + + if (opts.onUnblock === null) { + opts.onUnblock = $el.data('blockUI.onUnblock'); + $el.removeData('blockUI.onUnblock'); + } + + var els; + if (full) // crazy selector to handle odd field errors in ie6/7 + els = $('body').children().filter('.blockUI').add('body > .blockUI'); + else + els = $el.find('>.blockUI'); + + // fix cursor issue + if ( opts.cursorReset ) { + if ( els.length > 1 ) + els[1].style.cursor = opts.cursorReset; + if ( els.length > 2 ) + els[2].style.cursor = opts.cursorReset; + } + + if (full) + pageBlock = pageBlockEls = null; + + if (opts.fadeOut) { + count = els.length; + els.fadeOut(opts.fadeOut, function() { + if ( --count === 0) + reset(els,data,opts,el); + }); + } + else + reset(els, data, opts, el); + } + + // move blocking element back into the DOM where it started + function reset(els,data,opts,el) { + var $el = $(el); + els.each(function(i,o) { + // remove via DOM calls so we don't lose event handlers + if (this.parentNode) + this.parentNode.removeChild(this); + }); + + if (data && data.el) { + data.el.style.display = data.display; + data.el.style.position = data.position; + if (data.parent) + data.parent.appendChild(data.el); + $el.removeData('blockUI.history'); + } + + if ($el.data('blockUI.static')) { + $el.css('position', 'static'); // #22 + } + + if (typeof opts.onUnblock == 'function') + opts.onUnblock(el,opts); + + // fix issue in Safari 6 where block artifacts remain until reflow + var body = $(document.body), w = body.width(), cssW = body[0].style.width; + body.width(w-1).width(w); + body[0].style.width = cssW; + } + + // bind/unbind the handler + function bind(b, el, opts) { + var full = el == window, $el = $(el); + + // don't bother unbinding if there is nothing to unbind + if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) + return; + + $el.data('blockUI.isBlocked', b); + + // don't bind events when overlay is not in use or if bindEvents is false + if (!full || !opts.bindEvents || (b && !opts.showOverlay)) + return; + + // bind anchors and inputs for mouse and key events + var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove'; + if (b) + $(document).bind(events, opts, handler); + else + $(document).unbind(events, handler); + + // former impl... + // var $e = $('a,:input'); + // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); + } + + // event handler to suppress keyboard/mouse events when blocking + function handler(e) { + // allow tab navigation (conditionally) + if (e.keyCode && e.keyCode == 9) { + if (pageBlock && e.data.constrainTabKey) { + var els = pageBlockEls; + var fwd = !e.shiftKey && e.target === els[els.length-1]; + var back = e.shiftKey && e.target === els[0]; + if (fwd || back) { + setTimeout(function(){focus(back);},10); + return false; + } + } + } + var opts = e.data; + var target = $(e.target); + if (target.hasClass('blockOverlay') && opts.onOverlayClick) + opts.onOverlayClick(); + + // allow events within the message content + if (target.parents('div.' + opts.blockMsgClass).length > 0) + return true; + + // allow events for content that is not being blocked + return target.parents().children().filter('div.blockUI').length === 0; + } + + function focus(back) { + if (!pageBlockEls) + return; + var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; + if (e) + e.focus(); + } + + function center(el, x, y) { + var p = el.parentNode, s = el.style; + var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); + var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); + if (x) s.left = l > 0 ? (l+'px') : '0'; + if (y) s.top = t > 0 ? (t+'px') : '0'; + } + + function sz(el, p) { + return parseInt($.css(el,p),10)||0; + } + + } + + + /*global define:true */ + if (typeof define === 'function' && define.amd && define.amd.jQuery) { + define(['jquery'], setup); + } else { + setup(jQuery); + } + +})(); -- cgit v1.2.3 From 618056031827727ff1928b125569dae2e05f9bd1 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 4 Jan 2016 10:36:53 -0500 Subject: rempves tests for "you have master access" text --- spec/features/projects_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 4836b3b9b14..9a01c89ae2a 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -80,7 +80,6 @@ feature 'Project', feature: true do visit namespace_project_path(project.namespace, project) end - it { expect(page).to have_content('You have Master access to this project.') } it 'click project-settings and find leave project' do find('#project-settings-button').click expect(page).to have_link('Leave Project') -- cgit v1.2.3 From ad42441d2dc98dbf385e54c352a6a5a38155b223 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 1 Jan 2016 17:52:40 -0500 Subject: Remove jquery.blockUI.js plugin It was only used to block the issue (but not merge request) list when the sort was changed. --- app/assets/javascripts/application.js.coffee | 1 - app/assets/javascripts/issues.js.coffee | 7 - vendor/assets/javascripts/jquery.blockUI.js | 590 --------------------------- 3 files changed, 598 deletions(-) delete mode 100644 vendor/assets/javascripts/jquery.blockUI.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 7d3f18fcdbe..b9b095e004a 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -13,7 +13,6 @@ #= require jquery.waitforimages #= require jquery.atwho #= require jquery.scrollTo -#= require jquery.blockUI #= require jquery.turbolinks #= require d3 #= require cal-heatmap diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 35d34b20fae..a0acf3028bf 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -15,13 +15,6 @@ $(this).html totalIssues + 1 else $(this).html totalIssues - 1 - $("body").on "click", ".issues-other-filters .dropdown-menu a", -> - $('.issues-list').block( - message: null, - overlayCSS: - backgroundColor: '#DDD' - opacity: .4 - ) reload: -> Issues.initSelects() diff --git a/vendor/assets/javascripts/jquery.blockUI.js b/vendor/assets/javascripts/jquery.blockUI.js deleted file mode 100644 index c8702d79b65..00000000000 --- a/vendor/assets/javascripts/jquery.blockUI.js +++ /dev/null @@ -1,590 +0,0 @@ -/*! - * jQuery blockUI plugin - * Version 2.60.0-2013.04.05 - * @requires jQuery v1.7 or later - * - * Examples at: http://malsup.com/jquery/block/ - * Copyright (c) 2007-2013 M. Alsup - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * Thanks to Amir-Hossein Sobhi for some excellent contributions! - */ - -;(function() { -/*jshint eqeqeq:false curly:false latedef:false */ -"use strict"; - - function setup($) { - $.fn._fadeIn = $.fn.fadeIn; - - var noOp = $.noop || function() {}; - - // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle - // retarded userAgent strings on Vista) - var msie = /MSIE/.test(navigator.userAgent); - var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent); - var mode = document.documentMode || 0; - var setExpr = $.isFunction( document.createElement('div').style.setExpression ); - - // global $ methods for blocking/unblocking the entire page - $.blockUI = function(opts) { install(window, opts); }; - $.unblockUI = function(opts) { remove(window, opts); }; - - // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl) - $.growlUI = function(title, message, timeout, onClose) { - var $m = $('
      '); - if (title) $m.append('

      '+title+'

      '); - if (message) $m.append('

      '+message+'

      '); - if (timeout === undefined) timeout = 3000; - $.blockUI({ - message: $m, fadeIn: 700, fadeOut: 1000, centerY: false, - timeout: timeout, showOverlay: false, - onUnblock: onClose, - css: $.blockUI.defaults.growlCSS - }); - }; - - // plugin method for blocking element content - $.fn.block = function(opts) { - if ( this[0] === window ) { - $.blockUI( opts ); - return this; - } - var fullOpts = $.extend({}, $.blockUI.defaults, opts || {}); - this.each(function() { - var $el = $(this); - if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked')) - return; - $el.unblock({ fadeOut: 0 }); - }); - - return this.each(function() { - if ($.css(this,'position') == 'static') { - this.style.position = 'relative'; - $(this).data('blockUI.static', true); - } - this.style.zoom = 1; // force 'hasLayout' in ie - install(this, opts); - }); - }; - - // plugin method for unblocking element content - $.fn.unblock = function(opts) { - if ( this[0] === window ) { - $.unblockUI( opts ); - return this; - } - return this.each(function() { - remove(this, opts); - }); - }; - - $.blockUI.version = 2.60; // 2nd generation blocking at no extra cost! - - // override these in your code to change the default behavior and style - $.blockUI.defaults = { - // message displayed when blocking (use null for no message) - message: '

      Please wait...

      ', - - title: null, // title string; only used when theme == true - draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) - - theme: false, // set to true to use with jQuery UI themes - - // styles for the message when blocking; if you wish to disable - // these and use an external stylesheet then do this in your code: - // $.blockUI.defaults.css = {}; - css: { - padding: 0, - margin: 0, - width: '30%', - top: '40%', - left: '35%', - textAlign: 'center', - color: '#000', - border: '3px solid #aaa', - backgroundColor:'#fff', - cursor: 'wait' - }, - - // minimal style set used when themes are used - themedCSS: { - width: '30%', - top: '40%', - left: '35%' - }, - - // styles for the overlay - overlayCSS: { - backgroundColor: '#000', - opacity: 0.6, - cursor: 'wait' - }, - - // style to replace wait cursor before unblocking to correct issue - // of lingering wait cursor - cursorReset: 'default', - - // styles applied when using $.growlUI - growlCSS: { - width: '350px', - top: '10px', - left: '', - right: '10px', - border: 'none', - padding: '5px', - opacity: 0.6, - cursor: 'default', - color: '#fff', - backgroundColor: '#000', - '-webkit-border-radius':'10px', - '-moz-border-radius': '10px', - 'border-radius': '10px' - }, - - // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w - // (hat tip to Jorge H. N. de Vasconcelos) - /*jshint scripturl:true */ - iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', - - // force usage of iframe in non-IE browsers (handy for blocking applets) - forceIframe: false, - - // z-index for the blocking overlay - baseZ: 1000, - - // set these to true to have the message automatically centered - centerX: true, // <-- only effects element blocking (page block controlled via css above) - centerY: true, - - // allow body element to be stetched in ie6; this makes blocking look better - // on "short" pages. disable if you wish to prevent changes to the body height - allowBodyStretch: true, - - // enable if you want key and mouse events to be disabled for content that is blocked - bindEvents: true, - - // be default blockUI will supress tab navigation from leaving blocking content - // (if bindEvents is true) - constrainTabKey: true, - - // fadeIn time in millis; set to 0 to disable fadeIn on block - fadeIn: 200, - - // fadeOut time in millis; set to 0 to disable fadeOut on unblock - fadeOut: 400, - - // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock - timeout: 0, - - // disable if you don't want to show the overlay - showOverlay: true, - - // if true, focus will be placed in the first available input field when - // page blocking - focusInput: true, - - // elements that can receive focus - focusableElements: ':input:enabled:visible', - - // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) - // no longer needed in 2012 - // applyPlatformOpacityRules: true, - - // callback method invoked when fadeIn has completed and blocking message is visible - onBlock: null, - - // callback method invoked when unblocking has completed; the callback is - // passed the element that has been unblocked (which is the window object for page - // blocks) and the options that were passed to the unblock call: - // onUnblock(element, options) - onUnblock: null, - - // callback method invoked when the overlay area is clicked. - // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used. - onOverlayClick: null, - - // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 - quirksmodeOffsetHack: 4, - - // class name of the message block - blockMsgClass: 'blockMsg', - - // if it is already blocked, then ignore it (don't unblock and reblock) - ignoreIfBlocked: false - }; - - // private data and functions follow... - - var pageBlock = null; - var pageBlockEls = []; - - function install(el, opts) { - var css, themedCSS; - var full = (el == window); - var msg = (opts && opts.message !== undefined ? opts.message : undefined); - opts = $.extend({}, $.blockUI.defaults, opts || {}); - - if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked')) - return; - - opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); - css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); - if (opts.onOverlayClick) - opts.overlayCSS.cursor = 'pointer'; - - themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); - msg = msg === undefined ? opts.message : msg; - - // remove the current block (if there is one) - if (full && pageBlock) - remove(window, {fadeOut:0}); - - // if an existing element is being used as the blocking content then we capture - // its current place in the DOM (and current display style) so we can restore - // it when we unblock - if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { - var node = msg.jquery ? msg[0] : msg; - var data = {}; - $(el).data('blockUI.history', data); - data.el = node; - data.parent = node.parentNode; - data.display = node.style.display; - data.position = node.style.position; - if (data.parent) - data.parent.removeChild(node); - } - - $(el).data('blockUI.onUnblock', opts.onUnblock); - var z = opts.baseZ; - - // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; - // layer1 is the iframe layer which is used to supress bleed through of underlying content - // layer2 is the overlay layer which has opacity and a wait cursor (by default) - // layer3 is the message content that is displayed while blocking - var lyr1, lyr2, lyr3, s; - if (msie || opts.forceIframe) - lyr1 = $(''); - else - lyr1 = $(''); - - if (opts.theme) - lyr2 = $(''); - else - lyr2 = $(''); - - if (opts.theme && full) { - s = ''; - } - else if (opts.theme) { - s = ''; - } - else if (full) { - s = ''; - } - else { - s = ''; - } - lyr3 = $(s); - - // if we have a message, style it - if (msg) { - if (opts.theme) { - lyr3.css(themedCSS); - lyr3.addClass('ui-widget-content'); - } - else - lyr3.css(css); - } - - // style the overlay - if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/) - lyr2.css(opts.overlayCSS); - lyr2.css('position', full ? 'fixed' : 'absolute'); - - // make iframe layer transparent in IE - if (msie || opts.forceIframe) - lyr1.css('opacity',0.0); - - //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); - var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); - $.each(layers, function() { - this.appendTo($par); - }); - - if (opts.theme && opts.draggable && $.fn.draggable) { - lyr3.draggable({ - handle: '.ui-dialog-titlebar', - cancel: 'li' - }); - } - - // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) - var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0); - if (ie6 || expr) { - // give body 100% height - if (full && opts.allowBodyStretch && $.support.boxModel) - $('html,body').css('height','100%'); - - // fix ie6 issue when blocked element has a border width - if ((ie6 || !$.support.boxModel) && !full) { - var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); - var fixT = t ? '(0 - '+t+')' : 0; - var fixL = l ? '(0 - '+l+')' : 0; - } - - // simulate fixed position - $.each(layers, function(i,o) { - var s = o[0].style; - s.position = 'absolute'; - if (i < 2) { - if (full) - s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"'); - else - s.setExpression('height','this.parentNode.offsetHeight + "px"'); - if (full) - s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'); - else - s.setExpression('width','this.parentNode.offsetWidth + "px"'); - if (fixL) s.setExpression('left', fixL); - if (fixT) s.setExpression('top', fixT); - } - else if (opts.centerY) { - if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); - s.marginTop = 0; - } - else if (!opts.centerY && full) { - var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0; - var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; - s.setExpression('top',expression); - } - }); - } - - // show the message - if (msg) { - if (opts.theme) - lyr3.find('.ui-widget-content').append(msg); - else - lyr3.append(msg); - if (msg.jquery || msg.nodeType) - $(msg).show(); - } - - if ((msie || opts.forceIframe) && opts.showOverlay) - lyr1.show(); // opacity is zero - if (opts.fadeIn) { - var cb = opts.onBlock ? opts.onBlock : noOp; - var cb1 = (opts.showOverlay && !msg) ? cb : noOp; - var cb2 = msg ? cb : noOp; - if (opts.showOverlay) - lyr2._fadeIn(opts.fadeIn, cb1); - if (msg) - lyr3._fadeIn(opts.fadeIn, cb2); - } - else { - if (opts.showOverlay) - lyr2.show(); - if (msg) - lyr3.show(); - if (opts.onBlock) - opts.onBlock(); - } - - // bind key and mouse events - bind(1, el, opts); - - if (full) { - pageBlock = lyr3[0]; - pageBlockEls = $(opts.focusableElements,pageBlock); - if (opts.focusInput) - setTimeout(focus, 20); - } - else - center(lyr3[0], opts.centerX, opts.centerY); - - if (opts.timeout) { - // auto-unblock - var to = setTimeout(function() { - if (full) - $.unblockUI(opts); - else - $(el).unblock(opts); - }, opts.timeout); - $(el).data('blockUI.timeout', to); - } - } - - // remove the block - function remove(el, opts) { - var count; - var full = (el == window); - var $el = $(el); - var data = $el.data('blockUI.history'); - var to = $el.data('blockUI.timeout'); - if (to) { - clearTimeout(to); - $el.removeData('blockUI.timeout'); - } - opts = $.extend({}, $.blockUI.defaults, opts || {}); - bind(0, el, opts); // unbind events - - if (opts.onUnblock === null) { - opts.onUnblock = $el.data('blockUI.onUnblock'); - $el.removeData('blockUI.onUnblock'); - } - - var els; - if (full) // crazy selector to handle odd field errors in ie6/7 - els = $('body').children().filter('.blockUI').add('body > .blockUI'); - else - els = $el.find('>.blockUI'); - - // fix cursor issue - if ( opts.cursorReset ) { - if ( els.length > 1 ) - els[1].style.cursor = opts.cursorReset; - if ( els.length > 2 ) - els[2].style.cursor = opts.cursorReset; - } - - if (full) - pageBlock = pageBlockEls = null; - - if (opts.fadeOut) { - count = els.length; - els.fadeOut(opts.fadeOut, function() { - if ( --count === 0) - reset(els,data,opts,el); - }); - } - else - reset(els, data, opts, el); - } - - // move blocking element back into the DOM where it started - function reset(els,data,opts,el) { - var $el = $(el); - els.each(function(i,o) { - // remove via DOM calls so we don't lose event handlers - if (this.parentNode) - this.parentNode.removeChild(this); - }); - - if (data && data.el) { - data.el.style.display = data.display; - data.el.style.position = data.position; - if (data.parent) - data.parent.appendChild(data.el); - $el.removeData('blockUI.history'); - } - - if ($el.data('blockUI.static')) { - $el.css('position', 'static'); // #22 - } - - if (typeof opts.onUnblock == 'function') - opts.onUnblock(el,opts); - - // fix issue in Safari 6 where block artifacts remain until reflow - var body = $(document.body), w = body.width(), cssW = body[0].style.width; - body.width(w-1).width(w); - body[0].style.width = cssW; - } - - // bind/unbind the handler - function bind(b, el, opts) { - var full = el == window, $el = $(el); - - // don't bother unbinding if there is nothing to unbind - if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) - return; - - $el.data('blockUI.isBlocked', b); - - // don't bind events when overlay is not in use or if bindEvents is false - if (!full || !opts.bindEvents || (b && !opts.showOverlay)) - return; - - // bind anchors and inputs for mouse and key events - var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove'; - if (b) - $(document).bind(events, opts, handler); - else - $(document).unbind(events, handler); - - // former impl... - // var $e = $('a,:input'); - // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); - } - - // event handler to suppress keyboard/mouse events when blocking - function handler(e) { - // allow tab navigation (conditionally) - if (e.keyCode && e.keyCode == 9) { - if (pageBlock && e.data.constrainTabKey) { - var els = pageBlockEls; - var fwd = !e.shiftKey && e.target === els[els.length-1]; - var back = e.shiftKey && e.target === els[0]; - if (fwd || back) { - setTimeout(function(){focus(back);},10); - return false; - } - } - } - var opts = e.data; - var target = $(e.target); - if (target.hasClass('blockOverlay') && opts.onOverlayClick) - opts.onOverlayClick(); - - // allow events within the message content - if (target.parents('div.' + opts.blockMsgClass).length > 0) - return true; - - // allow events for content that is not being blocked - return target.parents().children().filter('div.blockUI').length === 0; - } - - function focus(back) { - if (!pageBlockEls) - return; - var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; - if (e) - e.focus(); - } - - function center(el, x, y) { - var p = el.parentNode, s = el.style; - var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); - var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); - if (x) s.left = l > 0 ? (l+'px') : '0'; - if (y) s.top = t > 0 ? (t+'px') : '0'; - } - - function sz(el, p) { - return parseInt($.css(el,p),10)||0; - } - - } - - - /*global define:true */ - if (typeof define === 'function' && define.amd && define.amd.jQuery) { - define(['jquery'], setup); - } else { - setup(jQuery); - } - -})(); -- cgit v1.2.3 From 12ce1cbfcfff3e28cae82d327b056644457f65f6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jan 2016 12:11:13 +0100 Subject: Merge pull request GH-9938 from huacnlee/hotfix/note_mail_with_notification Hotfix note mail with notification --- app/mailers/emails/notes.rb | 2 +- spec/services/notification_service_spec.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 65f37e92677..e1382d2da12 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -48,7 +48,7 @@ module Emails yield - SentNotification.record(@note, recipient_id, reply_key) + SentNotification.record_note(@note, recipient_id, reply_key) end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index c103752198d..fc8cf425c3a 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -52,6 +52,9 @@ describe NotificationService, services: true do it do add_users_with_subscription(note.project, issue) + # Ensure create SentNotification by noteable = issue 6 times, not noteable = note + expect(SentNotification).to receive(:record).with(issue, any_args).exactly(6).times + ActionMailer::Base.deliveries.clear notification.new_note(note) -- cgit v1.2.3 From 10a9751ace57a368ebf39a1f8a3b172783cbeab1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Jan 2016 13:41:43 -0500 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a0d5039905b..ff82ba8e86f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,7 +14,6 @@ v 8.4.0 (unreleased) - Revert back upvote and downvote button to the issue and MR pages - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) - Fix version check image in Safari - - Enable "Add key" button when user fills in a proper key (Stan Hu) - Show 'All' tab by default in the builds page v 8.3.3 (unreleased) -- cgit v1.2.3 From 7df3c1e8eafcb071f23ac1f4142e81ce8c1d9def Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Jan 2016 14:54:56 -0500 Subject: Correct the logo ID names Her left, not ours! --- app/assets/javascripts/logo.js.coffee | 8 ++++---- app/views/shared/_logo.svg | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee index 47135a6c5eb..a8f97bdf3d7 100644 --- a/app/assets/javascripts/logo.js.coffee +++ b/app/assets/javascripts/logo.js.coffee @@ -2,11 +2,11 @@ NProgress.configure(showSpinner: false) defaultClass = 'tanuki-shape' pieces = [ - 'path#tanuki-left-cheek', - 'path#tanuki-left-eye, path#tanuki-left-ear', - 'path#tanuki-nose', - 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-right-cheek', + 'path#tanuki-right-eye, path#tanuki-right-ear', + 'path#tanuki-nose', + 'path#tanuki-left-eye, path#tanuki-left-ear', + 'path#tanuki-left-cheek', ] firstPiece = pieces[0] timeout = null diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg index 90f5f4e672b..3d279ec228c 100644 --- a/app/views/shared/_logo.svg +++ b/app/views/shared/_logo.svg @@ -5,13 +5,13 @@ - - - - - - - + + + + + + + -- cgit v1.2.3 From 9f46ca444354d4c6b52de3f23ce17c11f705d006 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Jan 2016 14:57:11 -0500 Subject: Decrease the logo sweep delay --- app/assets/javascripts/logo.js.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee index a8f97bdf3d7..b4dc993dead 100644 --- a/app/assets/javascripts/logo.js.coffee +++ b/app/assets/javascripts/logo.js.coffee @@ -1,5 +1,6 @@ NProgress.configure(showSpinner: false) +delay = 150 defaultClass = 'tanuki-shape' pieces = [ 'path#tanuki-right-cheek', @@ -12,7 +13,7 @@ firstPiece = pieces[0] timeout = null clearHighlights = -> - $(".#{defaultClass}").attr('class', defaultClass) + $(".#{defaultClass}.highlight").attr('class', defaultClass) start = -> clearHighlights() @@ -40,7 +41,7 @@ work = (pieceIndex) -> nextIndex = pieceIndex + 1 work(nextIndex) - , 200) + , delay) $(document).on 'page:fetch', start $(document).on 'page:change', stop -- cgit v1.2.3 From 567d87d90f2ba7195901ea24d240686c6030a4a7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Jan 2016 15:18:04 -0500 Subject: Restructure logo JS to use `setInterval` --- app/assets/javascripts/logo.js.coffee | 46 ++++++++++++++++------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee index b4dc993dead..e864a674cdd 100644 --- a/app/assets/javascripts/logo.js.coffee +++ b/app/assets/javascripts/logo.js.coffee @@ -1,6 +1,5 @@ NProgress.configure(showSpinner: false) -delay = 150 defaultClass = 'tanuki-shape' pieces = [ 'path#tanuki-right-cheek', @@ -9,39 +8,36 @@ pieces = [ 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek', ] +pieceIndex = 0 firstPiece = pieces[0] -timeout = null + +currentTimer = null +delay = 150 clearHighlights = -> $(".#{defaultClass}.highlight").attr('class', defaultClass) start = -> clearHighlights() + pieceIndex = 0 pieces.reverse() unless pieces[0] == firstPiece - work(0) + currentTimer = setInterval(work, delay) stop = -> - window.clearTimeout(timeout) + clearInterval(currentTimer) clearHighlights() -work = (pieceIndex) -> - # jQuery's addClass won't work on an SVG. Who knew! - $piece = $(pieces[pieceIndex]) - $piece.attr('class', "#{defaultClass} highlight") - - timeout = setTimeout(-> - $piece.attr('class', defaultClass) - - # If we hit the last piece, reset the index and then reverse the array to - # get a nice back-and-forth sweeping look - if pieceIndex + 1 >= pieces.length - nextIndex = 0 - pieces.reverse() - else - nextIndex = pieceIndex + 1 - - work(nextIndex) - , delay) - -$(document).on 'page:fetch', start -$(document).on 'page:change', stop +work = -> + clearHighlights() + $(pieces[pieceIndex]).attr('class', "#{defaultClass} highlight") + + # If we hit the last piece, reset the index and then reverse the array to + # get a nice back-and-forth sweeping look + if pieceIndex == pieces.length - 1 + pieceIndex = 0 + pieces.reverse() + else + pieceIndex++ + +$(document).on('page:fetch', start) +$(document).on('page:change', stop) -- cgit v1.2.3 From 93096247d86a88e84a0bff7c8dd8496179638a9d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 4 Jan 2016 15:00:21 -0800 Subject: Don't notify users twice if they are both project watchers and subscribers Closes #4708 --- CHANGELOG | 1 + app/services/notification_service.rb | 1 + spec/services/notification_service_spec.rb | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e4b35b281bb..e8eb5d568f5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.4.0 (unreleased) - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu) + - Don't notify users twice if they are both project watchers and subscribers (Stan Hu) - Implement new UI for group page - Implement search inside emoji picker - Add API support for looking up a user by username (Stan Hu) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index bdf7b3ad2bb..e4edc55bf69 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -413,6 +413,7 @@ class NotificationService recipients = reject_unsubscribed_users(recipients, target) recipients.delete(current_user) + recipients = recipients.uniq recipients end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index c103752198d..588ecc51382 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -61,6 +61,7 @@ describe NotificationService, services: true do should_email(note.noteable.assignee) should_email(@u_mentioned) should_email(@subscriber) + should_email(@watcher_and_subscriber) should_email(@subscribed_participant) should_not_email(note.author) should_not_email(@u_participating) @@ -245,6 +246,7 @@ describe NotificationService, services: true do should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) + should_email(@watcher_and_subscriber) should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) @@ -260,6 +262,7 @@ describe NotificationService, services: true do should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) + should_email(@watcher_and_subscriber) should_not_email(@unsubscriber) should_not_email(@u_participating) end @@ -282,6 +285,7 @@ describe NotificationService, services: true do should_email(merge_request.assignee) should_email(@u_watcher) + should_email(@watcher_and_subscriber) should_email(@u_participant_mentioned) should_not_email(@u_participating) should_not_email(@u_disabled) @@ -296,6 +300,7 @@ describe NotificationService, services: true do should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) + should_email(@watcher_and_subscriber) should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) @@ -310,6 +315,7 @@ describe NotificationService, services: true do should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) + should_email(@watcher_and_subscriber) should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) @@ -324,6 +330,7 @@ describe NotificationService, services: true do should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) + should_email(@watcher_and_subscriber) should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) @@ -338,6 +345,7 @@ describe NotificationService, services: true do should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) + should_email(@watcher_and_subscriber) should_not_email(@unsubscriber) should_not_email(@u_participating) should_not_email(@u_disabled) @@ -387,14 +395,18 @@ describe NotificationService, services: true do @subscriber = create :user @unsubscriber = create :user @subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING) + @watcher_and_subscriber = create(:user, notification_level: Notification::N_WATCH) project.team << [@subscribed_participant, :master] project.team << [@subscriber, :master] project.team << [@unsubscriber, :master] + project.team << [@watcher_and_subscriber, :master] issuable.subscriptions.create(user: @subscriber, subscribed: true) issuable.subscriptions.create(user: @subscribed_participant, subscribed: true) issuable.subscriptions.create(user: @unsubscriber, subscribed: false) + # Make the watcher a subscriber to detect dupes + issuable.subscriptions.create(user: @watcher_and_subscriber, subscribed: true) end def sent_to_user?(user) -- cgit v1.2.3 From b807a23b4b8fdf1deff120bc9c0bb762991cef3d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Jan 2016 18:35:05 -0500 Subject: Redesign the AbuseReports index - Shows when the reported user joined - Show relative timestamp for when the report was created - Parse message with restricted Markdown pipeline to autolink URLs --- app/views/admin/abuse_reports/_abuse_report.html.haml | 18 ++++++++++-------- app/views/admin/abuse_reports/index.html.haml | 3 +-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index d3afc658cd6..cf50a376e11 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -2,19 +2,21 @@ - user = abuse_report.user %tr %td - - if reporter - = link_to reporter.name, reporter + - if user + = link_to user.name, [:admin, user] + .light.small + Joined #{time_ago_with_tooltip(user.created_at)} - else (removed) %td - = abuse_report.created_at.to_s(:short) - %td - = abuse_report.message - %td - - if user - = link_to user.name, user + - if reporter + = link_to reporter.name, [:admin, reporter] - else (removed) + .light.small + = time_ago_with_tooltip(abuse_report.created_at) + %td + = markdown(abuse_report.message.squish!, pipeline: :single_line) %td - if user = link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true), diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml index 40a5fe4628b..bc4a9cedb2c 100644 --- a/app/views/admin/abuse_reports/index.html.haml +++ b/app/views/admin/abuse_reports/index.html.haml @@ -6,10 +6,9 @@ %table.table %thead %tr + %th User %th Reported by - %th Reported at %th Message - %th User %th Primary action %th = render @abuse_reports -- cgit v1.2.3 From 0e60282e36faab8b0f4faee0b71716987df28416 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Jan 2016 18:46:43 -0500 Subject: Redirect back to user profile page after abuse report Now the reporter will see the fruits of their labor, namely, the red icon! --- app/controllers/abuse_reports_controller.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb index 20bc5173f1d..5718fd22de9 100644 --- a/app/controllers/abuse_reports_controller.rb +++ b/app/controllers/abuse_reports_controller.rb @@ -14,7 +14,7 @@ class AbuseReportsController < ApplicationController end message = "Thank you for your report. A GitLab administrator will look into it shortly." - redirect_to root_path, notice: message + redirect_to @abuse_report.user, notice: message else render :new end @@ -23,6 +23,9 @@ class AbuseReportsController < ApplicationController private def report_params - params.require(:abuse_report).permit(:user_id, :message) + params.require(:abuse_report).permit(%i( + message + user_id + )) end end -- cgit v1.2.3 From 01248d205103fe6c408e914e8943873ceb7acb2a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Jan 2016 18:56:48 -0500 Subject: Make AbuseReportMailer responsible for knowing if it should deliver --- app/mailers/abuse_report_mailer.rb | 10 ++++++++- spec/mailers/abuse_report_mailer_spec.rb | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 spec/mailers/abuse_report_mailer_spec.rb diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb index f0c41f69a5c..d0ce827a595 100644 --- a/app/mailers/abuse_report_mailer.rb +++ b/app/mailers/abuse_report_mailer.rb @@ -2,11 +2,19 @@ class AbuseReportMailer < BaseMailer include Gitlab::CurrentSettings def notify(abuse_report_id) + return unless deliverable? + @abuse_report = AbuseReport.find(abuse_report_id) mail( - to: current_application_settings.admin_notification_email, + to: current_application_settings.admin_notification_email, subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse" ) end + + private + + def deliverable? + current_application_settings.admin_notification_email.present? + end end diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb new file mode 100644 index 00000000000..eb433c38873 --- /dev/null +++ b/spec/mailers/abuse_report_mailer_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +describe AbuseReportMailer do + include EmailSpec::Matchers + + describe '.notify' do + context 'with admin_notification_email set' do + before do + stub_application_setting(admin_notification_email: 'admin@example.com') + end + + it 'sends to the admin_notification_email' do + report = create(:abuse_report) + + mail = described_class.notify(report.id) + + expect(mail).to deliver_to 'admin@example.com' + end + + it 'includes the user in the subject' do + report = create(:abuse_report) + + mail = described_class.notify(report.id) + + expect(mail).to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse" + end + end + + context 'with no admin_notification_email set' do + it 'returns early' do + stub_application_setting(admin_notification_email: nil) + + expect { described_class.notify(spy).deliver_now }. + not_to change { ActionMailer::Base.deliveries.count } + end + end + end +end -- cgit v1.2.3 From 46a220ae3c0e646aac63a3230399fcc8979df6ec Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 Jan 2016 18:59:42 -0500 Subject: Add `AbuseReport#notify` Tell, Don't Ask. --- app/controllers/abuse_reports_controller.rb | 4 +- app/models/abuse_report.rb | 6 ++ spec/controllers/abuse_reports_controller_spec.rb | 80 +++++++---------------- spec/models/abuse_report_spec.rb | 17 +++++ 4 files changed, 49 insertions(+), 58 deletions(-) diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb index 5718fd22de9..38814459f66 100644 --- a/app/controllers/abuse_reports_controller.rb +++ b/app/controllers/abuse_reports_controller.rb @@ -9,9 +9,7 @@ class AbuseReportsController < ApplicationController @abuse_report.reporter = current_user if @abuse_report.save - if current_application_settings.admin_notification_email.present? - AbuseReportMailer.notify(@abuse_report.id).deliver_later - end + @abuse_report.notify message = "Thank you for your report. A GitLab administrator will look into it shortly." redirect_to @abuse_report.user, notice: message diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index 89b3116b9f2..55864236b2f 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -18,4 +18,10 @@ class AbuseReport < ActiveRecord::Base validates :user, presence: true validates :message, presence: true validates :user_id, uniqueness: true + + def notify + return unless self.persisted? + + AbuseReportMailer.notify(self.id).deliver_later + end end diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb index 15824a1c67f..80a418feb3e 100644 --- a/spec/controllers/abuse_reports_controller_spec.rb +++ b/spec/controllers/abuse_reports_controller_spec.rb @@ -1,76 +1,46 @@ require 'spec_helper' describe AbuseReportsController do - let(:reporter) { create(:user) } - let(:user) { create(:user) } - let(:message) { "This user is a spammer" } + let(:reporter) { create(:user) } + let(:user) { create(:user) } + let(:attrs) do + attributes_for(:abuse_report) do |hash| + hash[:user_id] = user.id + end + end before do sign_in(reporter) end - describe "POST create" do - context "with admin notification email set" do - let(:admin_email) { "admin@example.com"} - - before(:each) do - stub_application_setting(admin_notification_email: admin_email) + describe 'POST create' do + context 'with valid attributes' do + it 'saves the abuse report' do + expect do + post :create, abuse_report: attrs + end.to change { AbuseReport.count }.by(1) end - it "sends a notification email" do - perform_enqueued_jobs do - post :create, - abuse_report: { - user_id: user.id, - message: message - } - - email = ActionMailer::Base.deliveries.last + it 'calls notify' do + expect_any_instance_of(AbuseReport).to receive(:notify) - expect(email.to).to eq([admin_email]) - expect(email.subject).to include(user.username) - expect(email.text_part.body).to include(message) - end + post :create, abuse_report: attrs end - it "saves the abuse report" do - perform_enqueued_jobs do - expect do - post :create, - abuse_report: { - user_id: user.id, - message: message - } - end.to change { AbuseReport.count }.by(1) - end - end - end + it 'redirects back to the reported user' do + post :create, abuse_report: attrs - context "without admin notification email set" do - before(:each) do - stub_application_setting(admin_notification_email: nil) + expect(response).to redirect_to user end + end - it "does not send a notification email" do - expect do - post :create, - abuse_report: { - user_id: user.id, - message: message - } - end.not_to change { ActionMailer::Base.deliveries.count } - end + context 'with invalid attributes' do + it 'renders new' do + attrs.delete(:user_id) + post :create, abuse_report: attrs - it "saves the abuse report" do - expect do - post :create, - abuse_report: { - user_id: user.id, - message: message - } - end.to change { AbuseReport.count }.by(1) + expect(response).to render_template(:new) end end end - end diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index d45319b25d4..46cab1644c7 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -28,4 +28,21 @@ RSpec.describe AbuseReport, type: :model do it { is_expected.to validate_presence_of(:message) } it { is_expected.to validate_uniqueness_of(:user_id) } end + + describe '#notify' do + it 'delivers' do + expect(AbuseReportMailer).to receive(:notify).with(subject.id). + and_return(spy) + + subject.notify + end + + it 'returns early when not persisted' do + report = build(:abuse_report) + + expect(AbuseReportMailer).not_to receive(:notify) + + report.notify + end + end end -- cgit v1.2.3 From f7ba38c073387eedb13375ad6286ba08ce6badb9 Mon Sep 17 00:00:00 2001 From: Andriy Dyadyura Date: Tue, 5 Jan 2016 10:45:18 +0100 Subject: markdown fixes --- app/assets/stylesheets/framework/typography.scss | 6 +- db/schema.rb | 520 +++++++++++------------ 2 files changed, 263 insertions(+), 263 deletions(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index c3e4ad0ad00..05d0c865164 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -54,17 +54,17 @@ h3 { margin: 24px 0 12px 0; - font-size: 1.25em; + font-size: 1.1em; } h4 { margin: 24px 0 12px 0; - font-size: 1.1em; + font-size: 1em; } h5 { margin: 24px 0 12px 0; - font-size: 1em; + font-size: 0.95em; } h6 { diff --git a/db/schema.rb b/db/schema.rb index 7a6d34b8153..48e6983684a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -32,44 +32,44 @@ ActiveRecord::Schema.define(version: 20151229112614) do t.text "sign_in_text" t.datetime "created_at" t.datetime "updated_at" - t.string "home_page_url" - t.integer "default_branch_protection", default: 2 - t.boolean "twitter_sharing_enabled", default: true + t.string "home_page_url", limit: 255 + t.integer "default_branch_protection", default: 2 + t.boolean "twitter_sharing_enabled", default: true t.text "restricted_visibility_levels" - t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false + t.boolean "version_check_enabled", default: true + t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" - t.boolean "user_oauth_applications", default: true - t.string "after_sign_out_path" - t.integer "session_expire_delay", default: 10080, null: false + t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path", limit: 255 + t.integer "session_expire_delay", default: 10080, null: false t.text "import_sources" t.text "help_page_text" - t.string "admin_notification_email" - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false + t.string "admin_notification_email", limit: 255 + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" t.string "metrics_username" t.string "metrics_password" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false t.string "recaptcha_site_key" t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 + t.integer "metrics_port", default: 8089 end create_table "audit_events", force: :cascade do |t| - t.integer "author_id", null: false - t.string "type", null: false - t.integer "entity_id", null: false - t.string "entity_type", null: false + t.integer "author_id", null: false + t.string "type", limit: 255, null: false + t.integer "entity_id", null: false + t.string "entity_type", limit: 255, null: false t.text "details" t.datetime "created_at" t.datetime "updated_at" @@ -80,14 +80,14 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" t.integer "alert_type" t.datetime "created_at" t.datetime "updated_at" - t.string "color" - t.string "font" + t.string "color", limit: 255 + t.string "font", limit: 255 end create_table "ci_application_settings", force: :cascade do |t| @@ -99,7 +99,7 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "ci_builds", force: :cascade do |t| t.integer "project_id" - t.string "status" + t.string "status", limit: 255 t.datetime "finished_at" t.text "trace" t.datetime "created_at" @@ -110,19 +110,19 @@ ActiveRecord::Schema.define(version: 20151229112614) do t.integer "commit_id" t.text "commands" t.integer "job_id" - t.string "name" - t.boolean "deploy", default: false + t.string "name", limit: 255 + t.boolean "deploy", default: false t.text "options" - t.boolean "allow_failure", default: false, null: false - t.string "stage" + t.boolean "allow_failure", default: false, null: false + t.string "stage", limit: 255 t.integer "trigger_request_id" t.integer "stage_idx" t.boolean "tag" - t.string "ref" + t.string "ref", limit: 255 t.integer "user_id" - t.string "type" - t.string "target_url" - t.string "description" + t.string "type", limit: 255 + t.string "target_url", limit: 255 + t.string "description", limit: 255 t.text "artifacts_file" t.integer "gl_project_id" end @@ -141,13 +141,13 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "ci_commits", force: :cascade do |t| t.integer "project_id" - t.string "ref" - t.string "sha" - t.string "before_sha" + t.string "ref", limit: 255 + t.string "sha", limit: 255 + t.string "before_sha", limit: 255 t.text "push_data" t.datetime "created_at" t.datetime "updated_at" - t.boolean "tag", default: false + t.boolean "tag", default: false t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" @@ -174,16 +174,16 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree create_table "ci_jobs", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id", null: false t.text "commands" - t.boolean "active", default: true, null: false + t.boolean "active", default: true, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "build_branches", default: true, null: false - t.boolean "build_tags", default: false, null: false - t.string "job_type", default: "parallel" - t.string "refs" + t.string "name", limit: 255 + t.boolean "build_branches", default: true, null: false + t.boolean "build_tags", default: false, null: false + t.string "job_type", limit: 255, default: "parallel" + t.string "refs", limit: 255 t.datetime "deleted_at" end @@ -191,25 +191,25 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree create_table "ci_projects", force: :cascade do |t| - t.string "name" - t.integer "timeout", default: 3600, null: false + t.string "name", limit: 255 + t.integer "timeout", default: 3600, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "token" - t.string "default_ref" - t.string "path" - t.boolean "always_build", default: false, null: false + t.string "token", limit: 255 + t.string "default_ref", limit: 255 + t.string "path", limit: 255 + t.boolean "always_build", default: false, null: false t.integer "polling_interval" - t.boolean "public", default: false, null: false - t.string "ssh_url_to_repo" + t.boolean "public", default: false, null: false + t.string "ssh_url_to_repo", limit: 255 t.integer "gitlab_id" - t.boolean "allow_git_fetch", default: true, null: false - t.string "email_recipients", default: "", null: false - t.boolean "email_add_pusher", default: true, null: false - t.boolean "email_only_broken_builds", default: true, null: false - t.string "skip_refs" - t.string "coverage_regex" - t.boolean "shared_runners_enabled", default: false + t.boolean "allow_git_fetch", default: true, null: false + t.string "email_recipients", limit: 255, default: "", null: false + t.boolean "email_add_pusher", default: true, null: false + t.boolean "email_only_broken_builds", default: true, null: false + t.string "skip_refs", limit: 255 + t.string "coverage_regex", limit: 255 + t.boolean "shared_runners_enabled", default: false t.text "generated_yaml_config" end @@ -228,34 +228,34 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree create_table "ci_runners", force: :cascade do |t| - t.string "token" + t.string "token", limit: 255 t.datetime "created_at" t.datetime "updated_at" - t.string "description" + t.string "description", limit: 255 t.datetime "contacted_at" - t.boolean "active", default: true, null: false - t.boolean "is_shared", default: false - t.string "name" - t.string "version" - t.string "revision" - t.string "platform" - t.string "architecture" + t.boolean "active", default: true, null: false + t.boolean "is_shared", default: false + t.string "name", limit: 255 + t.string "version", limit: 255 + t.string "revision", limit: 255 + t.string "platform", limit: 255 + t.string "architecture", limit: 255 end create_table "ci_services", force: :cascade do |t| - t.string "type" - t.string "title" - t.integer "project_id", null: false + t.string "type", limit: 255 + t.string "title", limit: 255 + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.boolean "active", default: false, null: false t.text "properties" end add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree create_table "ci_sessions", force: :cascade do |t| - t.string "session_id", null: false + t.string "session_id", limit: 255, null: false t.text "data" t.datetime "created_at" t.datetime "updated_at" @@ -267,9 +267,9 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "ci_taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type" + t.string "taggable_type", limit: 255 t.integer "tagger_id" - t.string "tagger_type" + t.string "tagger_type", limit: 255 t.string "context", limit: 128 t.datetime "created_at" end @@ -278,8 +278,8 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "ci_tags", force: :cascade do |t| - t.string "name" - t.integer "taggings_count", default: 0 + t.string "name", limit: 255 + t.integer "taggings_count", default: 0 end add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree @@ -293,7 +293,7 @@ ActiveRecord::Schema.define(version: 20151229112614) do end create_table "ci_triggers", force: :cascade do |t| - t.string "token" + t.string "token", limit: 255 t.integer "project_id" t.datetime "deleted_at" t.datetime "created_at" @@ -306,19 +306,19 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "ci_variables", force: :cascade do |t| t.integer "project_id" - t.string "key" + t.string "key", limit: 255 t.text "value" t.text "encrypted_value" - t.string "encrypted_value_salt" - t.string "encrypted_value_iv" + t.string "encrypted_value_salt", limit: 255 + t.string "encrypted_value_iv", limit: 255 t.integer "gl_project_id" end add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree create_table "ci_web_hooks", force: :cascade do |t| - t.string "url", null: false - t.integer "project_id", null: false + t.string "url", limit: 255, null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -333,8 +333,8 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree create_table "emails", force: :cascade do |t| - t.integer "user_id", null: false - t.string "email", null: false + t.integer "user_id", null: false + t.string "email", limit: 255, null: false t.datetime "created_at" t.datetime "updated_at" end @@ -343,9 +343,9 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "events", force: :cascade do |t| - t.string "target_type" + t.string "target_type", limit: 255 t.integer "target_id" - t.string "title" + t.string "title", limit: 255 t.text "data" t.integer "project_id" t.datetime "created_at" @@ -371,8 +371,8 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree create_table "identities", force: :cascade do |t| - t.string "extern_uid" - t.string "provider" + t.string "extern_uid", limit: 255 + t.string "provider", limit: 255 t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" @@ -382,17 +382,17 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issues", force: :cascade do |t| - t.string "title" + t.string "title", limit: 255 t.integer "assignee_id" t.integer "author_id" t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 - t.string "branch_name" + t.integer "position", default: 0 + t.string "branch_name", limit: 255 t.text "description" t.integer "milestone_id" - t.string "state" + t.string "state", limit: 255 t.integer "iid" t.integer "updated_by_id" end @@ -412,10 +412,10 @@ ActiveRecord::Schema.define(version: 20151229112614) do t.datetime "created_at" t.datetime "updated_at" t.text "key" - t.string "title" - t.string "type" - t.string "fingerprint" - t.boolean "public", default: false, null: false + t.string "title", limit: 255 + t.string "type", limit: 255 + t.string "fingerprint", limit: 255 + t.boolean "public", default: false, null: false end add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree @@ -424,7 +424,7 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "label_links", force: :cascade do |t| t.integer "label_id" t.integer "target_id" - t.string "target_type" + t.string "target_type", limit: 255 t.datetime "created_at" t.datetime "updated_at" end @@ -433,22 +433,22 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree create_table "labels", force: :cascade do |t| - t.string "title" - t.string "color" + t.string "title", limit: 255 + t.string "color", limit: 255 t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false + t.boolean "template", default: false end add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree create_table "lfs_objects", force: :cascade do |t| - t.string "oid", null: false - t.integer "size", null: false + t.string "oid", limit: 255, null: false + t.integer "size", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "file" + t.string "file", limit: 255 end add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree @@ -463,17 +463,17 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree create_table "members", force: :cascade do |t| - t.integer "access_level", null: false - t.integer "source_id", null: false - t.string "source_type", null: false + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", limit: 255, null: false t.integer "user_id" - t.integer "notification_level", null: false - t.string "type" + t.integer "notification_level", null: false + t.string "type", limit: 255 t.datetime "created_at" t.datetime "updated_at" t.integer "created_by_id" - t.string "invite_email" - t.string "invite_token" + t.string "invite_email", limit: 255 + t.string "invite_token", limit: 255 t.datetime "invite_accepted_at" end @@ -485,10 +485,10 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree create_table "merge_request_diffs", force: :cascade do |t| - t.string "state" + t.string "state", limit: 255 t.text "st_commits" t.text "st_diffs" - t.integer "merge_request_id", null: false + t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -496,26 +496,26 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", null: false - t.string "source_branch", null: false - t.integer "source_project_id", null: false + t.string "target_branch", limit: 255, null: false + t.string "source_branch", limit: 255, null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" - t.string "title" + t.string "title", limit: 255 t.datetime "created_at" t.datetime "updated_at" t.integer "milestone_id" - t.string "state" - t.string "merge_status" - t.integer "target_project_id", null: false + t.string "state", limit: 255 + t.string "merge_status", limit: 255 + t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.integer "position", default: 0 + t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" - t.string "merge_error" + t.string "merge_error", limit: 255 t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false + t.boolean "merge_when_build_succeeds", default: false, null: false t.integer "merge_user_id" end @@ -531,13 +531,13 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree create_table "milestones", force: :cascade do |t| - t.string "title", null: false - t.integer "project_id", null: false + t.string "title", limit: 255, null: false + t.integer "project_id", null: false t.text "description" t.date "due_date" t.datetime "created_at" t.datetime "updated_at" - t.string "state" + t.string "state", limit: 255 t.integer "iid" end @@ -547,14 +547,14 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false + t.string "name", limit: 255, null: false + t.string "path", limit: 255, null: false t.integer "owner_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type" - t.string "description", default: "", null: false - t.string "avatar" + t.string "type", limit: 255 + t.string "description", limit: 255, default: "", null: false + t.string "avatar", limit: 255 end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree @@ -565,19 +565,19 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "notes", force: :cascade do |t| t.text "note" - t.string "noteable_type" + t.string "noteable_type", limit: 255 t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" t.integer "project_id" - t.string "attachment" - t.string "line_code" - t.string "commit_id" + t.string "attachment", limit: 255 + t.string "line_code", limit: 255 + t.string "commit_id", limit: 255 t.integer "noteable_id" - t.boolean "system", default: false, null: false + t.boolean "system", default: false, null: false t.text "st_diff" t.integer "updated_by_id" - t.boolean "is_award", default: false, null: false + t.boolean "is_award", default: false, null: false end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -593,14 +593,14 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", limit: 255, null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false t.datetime "revoked_at" - t.string "scopes" + t.string "scopes", limit: 255 end add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree @@ -608,12 +608,12 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "oauth_access_tokens", force: :cascade do |t| t.integer "resource_owner_id" t.integer "application_id" - t.string "token", null: false - t.string "refresh_token" + t.string "token", limit: 255, null: false + t.string "refresh_token", limit: 255 t.integer "expires_in" t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes" + t.datetime "created_at", null: false + t.string "scopes", limit: 255 end add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree @@ -621,15 +621,15 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree create_table "oauth_applications", force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false + t.string "name", limit: 255, null: false + t.string "uid", limit: 255, null: false + t.string "secret", limit: 255, null: false + t.text "redirect_uri", null: false + t.string "scopes", limit: 255, default: "", null: false t.datetime "created_at" t.datetime "updated_at" t.integer "owner_id" - t.string "owner_type" + t.string "owner_type", limit: 255 end add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree @@ -641,39 +641,39 @@ ActiveRecord::Schema.define(version: 20151229112614) do end create_table "projects", force: :cascade do |t| - t.string "name" - t.string "path" + t.string "name", limit: 255 + t.string "path", limit: 255 t.text "description" t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "wall_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.string "issues_tracker", default: "gitlab", null: false - t.string "issues_tracker_id" - t.boolean "snippets_enabled", default: true, null: false + t.string "issues_tracker", limit: 255, default: "gitlab", null: false + t.string "issues_tracker_id", limit: 255 + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" - t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false - t.string "avatar" - t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type" - t.string "import_source" - t.integer "commit_count", default: 0 + t.string "import_url", limit: 255 + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false + t.string "avatar", limit: 255 + t.string "import_status", limit: 255 + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type", limit: 255 + t.string "import_source", limit: 255 + t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree @@ -689,17 +689,17 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", null: false + t.integer "project_id", null: false + t.string "name", limit: 255, null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "developers_can_push", default: false, null: false + t.boolean "developers_can_push", default: false, null: false end add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree create_table "releases", force: :cascade do |t| - t.string "tag" + t.string "tag", limit: 255 t.text "description" t.integer "project_id" t.datetime "created_at" @@ -712,30 +712,30 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" - t.string "noteable_type" + t.string "noteable_type", limit: 255 t.integer "recipient_id" - t.string "commit_id" - t.string "reply_key", null: false - t.string "line_code" + t.string "commit_id", limit: 255 + t.string "reply_key", limit: 255, null: false + t.string "line_code", limit: 255 end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree create_table "services", force: :cascade do |t| - t.string "type" - t.string "title" + t.string "type", limit: 255 + t.string "title", limit: 255 t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.boolean "active", default: false, null: false t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false end add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree @@ -743,16 +743,16 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "services", ["template"], name: "index_services_on_template", using: :btree create_table "snippets", force: :cascade do |t| - t.string "title" + t.string "title", limit: 255 t.text "content" - t.integer "author_id", null: false + t.integer "author_id", null: false t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "file_name" + t.string "file_name", limit: 255 t.datetime "expires_at" - t.string "type" - t.integer "visibility_level", default: 0, null: false + t.string "type", limit: 255 + t.integer "visibility_level", default: 0, null: false end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -765,7 +765,7 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "subscriptions", force: :cascade do |t| t.integer "user_id" t.integer "subscribable_id" - t.string "subscribable_type" + t.string "subscribable_type", limit: 255 t.boolean "subscribed" t.datetime "created_at" t.datetime "updated_at" @@ -776,10 +776,10 @@ ActiveRecord::Schema.define(version: 20151229112614) do create_table "taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type" + t.string "taggable_type", limit: 255 t.integer "tagger_id" - t.string "tagger_type" - t.string "context" + t.string "tagger_type", limit: 255 + t.string "context", limit: 255 t.datetime "created_at" end @@ -787,67 +787,67 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name" - t.integer "taggings_count", default: 0 + t.string "name", limit: 255 + t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", limit: 255, default: "", null: false + t.string "encrypted_password", limit: 255, default: "", null: false + t.string "reset_password_token", limit: 255 t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" - t.datetime "created_at" - t.datetime "updated_at" - t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false - t.string "authentication_token" - t.integer "theme_id", default: 1, null: false - t.string "bio" - t.integer "failed_attempts", default: 0 + t.string "current_sign_in_ip", limit: 255 + t.string "last_sign_in_ip", limit: 255 + t.datetime "created_at" + t.datetime "updated_at" + t.string "name", limit: 255 + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", limit: 255, default: "", null: false + t.string "linkedin", limit: 255, default: "", null: false + t.string "twitter", limit: 255, default: "", null: false + t.string "authentication_token", limit: 255 + t.integer "theme_id", default: 1, null: false + t.string "bio", limit: 255 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false - t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.string "username", limit: 255 + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false + t.string "state", limit: 255 + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" - t.string "avatar" - t.string "confirmation_token" + t.string "avatar", limit: 255 + t.string "confirmation_token", limit: 255 t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location" - t.string "encrypted_otp_secret" - t.string "encrypted_otp_secret_iv" - t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false + t.string "unconfirmed_email", limit: 255 + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", limit: 255, default: "", null: false + t.string "notification_email", limit: 255 + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location", limit: 255 + t.string "encrypted_otp_secret", limit: 255 + t.string "encrypted_otp_secret_iv", limit: 255 + t.string "encrypted_otp_secret_salt", limit: 255 + t.boolean "otp_required_for_login", default: false, null: false t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 + t.string "public_email", limit: 255, default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false t.string "unlock_token" t.datetime "otp_grace_period_started_at" end @@ -874,19 +874,19 @@ ActiveRecord::Schema.define(version: 20151229112614) do add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree create_table "web_hooks", force: :cascade do |t| - t.string "url" + t.string "url", limit: 255 t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", default: "ProjectHook" + t.string "type", limit: 255, default: "ProjectHook" t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false - t.boolean "tag_push_events", default: false - t.boolean "note_events", default: false, null: false - t.boolean "enable_ssl_verification", default: true - t.boolean "build_events", default: false, null: false + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false + t.boolean "tag_push_events", default: false + t.boolean "note_events", default: false, null: false + t.boolean "enable_ssl_verification", default: true + t.boolean "build_events", default: false, null: false end add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree -- cgit v1.2.3 From 00e8532d89fb04b39e9811c5ec5953dd2d098f6f Mon Sep 17 00:00:00 2001 From: Andriy Dyadyura Date: Tue, 5 Jan 2016 10:51:59 +0100 Subject: markdown fixes --- app/assets/stylesheets/framework/typography.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 05d0c865164..714369d9f15 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -59,7 +59,7 @@ h4 { margin: 24px 0 12px 0; - font-size: 1em; + font-size: 0.98em; } h5 { -- cgit v1.2.3 From d17945bb4ef0750f546a9dea79df1abf4ef140a5 Mon Sep 17 00:00:00 2001 From: Felix Eckhofer Date: Tue, 5 Jan 2016 13:01:23 +0100 Subject: Remove misleading `ssh-dsa` The keytype field is actually `ssh-dss` for DSA keys and they will not be stored as `id_rsa.pub`. Note that newer version of ssh actually also support `ecdsa-sha2-nistp256` and others so it is also misleading to assume the field always starts with `ssh-`. --- doc/ssh/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ssh/README.md b/doc/ssh/README.md index fe5b45dd432..64795233e41 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -9,7 +9,7 @@ already has one by running the following command: cat ~/.ssh/id_rsa.pub ``` -If you see a long string starting with `ssh-rsa` or `ssh-dsa`, you can skip the `ssh-keygen` step. +If you see a long string starting with `ssh-rsa`, you can skip the `ssh-keygen` step. Note: It is a best practice to use a password for an SSH key, but it is not required and you can skip creating a password by pressing enter. Note that @@ -29,7 +29,7 @@ cat ~/.ssh/id_rsa.pub ``` Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your -user profile. Please copy the complete key starting with `ssh-` and ending +user profile. Please copy the complete key starting with `ssh-rsa` and ending with your username and host. To copy your public key to the clipboard, use code below. Depending on your -- cgit v1.2.3 From ca8639a4662c59b15ef4e0ed02b6587ce4049b0d Mon Sep 17 00:00:00 2001 From: Felix Eckhofer Date: Tue, 5 Jan 2016 13:02:45 +0100 Subject: Clarify the key generation step Users must not change the default filename or they will have to create a suitable .ssh/config which is out-of-scope here. --- doc/ssh/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/ssh/README.md b/doc/ssh/README.md index 64795233e41..c4b1fc3b626 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -20,8 +20,9 @@ To generate a new SSH key, use the following command: ssh-keygen -t rsa -C "$your_email" ``` This command will prompt you for a location and filename to store the key -pair and for a password. When prompted for the location and filename, you -can press enter to use the default. +pair and for a password. When prompted for the location and filename, just +press enter to use the default. If you use a different name, the key will not +be used automatically. Use the command below to show your public key: ```bash -- cgit v1.2.3 From 766a59630f8e004fbd99549dc6d27f7b29fff62f Mon Sep 17 00:00:00 2001 From: Felix Eckhofer Date: Tue, 5 Jan 2016 13:06:23 +0100 Subject: Fix grammar --- doc/ssh/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ssh/README.md b/doc/ssh/README.md index c4b1fc3b626..77eb53427e2 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -33,7 +33,7 @@ Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile. Please copy the complete key starting with `ssh-rsa` and ending with your username and host. -To copy your public key to the clipboard, use code below. Depending on your +To copy your public key to the clipboard, use the code below. Depending on your OS you'll need to use a different command: **Windows:** -- cgit v1.2.3