From 31704680cc967f0a053edc70db828ff780d7d0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Wed, 2 Nov 2016 00:32:20 +0200 Subject: post_receive: accept any user email from last commit --- lib/gitlab/identifier.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb index f8809db21aa..c5acf18beb5 100644 --- a/lib/gitlab/identifier.rb +++ b/lib/gitlab/identifier.rb @@ -24,7 +24,7 @@ module Gitlab email = commit.author_email identify_with_cache(:email, email) do - User.find_by(email: email) + User.find_by_any_email(email) end end -- cgit v1.2.1 From 1afab9eb79c87f32c7b899e58bc9a0ea8a113594 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Wed, 21 Sep 2016 16:15:12 +0200 Subject: Add button to delete all merged branches It adds a button to the branches page that the user can use to delete all the branches that are already merged. This can be used to clean up all the branches that were forgotten to delete while merging MRs. Fixes #21076. --- lib/api/branches.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'lib') diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 21a106387f0..73aed624ea7 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -128,6 +128,18 @@ module API render_api_error!(result[:message], result[:return_code]) end end + + # Delete all merged branches + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # DELETE /projects/:id/repository/branches/delete_merged + delete ":id/repository/merged_branches" do + DeleteMergedBranchesService.new(user_project, current_user).async_execute + + status(200) + end end end end -- cgit v1.2.1 From 3fa265d19547669c60788e38e389fa12bb119235 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Thu, 10 Nov 2016 18:24:12 +0100 Subject: Fix expanding a collapsed diff when converting a symlink to a regular file In this case comparing old_path and new_path is not enough because there are two entires that match. --- lib/gitlab/diff/file.rb | 2 +- lib/gitlab/diff/file_collection/merge_request_diff.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index ce85e5e0123..5110bfbf898 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -126,7 +126,7 @@ module Gitlab repository.blob_at(commit.id, file_path) end - def cache_key + def file_identifier "#{file_path}-#{new_file}-#{deleted_file}-#{renamed_file}" end end diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb index dc4d47c878b..fe7adb7bed6 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -39,7 +39,7 @@ module Gitlab # hashes that represent serialized diff lines. # def cache_highlight!(diff_file) - item_key = diff_file.cache_key + item_key = diff_file.file_identifier if highlight_cache[item_key] highlight_diff_file_from_cache!(diff_file, highlight_cache[item_key]) -- cgit v1.2.1 From 9e2964c15a7d387e46e25c83afa478c12a856d77 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 4 Nov 2016 12:53:12 -0600 Subject: Allow certain Sidekiq jobs to be throttled --- lib/gitlab/current_settings.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index ef9160d6437..801c2d5abcd 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -50,6 +50,7 @@ module Gitlab repository_checks_enabled: true, container_registry_token_expire_delay: 5, user_default_external: false, + sidekiq_throttling_enabled: false, ) end -- cgit v1.2.1 From b95216aabadb336e4ed8cdc01f69e873f47f10d0 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 4 Nov 2016 16:54:24 -0600 Subject: Allow the Sidekiq queues to throttle and the factor by which to throttle them to be configurable --- lib/gitlab/current_settings.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 801c2d5abcd..3a651ef318a 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -23,6 +23,10 @@ module Gitlab settings || fake_application_settings end + def sidekiq_throttling_enabled? + current_application_settings.sidekiq_throttling_enabled + end + def fake_application_settings OpenStruct.new( default_projects_limit: Settings.gitlab['default_projects_limit'], -- cgit v1.2.1 From 208530494e5d2c5c62a3e1c24489aae0e4935e3a Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 8 Nov 2016 16:31:37 -0600 Subject: Refactored initializer code to its own class and added tests --- lib/gitlab/sidekiq_throttler.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lib/gitlab/sidekiq_throttler.rb (limited to 'lib') diff --git a/lib/gitlab/sidekiq_throttler.rb b/lib/gitlab/sidekiq_throttler.rb new file mode 100644 index 00000000000..771736e7606 --- /dev/null +++ b/lib/gitlab/sidekiq_throttler.rb @@ -0,0 +1,21 @@ +module Gitlab + class SidekiqThrottler + class << self + def execute! + if Gitlab::CurrentSettings.sidekiq_throttling_enabled? + current_application_settings.sidekiq_throttling_queues.each do |queue| + Sidekiq::Queue[queue].limit = set_limit + end + end + end + + private + + def set_limit + factor = current_application_settings.sidekiq_throttling_factor + + (factor * Sidekiq.options[:concurrency]).ceil + end + end + end +end -- cgit v1.2.1 From 3cff3a2e5b87c40927eb02a8884c84260ca30c2a Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Wed, 19 Oct 2016 09:50:34 -0500 Subject: Omniauth auto link LDAP user falls back to find by DN when user cannot be found by uid --- lib/gitlab/o_auth/user.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 0a91d3918d5..a8b4dc2a83f 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -102,6 +102,8 @@ module Gitlab Gitlab::LDAP::Config.providers.each do |provider| adapter = Gitlab::LDAP::Adapter.new(provider) @ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter) + # The `uid` might actually be a DN. Try it next. + @ldap_person ||= Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter) break if @ldap_person end @ldap_person -- cgit v1.2.1 From e840749b84ceb226e46ebdfb489c735e3370cff7 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 10 Nov 2016 11:36:52 -0600 Subject: Refactored Sidekiq Throttler and updated documentation --- lib/gitlab/sidekiq_throttler.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/sidekiq_throttler.rb b/lib/gitlab/sidekiq_throttler.rb index 771736e7606..d4d39a888e7 100644 --- a/lib/gitlab/sidekiq_throttler.rb +++ b/lib/gitlab/sidekiq_throttler.rb @@ -3,18 +3,20 @@ module Gitlab class << self def execute! if Gitlab::CurrentSettings.sidekiq_throttling_enabled? - current_application_settings.sidekiq_throttling_queues.each do |queue| - Sidekiq::Queue[queue].limit = set_limit + Gitlab::CurrentSettings.current_application_settings.sidekiq_throttling_queues.each do |queue| + Sidekiq::Queue[queue].limit = queue_limit end end end private - def set_limit - factor = current_application_settings.sidekiq_throttling_factor - - (factor * Sidekiq.options[:concurrency]).ceil + def queue_limit + @queue_limit ||= + begin + factor = Gitlab::CurrentSettings.current_application_settings.sidekiq_throttling_factor + (factor * Sidekiq.options[:concurrency]).ceil + end end end end -- cgit v1.2.1 From c50b98da723dab9a35ddb2cde0258d141cf92495 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 11 Nov 2016 14:44:08 -0600 Subject: Centralize LDAP config/filter logic Centralize all LDAP config logic in `GitLab::LDAP::Config`. Previously, some logic was in the Devise initializer and it was not honoring the `user_filter`. If a user outside the configured `user_filter` signed in, an account would be created but they would then be denied access. Now that logic is centralized, the filter is honored and users outside the filter are never created. --- lib/gitlab/ldap/adapter.rb | 4 +-- lib/gitlab/ldap/authentication.rb | 6 ++-- lib/gitlab/ldap/config.rb | 65 ++++++++++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 8b38cfaefb6..7b05290e5cc 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -89,9 +89,7 @@ module Gitlab end def user_filter(filter = nil) - if config.user_filter.present? - user_filter = Net::LDAP::Filter.construct(config.user_filter) - end + user_filter = config.constructed_user_filter if config.user_filter.present? if user_filter && filter Net::LDAP::Filter.join(filter, user_filter) diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb index bad683c6511..4745311402c 100644 --- a/lib/gitlab/ldap/authentication.rb +++ b/lib/gitlab/ldap/authentication.rb @@ -54,11 +54,9 @@ module Gitlab # Apply LDAP user filter if present if config.user_filter.present? - filter = Net::LDAP::Filter.join( - filter, - Net::LDAP::Filter.construct(config.user_filter) - ) + filter = Net::LDAP::Filter.join(filter, config.constructed_user_filter) end + filter end diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb index 6ea069d26df..de52ef3fc65 100644 --- a/lib/gitlab/ldap/config.rb +++ b/lib/gitlab/ldap/config.rb @@ -13,7 +13,7 @@ module Gitlab end def self.providers - servers.map {|server| server['provider_name'] } + servers.map { |server| server['provider_name'] } end def self.valid_provider?(provider) @@ -38,13 +38,31 @@ module Gitlab end def adapter_options - { - host: options['host'], - port: options['port'], - encryption: encryption - }.tap do |options| - options.merge!(auth_options) if has_auth? + opts = base_options.merge( + encryption: encryption, + ) + + opts.merge!(auth_options) if has_auth? + + opts + end + + def omniauth_options + opts = base_options.merge( + base: base, + method: options['method'], + filter: omniauth_user_filter, + name_proc: name_proc + ) + + if has_auth? + opts.merge!( + bind_dn: options['bind_dn'], + password: options['password'] + ) end + + opts end def base @@ -68,6 +86,10 @@ module Gitlab options['user_filter'] end + def constructed_user_filter + @constructed_user_filter ||= Net::LDAP::Filter.construct(user_filter) + end + def group_base options['group_base'] end @@ -96,8 +118,27 @@ module Gitlab options['password'] || options['bind_dn'] end + def allow_username_or_email_login + options['allow_username_or_email_login'] + end + + def name_proc + if allow_username_or_email_login + Proc.new { |name| name.gsub(/@.*\z/, '') } + else + Proc.new { |name| name } + end + end + protected + def base_options + { + host: options['host'], + port: options['port'] + } + end + def base_config Gitlab.config.ldap end @@ -126,6 +167,16 @@ module Gitlab } } end + + def omniauth_user_filter + uid_filter = Net::LDAP::Filter.eq(uid, '%{username}') + + if user_filter.present? + Net::LDAP::Filter.join(uid_filter, constructed_user_filter).to_s + else + uid_filter.to_s + end + end end end end -- cgit v1.2.1 From af941732ed92ad44a7568964e2a9191d0c72164c Mon Sep 17 00:00:00 2001 From: Vincent Composieux Date: Fri, 11 Nov 2016 12:27:23 +0100 Subject: Added ability to put emojis into repository name Added ability to put emojis into repository name --- lib/gitlab/regex.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index cb1659f9cee..155ca47e04c 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -26,12 +26,12 @@ module Gitlab end def project_name_regex - @project_name_regex ||= /\A[\p{Alnum}_][\p{Alnum}\p{Pd}_\. ]*\z/.freeze + @project_name_regex ||= /\A[\p{Alnum}\u{00A9}-\u{1f9c0}_][\p{Alnum}\p{Pd}\u{00A9}-\u{1f9c0}_\. ]*\z/.freeze end def project_name_regex_message - "can contain only letters, digits, '_', '.', dash and space. " \ - "It must start with letter, digit or '_'." + "can contain only letters, digits, emojis, '_', '.', dash, space. " \ + "It must start with letter, digit, emoji or '_'." end def project_path_regex -- cgit v1.2.1 From 5c966f70fb218d6f4de0f888733604293f36c33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rebeca=20M=C3=A9ndez?= Date: Mon, 29 Aug 2016 17:23:40 +0200 Subject: Issue #4270: Recursive option for files through API --- lib/api/entities.rb | 2 +- lib/api/repositories.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1942aeea656..8f1aaaaaaa0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -159,7 +159,7 @@ module API end class RepoTreeObject < Grape::Entity - expose :id, :name, :type + expose :id, :name, :type, :path expose :mode do |obj, options| filemode = obj.mode.to_s(8) diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index f55aceed92c..0bb2f74809a 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -21,16 +21,18 @@ module API # Parameters: # id (required) - The ID of a project # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used + # recursive (optional) - Used to get a recursive tree # Example Request: # GET /projects/:id/repository/tree get ':id/repository/tree' do ref = params[:ref_name] || user_project.try(:default_branch) || 'master' path = params[:path] || nil + recursive = to_boolean(params[:recursive]) commit = user_project.commit(ref) not_found!('Tree') unless commit - tree = user_project.repository.tree(commit.id, path) + tree = user_project.repository.tree(commit.id, path, recursive: recursive) present tree.sorted_entries, with: Entities::RepoTreeObject end -- cgit v1.2.1 From 5a65d9d53bd24574c833dd4fba4b8ae44f71b822 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 12 Nov 2016 16:00:21 -0800 Subject: Only turn on Sidekiq throttling if application settings exists If the database has not been migrated properly, the initializer for Sidekiq will fail to start and cause the whole Rails server to crash. This change checks the existence for the column and allows Rails to start even if the setting has not been added as a database column. Closes #24452 --- lib/gitlab/current_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 3a651ef318a..c6bb8f9c8ed 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -24,7 +24,7 @@ module Gitlab end def sidekiq_throttling_enabled? - current_application_settings.sidekiq_throttling_enabled + current_application_settings.sidekiq_throttling_enabled? end def fake_application_settings -- cgit v1.2.1 From c70146839009397b3a1a9ab9bd1108db3cb70c04 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 14 Nov 2016 10:31:45 +0100 Subject: Improve naming convention in ci configuration module --- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- lib/gitlab/ci/config.rb | 2 +- lib/gitlab/ci/config/entry/artifacts.rb | 35 ++++++ lib/gitlab/ci/config/entry/attributable.rb | 23 ++++ lib/gitlab/ci/config/entry/boolean.rb | 18 +++ lib/gitlab/ci/config/entry/cache.rb | 29 +++++ lib/gitlab/ci/config/entry/commands.rb | 33 +++++ lib/gitlab/ci/config/entry/configurable.rb | 78 ++++++++++++ lib/gitlab/ci/config/entry/environment.rb | 82 ++++++++++++ lib/gitlab/ci/config/entry/factory.rb | 73 +++++++++++ lib/gitlab/ci/config/entry/global.rb | 72 +++++++++++ lib/gitlab/ci/config/entry/hidden.rb | 22 ++++ lib/gitlab/ci/config/entry/image.rb | 18 +++ lib/gitlab/ci/config/entry/job.rb | 140 +++++++++++++++++++++ lib/gitlab/ci/config/entry/jobs.rb | 52 ++++++++ lib/gitlab/ci/config/entry/key.rb | 18 +++ .../ci/config/entry/legacy_validation_helpers.rb | 51 ++++++++ lib/gitlab/ci/config/entry/node.rb | 83 ++++++++++++ lib/gitlab/ci/config/entry/paths.rb | 18 +++ lib/gitlab/ci/config/entry/script.rb | 18 +++ lib/gitlab/ci/config/entry/services.rb | 18 +++ lib/gitlab/ci/config/entry/stage.rb | 22 ++++ lib/gitlab/ci/config/entry/stages.rb | 22 ++++ lib/gitlab/ci/config/entry/trigger.rb | 26 ++++ lib/gitlab/ci/config/entry/undefined.rb | 36 ++++++ lib/gitlab/ci/config/entry/unspecified.rb | 19 +++ lib/gitlab/ci/config/entry/validatable.rb | 27 ++++ lib/gitlab/ci/config/entry/validator.rb | 42 +++++++ lib/gitlab/ci/config/entry/validators.rb | 82 ++++++++++++ lib/gitlab/ci/config/entry/variables.rb | 22 ++++ lib/gitlab/ci/config/node/artifacts.rb | 35 ------ lib/gitlab/ci/config/node/attributable.rb | 23 ---- lib/gitlab/ci/config/node/boolean.rb | 18 --- lib/gitlab/ci/config/node/cache.rb | 29 ----- lib/gitlab/ci/config/node/commands.rb | 33 ----- lib/gitlab/ci/config/node/configurable.rb | 78 ------------ lib/gitlab/ci/config/node/entry.rb | 83 ------------ lib/gitlab/ci/config/node/environment.rb | 82 ------------ lib/gitlab/ci/config/node/factory.rb | 73 ----------- lib/gitlab/ci/config/node/global.rb | 72 ----------- lib/gitlab/ci/config/node/hidden.rb | 22 ---- lib/gitlab/ci/config/node/image.rb | 18 --- lib/gitlab/ci/config/node/job.rb | 140 --------------------- lib/gitlab/ci/config/node/jobs.rb | 52 -------- lib/gitlab/ci/config/node/key.rb | 18 --- .../ci/config/node/legacy_validation_helpers.rb | 51 -------- lib/gitlab/ci/config/node/paths.rb | 18 --- lib/gitlab/ci/config/node/script.rb | 18 --- lib/gitlab/ci/config/node/services.rb | 18 --- lib/gitlab/ci/config/node/stage.rb | 22 ---- lib/gitlab/ci/config/node/stages.rb | 22 ---- lib/gitlab/ci/config/node/trigger.rb | 26 ---- lib/gitlab/ci/config/node/undefined.rb | 38 ------ lib/gitlab/ci/config/node/unspecified.rb | 19 --- lib/gitlab/ci/config/node/validatable.rb | 27 ---- lib/gitlab/ci/config/node/validator.rb | 42 ------- lib/gitlab/ci/config/node/validators.rb | 82 ------------ lib/gitlab/ci/config/node/variables.rb | 22 ---- 58 files changed, 1181 insertions(+), 1183 deletions(-) create mode 100644 lib/gitlab/ci/config/entry/artifacts.rb create mode 100644 lib/gitlab/ci/config/entry/attributable.rb create mode 100644 lib/gitlab/ci/config/entry/boolean.rb create mode 100644 lib/gitlab/ci/config/entry/cache.rb create mode 100644 lib/gitlab/ci/config/entry/commands.rb create mode 100644 lib/gitlab/ci/config/entry/configurable.rb create mode 100644 lib/gitlab/ci/config/entry/environment.rb create mode 100644 lib/gitlab/ci/config/entry/factory.rb create mode 100644 lib/gitlab/ci/config/entry/global.rb create mode 100644 lib/gitlab/ci/config/entry/hidden.rb create mode 100644 lib/gitlab/ci/config/entry/image.rb create mode 100644 lib/gitlab/ci/config/entry/job.rb create mode 100644 lib/gitlab/ci/config/entry/jobs.rb create mode 100644 lib/gitlab/ci/config/entry/key.rb create mode 100644 lib/gitlab/ci/config/entry/legacy_validation_helpers.rb create mode 100644 lib/gitlab/ci/config/entry/node.rb create mode 100644 lib/gitlab/ci/config/entry/paths.rb create mode 100644 lib/gitlab/ci/config/entry/script.rb create mode 100644 lib/gitlab/ci/config/entry/services.rb create mode 100644 lib/gitlab/ci/config/entry/stage.rb create mode 100644 lib/gitlab/ci/config/entry/stages.rb create mode 100644 lib/gitlab/ci/config/entry/trigger.rb create mode 100644 lib/gitlab/ci/config/entry/undefined.rb create mode 100644 lib/gitlab/ci/config/entry/unspecified.rb create mode 100644 lib/gitlab/ci/config/entry/validatable.rb create mode 100644 lib/gitlab/ci/config/entry/validator.rb create mode 100644 lib/gitlab/ci/config/entry/validators.rb create mode 100644 lib/gitlab/ci/config/entry/variables.rb delete mode 100644 lib/gitlab/ci/config/node/artifacts.rb delete mode 100644 lib/gitlab/ci/config/node/attributable.rb delete mode 100644 lib/gitlab/ci/config/node/boolean.rb delete mode 100644 lib/gitlab/ci/config/node/cache.rb delete mode 100644 lib/gitlab/ci/config/node/commands.rb delete mode 100644 lib/gitlab/ci/config/node/configurable.rb delete mode 100644 lib/gitlab/ci/config/node/entry.rb delete mode 100644 lib/gitlab/ci/config/node/environment.rb delete mode 100644 lib/gitlab/ci/config/node/factory.rb delete mode 100644 lib/gitlab/ci/config/node/global.rb delete mode 100644 lib/gitlab/ci/config/node/hidden.rb delete mode 100644 lib/gitlab/ci/config/node/image.rb delete mode 100644 lib/gitlab/ci/config/node/job.rb delete mode 100644 lib/gitlab/ci/config/node/jobs.rb delete mode 100644 lib/gitlab/ci/config/node/key.rb delete mode 100644 lib/gitlab/ci/config/node/legacy_validation_helpers.rb delete mode 100644 lib/gitlab/ci/config/node/paths.rb delete mode 100644 lib/gitlab/ci/config/node/script.rb delete mode 100644 lib/gitlab/ci/config/node/services.rb delete mode 100644 lib/gitlab/ci/config/node/stage.rb delete mode 100644 lib/gitlab/ci/config/node/stages.rb delete mode 100644 lib/gitlab/ci/config/node/trigger.rb delete mode 100644 lib/gitlab/ci/config/node/undefined.rb delete mode 100644 lib/gitlab/ci/config/node/unspecified.rb delete mode 100644 lib/gitlab/ci/config/node/validatable.rb delete mode 100644 lib/gitlab/ci/config/node/validator.rb delete mode 100644 lib/gitlab/ci/config/node/validators.rb delete mode 100644 lib/gitlab/ci/config/node/variables.rb (limited to 'lib') diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 3e33c9399e2..fef652cb975 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -2,7 +2,7 @@ module Ci class GitlabCiYamlProcessor class ValidationError < StandardError; end - include Gitlab::Ci::Config::Node::LegacyValidationHelpers + include Gitlab::Ci::Config::Entry::LegacyValidationHelpers attr_reader :path, :cache, :stages, :jobs diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index bbfa6cf7d05..06599238d22 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -13,7 +13,7 @@ module Gitlab def initialize(config) @config = Loader.new(config).load! - @global = Node::Global.new(@config) + @global = Entry::Global.new(@config) @global.compose! end diff --git a/lib/gitlab/ci/config/entry/artifacts.rb b/lib/gitlab/ci/config/entry/artifacts.rb new file mode 100644 index 00000000000..b756b0d4555 --- /dev/null +++ b/lib/gitlab/ci/config/entry/artifacts.rb @@ -0,0 +1,35 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a configuration of job artifacts. + # + class Artifacts < Node + include Validatable + include Attributable + + ALLOWED_KEYS = %i[name untracked paths when expire_in] + + attributes ALLOWED_KEYS + + validations do + validates :config, type: Hash + validates :config, allowed_keys: ALLOWED_KEYS + + with_options allow_nil: true do + validates :name, type: String + validates :untracked, boolean: true + validates :paths, array_of_strings: true + validates :when, + inclusion: { in: %w[on_success on_failure always], + message: 'should be on_success, on_failure ' \ + 'or always' } + validates :expire_in, duration: true + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/attributable.rb b/lib/gitlab/ci/config/entry/attributable.rb new file mode 100644 index 00000000000..1c8b55ee4c4 --- /dev/null +++ b/lib/gitlab/ci/config/entry/attributable.rb @@ -0,0 +1,23 @@ +module Gitlab + module Ci + class Config + module Entry + module Attributable + extend ActiveSupport::Concern + + class_methods do + def attributes(*attributes) + attributes.flatten.each do |attribute| + define_method(attribute) do + return unless config.is_a?(Hash) + + config[attribute] + end + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/boolean.rb b/lib/gitlab/ci/config/entry/boolean.rb new file mode 100644 index 00000000000..f3357f85b99 --- /dev/null +++ b/lib/gitlab/ci/config/entry/boolean.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a boolean value. + # + class Boolean < Node + include Validatable + + validations do + validates :config, boolean: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb new file mode 100644 index 00000000000..544e0953b17 --- /dev/null +++ b/lib/gitlab/ci/config/entry/cache.rb @@ -0,0 +1,29 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a cache configuration + # + class Cache < Node + include Configurable + + ALLOWED_KEYS = %i[key untracked paths] + + validations do + validates :config, allowed_keys: ALLOWED_KEYS + end + + node :key, Entry::Key, + description: 'Cache key used to define a cache affinity.' + + node :untracked, Entry::Boolean, + description: 'Cache all untracked files.' + + node :paths, Entry::Paths, + description: 'Specify which paths should be cached across builds.' + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/commands.rb b/lib/gitlab/ci/config/entry/commands.rb new file mode 100644 index 00000000000..65d19db249c --- /dev/null +++ b/lib/gitlab/ci/config/entry/commands.rb @@ -0,0 +1,33 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a job script. + # + class Commands < Node + include Validatable + + validations do + include LegacyValidationHelpers + + validate do + unless string_or_array_of_strings?(config) + errors.add(:config, + 'should be a string or an array of strings') + end + end + + def string_or_array_of_strings?(field) + validate_string(field) || validate_array_of_strings(field) + end + end + + def value + Array(@config) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/configurable.rb b/lib/gitlab/ci/config/entry/configurable.rb new file mode 100644 index 00000000000..b6c7b05e369 --- /dev/null +++ b/lib/gitlab/ci/config/entry/configurable.rb @@ -0,0 +1,78 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # This mixin is responsible for adding DSL, which purpose is to + # simplifly process of adding child nodes. + # + # This can be used only if parent node is a configuration entry that + # holds a hash as a configuration value, for example: + # + # job: + # script: ... + # artifacts: ... + # + module Configurable + extend ActiveSupport::Concern + include Validatable + + included do + validations do + validates :config, type: Hash + end + end + + def compose!(deps = nil) + return unless valid? + + self.class.nodes.each do |key, factory| + factory + .value(@config[key]) + .with(key: key, parent: self) + + @entries[key] = factory.create! + end + + yield if block_given? + + @entries.each_value do |entry| + entry.compose!(deps) + end + end + + class_methods do + def nodes + Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }] + end + + private # rubocop:disable Lint/UselessAccessModifier + + def node(key, node, metadata) + factory = Entry::Factory.new(node) + .with(description: metadata[:description]) + + (@nodes ||= {}).merge!(key.to_sym => factory) + end + + def helpers(*nodes) + nodes.each do |symbol| + define_method("#{symbol}_defined?") do + @entries[symbol].specified? if @entries[symbol] + end + + define_method("#{symbol}_value") do + return unless @entries[symbol] && @entries[symbol].valid? + + @entries[symbol].value + end + + alias_method symbol.to_sym, "#{symbol}_value".to_sym + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/environment.rb b/lib/gitlab/ci/config/entry/environment.rb new file mode 100644 index 00000000000..b7b4b91eb51 --- /dev/null +++ b/lib/gitlab/ci/config/entry/environment.rb @@ -0,0 +1,82 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents an environment. + # + class Environment < Node + include Validatable + + ALLOWED_KEYS = %i[name url action on_stop] + + validations do + validate do + unless hash? || string? + errors.add(:config, 'should be a hash or a string') + end + end + + validates :name, presence: true + validates :name, + type: { + with: String, + message: Gitlab::Regex.environment_name_regex_message } + + validates :name, + format: { + with: Gitlab::Regex.environment_name_regex, + message: Gitlab::Regex.environment_name_regex_message } + + with_options if: :hash? do + validates :config, allowed_keys: ALLOWED_KEYS + + validates :url, + length: { maximum: 255 }, + addressable_url: true, + allow_nil: true + + validates :action, + inclusion: { in: %w[start stop], message: 'should be start or stop' }, + allow_nil: true + + validates :on_stop, type: String, allow_nil: true + end + end + + def hash? + @config.is_a?(Hash) + end + + def string? + @config.is_a?(String) + end + + def name + value[:name] + end + + def url + value[:url] + end + + def action + value[:action] || 'start' + end + + def on_stop + value[:on_stop] + end + + def value + case @config + when String then { name: @config, action: 'start' } + when Hash then @config + else {} + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/factory.rb b/lib/gitlab/ci/config/entry/factory.rb new file mode 100644 index 00000000000..9f5e393d191 --- /dev/null +++ b/lib/gitlab/ci/config/entry/factory.rb @@ -0,0 +1,73 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Factory class responsible for fabricating entry objects. + # + class Factory + class InvalidFactory < StandardError; end + + def initialize(entry) + @entry = entry + @metadata = {} + @attributes = {} + end + + def value(value) + @value = value + self + end + + def metadata(metadata) + @metadata.merge!(metadata) + self + end + + def with(attributes) + @attributes.merge!(attributes) + self + end + + def create! + raise InvalidFactory unless defined?(@value) + + ## + # We assume that unspecified entry is undefined. + # See issue #18775. + # + if @value.nil? + Entry::Unspecified.new( + fabricate_unspecified + ) + else + fabricate(@entry, @value) + end + end + + private + + def fabricate_unspecified + ## + # If entry has a default value we fabricate concrete node + # with default value. + # + if @entry.default.nil? + fabricate(Entry::Undefined) + else + fabricate(@entry, @entry.default) + end + end + + def fabricate(entry, value = nil) + entry.new(value, @metadata).tap do |node| + node.key = @attributes[:key] + node.parent = @attributes[:parent] + node.description = @attributes[:description] + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/global.rb b/lib/gitlab/ci/config/entry/global.rb new file mode 100644 index 00000000000..cdf314a4b37 --- /dev/null +++ b/lib/gitlab/ci/config/entry/global.rb @@ -0,0 +1,72 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # This class represents a global entry - root Entry for entire + # GitLab CI Configuration file. + # + class Global < Node + include Configurable + + node :before_script, Entry::Script, + description: 'Script that will be executed before each job.' + + node :image, Entry::Image, + description: 'Docker image that will be used to execute jobs.' + + node :services, Entry::Services, + description: 'Docker images that will be linked to the container.' + + node :after_script, Entry::Script, + description: 'Script that will be executed after each job.' + + node :variables, Entry::Variables, + description: 'Environment variables that will be used.' + + node :stages, Entry::Stages, + description: 'Configuration of stages for this pipeline.' + + node :types, Entry::Stages, + description: 'Deprecated: stages for this pipeline.' + + node :cache, Entry::Cache, + description: 'Configure caching between build jobs.' + + helpers :before_script, :image, :services, :after_script, + :variables, :stages, :types, :cache, :jobs + + def compose!(_deps = nil) + super(self) do + compose_jobs! + compose_deprecated_entries! + end + end + + private + + def compose_jobs! + factory = Entry::Factory.new(Entry::Jobs) + .value(@config.except(*self.class.nodes.keys)) + .with(key: :jobs, parent: self, + description: 'Jobs definition for this pipeline') + + @entries[:jobs] = factory.create! + end + + def compose_deprecated_entries! + ## + # Deprecated `:types` key workaround - if types are defined and + # stages are not defined we use types definition as stages. + # + if types_defined? && !stages_defined? + @entries[:stages] = @entries[:types] + end + + @entries.delete(:types) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/hidden.rb b/lib/gitlab/ci/config/entry/hidden.rb new file mode 100644 index 00000000000..6fc3aa385bc --- /dev/null +++ b/lib/gitlab/ci/config/entry/hidden.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a hidden CI/CD key. + # + class Hidden < Node + include Validatable + + validations do + validates :config, presence: true + end + + def relevant? + false + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb new file mode 100644 index 00000000000..b5050257688 --- /dev/null +++ b/lib/gitlab/ci/config/entry/image.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a Docker image. + # + class Image < Node + include Validatable + + validations do + validates :config, type: String + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb new file mode 100644 index 00000000000..b4e69a04706 --- /dev/null +++ b/lib/gitlab/ci/config/entry/job.rb @@ -0,0 +1,140 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a concrete CI/CD job. + # + class Job < Node + include Configurable + include Attributable + + ALLOWED_KEYS = %i[tags script only except type image services allow_failure + type stage when artifacts cache dependencies before_script + after_script variables environment] + + attributes :tags, :allow_failure, :when, :dependencies + + validations do + validates :config, allowed_keys: ALLOWED_KEYS + + validates :config, presence: true + validates :name, presence: true + validates :name, type: Symbol + + with_options allow_nil: true do + validates :tags, array_of_strings: true + validates :allow_failure, boolean: true + validates :when, + inclusion: { in: %w[on_success on_failure always manual], + message: 'should be on_success, on_failure, ' \ + 'always or manual' } + + validates :dependencies, array_of_strings: true + end + end + + node :before_script, Entry::Script, + description: 'Global before script overridden in this job.' + + node :script, Entry::Commands, + description: 'Commands that will be executed in this job.' + + node :stage, Entry::Stage, + description: 'Pipeline stage this job will be executed into.' + + node :type, Entry::Stage, + description: 'Deprecated: stage this job will be executed into.' + + node :after_script, Entry::Script, + description: 'Commands that will be executed when finishing job.' + + node :cache, Entry::Cache, + description: 'Cache definition for this job.' + + node :image, Entry::Image, + description: 'Image that will be used to execute this job.' + + node :services, Entry::Services, + description: 'Services that will be used to execute this job.' + + node :only, Entry::Trigger, + description: 'Refs policy this job will be executed for.' + + node :except, Entry::Trigger, + description: 'Refs policy this job will be executed for.' + + node :variables, Entry::Variables, + description: 'Environment variables available for this job.' + + node :artifacts, Entry::Artifacts, + description: 'Artifacts configuration for this job.' + + node :environment, Entry::Environment, + description: 'Environment configuration for this job.' + + helpers :before_script, :script, :stage, :type, :after_script, + :cache, :image, :services, :only, :except, :variables, + :artifacts, :commands, :environment + + def compose!(deps = nil) + super do + if type_defined? && !stage_defined? + @entries[:stage] = @entries[:type] + end + + @entries.delete(:type) + end + + inherit!(deps) + end + + def name + @metadata[:name] + end + + def value + @config.merge(to_hash.compact) + end + + def commands + (before_script_value.to_a + script_value.to_a).join("\n") + end + + private + + def inherit!(deps) + return unless deps + + self.class.nodes.each_key do |key| + global_entry = deps[key] + job_entry = @entries[key] + + if global_entry.specified? && !job_entry.specified? + @entries[key] = global_entry + end + end + end + + def to_hash + { name: name, + before_script: before_script, + script: script, + commands: commands, + image: image, + services: services, + stage: stage, + cache: cache, + only: only, + except: except, + variables: variables_defined? ? variables : nil, + environment: environment_defined? ? environment : nil, + environment_name: environment_defined? ? environment[:name] : nil, + artifacts: artifacts, + after_script: after_script } + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/jobs.rb b/lib/gitlab/ci/config/entry/jobs.rb new file mode 100644 index 00000000000..5671a09480b --- /dev/null +++ b/lib/gitlab/ci/config/entry/jobs.rb @@ -0,0 +1,52 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a set of jobs. + # + class Jobs < Node + include Validatable + + validations do + validates :config, type: Hash + + validate do + unless has_visible_job? + errors.add(:config, 'should contain at least one visible job') + end + end + + def has_visible_job? + config.any? { |name, _| !hidden?(name) } + end + end + + def hidden?(name) + name.to_s.start_with?('.') + end + + def compose!(deps = nil) + super do + @config.each do |name, config| + node = hidden?(name) ? Entry::Hidden : Entry::Job + + factory = Entry::Factory.new(node) + .value(config || {}) + .metadata(name: name) + .with(key: name, parent: self, + description: "#{name} job definition.") + + @entries[name] = factory.create! + end + + @entries.each_value do |entry| + entry.compose!(deps) + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/key.rb b/lib/gitlab/ci/config/entry/key.rb new file mode 100644 index 00000000000..0e4c9fe6edc --- /dev/null +++ b/lib/gitlab/ci/config/entry/key.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a key. + # + class Key < Node + include Validatable + + validations do + validates :config, key: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb new file mode 100644 index 00000000000..f01975aab5c --- /dev/null +++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb @@ -0,0 +1,51 @@ +module Gitlab + module Ci + class Config + module Entry + module LegacyValidationHelpers + private + + def validate_duration(value) + value.is_a?(String) && ChronicDuration.parse(value) + rescue ChronicDuration::DurationParseError + false + end + + def validate_array_of_strings(values) + values.is_a?(Array) && values.all? { |value| validate_string(value) } + end + + def validate_array_of_strings_or_regexps(values) + values.is_a?(Array) && values.all? { |value| validate_string_or_regexp(value) } + end + + def validate_variables(variables) + variables.is_a?(Hash) && + variables.all? { |key, value| validate_string(key) && validate_string(value) } + end + + def validate_string(value) + value.is_a?(String) || value.is_a?(Symbol) + end + + def validate_string_or_regexp(value) + return true if value.is_a?(Symbol) + return false unless value.is_a?(String) + + if value.first == '/' && value.last == '/' + Regexp.new(value[1...-1]) + else + true + end + rescue RegexpError + false + end + + def validate_boolean(value) + value.in?([true, false]) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb new file mode 100644 index 00000000000..5eef2868cd6 --- /dev/null +++ b/lib/gitlab/ci/config/entry/node.rb @@ -0,0 +1,83 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Base abstract class for each configuration entry node. + # + class Node + class InvalidError < StandardError; end + + attr_reader :config, :metadata + attr_accessor :key, :parent, :description + + def initialize(config, **metadata) + @config = config + @metadata = metadata + @entries = {} + + @validator = self.class.validator.new(self) + @validator.validate(:new) + end + + def [](key) + @entries[key] || Entry::Undefined.new + end + + def compose!(deps = nil) + return unless valid? + + yield if block_given? + end + + def leaf? + @entries.none? + end + + def descendants + @entries.values + end + + def ancestors + @parent ? @parent.ancestors + [@parent] : [] + end + + def valid? + errors.none? + end + + def errors + @validator.messages + descendants.flat_map(&:errors) + end + + def value + if leaf? + @config + else + meaningful = @entries.select do |_key, value| + value.specified? && value.relevant? + end + + Hash[meaningful.map { |key, entry| [key, entry.value] }] + end + end + + def specified? + true + end + + def relevant? + true + end + + def self.default + end + + def self.validator + Validator + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/paths.rb b/lib/gitlab/ci/config/entry/paths.rb new file mode 100644 index 00000000000..68dad161149 --- /dev/null +++ b/lib/gitlab/ci/config/entry/paths.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents an array of paths. + # + class Paths < Node + include Validatable + + validations do + validates :config, array_of_strings: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/script.rb b/lib/gitlab/ci/config/entry/script.rb new file mode 100644 index 00000000000..29ecd9995ca --- /dev/null +++ b/lib/gitlab/ci/config/entry/script.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a script. + # + class Script < Node + include Validatable + + validations do + validates :config, array_of_strings: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/services.rb b/lib/gitlab/ci/config/entry/services.rb new file mode 100644 index 00000000000..84f8ab780f5 --- /dev/null +++ b/lib/gitlab/ci/config/entry/services.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a configuration of Docker services. + # + class Services < Node + include Validatable + + validations do + validates :config, array_of_strings: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/stage.rb b/lib/gitlab/ci/config/entry/stage.rb new file mode 100644 index 00000000000..b7afaba1de8 --- /dev/null +++ b/lib/gitlab/ci/config/entry/stage.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a stage for a job. + # + class Stage < Node + include Validatable + + validations do + validates :config, type: String + end + + def self.default + 'test' + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/stages.rb b/lib/gitlab/ci/config/entry/stages.rb new file mode 100644 index 00000000000..ec187bd3732 --- /dev/null +++ b/lib/gitlab/ci/config/entry/stages.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a configuration for pipeline stages. + # + class Stages < Node + include Validatable + + validations do + validates :config, array_of_strings: true + end + + def self.default + %w[build test deploy] + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/trigger.rb b/lib/gitlab/ci/config/entry/trigger.rb new file mode 100644 index 00000000000..28b0a9ffe01 --- /dev/null +++ b/lib/gitlab/ci/config/entry/trigger.rb @@ -0,0 +1,26 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents a trigger policy for the job. + # + class Trigger < Node + include Validatable + + validations do + include LegacyValidationHelpers + + validate :array_of_strings_or_regexps + + def array_of_strings_or_regexps + unless validate_array_of_strings_or_regexps(config) + errors.add(:config, 'should be an array of strings or regexps') + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/undefined.rb b/lib/gitlab/ci/config/entry/undefined.rb new file mode 100644 index 00000000000..b33b8238230 --- /dev/null +++ b/lib/gitlab/ci/config/entry/undefined.rb @@ -0,0 +1,36 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # This class represents an undefined entry. + # + class Undefined < Node + def initialize(*) + super(nil) + end + + def value + nil + end + + def valid? + true + end + + def errors + [] + end + + def specified? + false + end + + def relevant? + false + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/unspecified.rb b/lib/gitlab/ci/config/entry/unspecified.rb new file mode 100644 index 00000000000..fbb2551e870 --- /dev/null +++ b/lib/gitlab/ci/config/entry/unspecified.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # This class represents an unspecified entry. + # + # It decorates original entry adding method that indicates it is + # unspecified. + # + class Unspecified < SimpleDelegator + def specified? + false + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/validatable.rb b/lib/gitlab/ci/config/entry/validatable.rb new file mode 100644 index 00000000000..f7f1b111571 --- /dev/null +++ b/lib/gitlab/ci/config/entry/validatable.rb @@ -0,0 +1,27 @@ +module Gitlab + module Ci + class Config + module Entry + module Validatable + extend ActiveSupport::Concern + + class_methods do + def validator + @validator ||= Class.new(Entry::Validator).tap do |validator| + if defined?(@validations) + @validations.each { |rules| validator.class_eval(&rules) } + end + end + end + + private + + def validations(&block) + (@validations ||= []).append(block) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/validator.rb b/lib/gitlab/ci/config/entry/validator.rb new file mode 100644 index 00000000000..55343005fe3 --- /dev/null +++ b/lib/gitlab/ci/config/entry/validator.rb @@ -0,0 +1,42 @@ +module Gitlab + module Ci + class Config + module Entry + class Validator < SimpleDelegator + include ActiveModel::Validations + include Entry::Validators + + def initialize(entry) + super(entry) + @entry = entry + end + + def messages + errors.full_messages.map do |error| + "#{location} #{error}".downcase + end + end + + def self.name + 'Validator' + end + + private + + def location + predecessors = ancestors.map(&:key).compact + predecessors.append(key_name).join(':') + end + + def key_name + if key.blank? + @entry.class.name.demodulize.underscore.humanize + else + key + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb new file mode 100644 index 00000000000..8632dd0e233 --- /dev/null +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -0,0 +1,82 @@ +module Gitlab + module Ci + class Config + module Entry + module Validators + class AllowedKeysValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + unknown_keys = record.config.try(:keys).to_a - options[:in] + + if unknown_keys.any? + record.errors.add(:config, 'contains unknown keys: ' + + unknown_keys.join(', ')) + end + end + end + + class ArrayOfStringsValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_array_of_strings(value) + record.errors.add(attribute, 'should be an array of strings') + end + end + end + + class BooleanValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_boolean(value) + record.errors.add(attribute, 'should be a boolean value') + end + end + end + + class DurationValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_duration(value) + record.errors.add(attribute, 'should be a duration') + end + end + end + + class KeyValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_string(value) + record.errors.add(attribute, 'should be a string or symbol') + end + end + end + + class TypeValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + type = options[:with] + raise unless type.is_a?(Class) + + unless value.is_a?(type) + message = options[:message] || "should be a #{type.name}" + record.errors.add(attribute, message) + end + end + end + + class VariablesValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_variables(value) + record.errors.add(attribute, 'should be a hash of key value pairs') + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/variables.rb b/lib/gitlab/ci/config/entry/variables.rb new file mode 100644 index 00000000000..f615874fa68 --- /dev/null +++ b/lib/gitlab/ci/config/entry/variables.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents environment variables. + # + class Variables < Node + include Validatable + + validations do + validates :config, variables: true + end + + def self.default + {} + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/artifacts.rb b/lib/gitlab/ci/config/node/artifacts.rb deleted file mode 100644 index 844bd2fe998..00000000000 --- a/lib/gitlab/ci/config/node/artifacts.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a configuration of job artifacts. - # - class Artifacts < Entry - include Validatable - include Attributable - - ALLOWED_KEYS = %i[name untracked paths when expire_in] - - attributes ALLOWED_KEYS - - validations do - validates :config, type: Hash - validates :config, allowed_keys: ALLOWED_KEYS - - with_options allow_nil: true do - validates :name, type: String - validates :untracked, boolean: true - validates :paths, array_of_strings: true - validates :when, - inclusion: { in: %w[on_success on_failure always], - message: 'should be on_success, on_failure ' \ - 'or always' } - validates :expire_in, duration: true - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/attributable.rb b/lib/gitlab/ci/config/node/attributable.rb deleted file mode 100644 index 221b666f9f6..00000000000 --- a/lib/gitlab/ci/config/node/attributable.rb +++ /dev/null @@ -1,23 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - module Attributable - extend ActiveSupport::Concern - - class_methods do - def attributes(*attributes) - attributes.flatten.each do |attribute| - define_method(attribute) do - return unless config.is_a?(Hash) - - config[attribute] - end - end - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/boolean.rb b/lib/gitlab/ci/config/node/boolean.rb deleted file mode 100644 index 84b03ee7832..00000000000 --- a/lib/gitlab/ci/config/node/boolean.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a boolean value. - # - class Boolean < Entry - include Validatable - - validations do - validates :config, boolean: true - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb deleted file mode 100644 index b4bda2841ac..00000000000 --- a/lib/gitlab/ci/config/node/cache.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a cache configuration - # - class Cache < Entry - include Configurable - - ALLOWED_KEYS = %i[key untracked paths] - - validations do - validates :config, allowed_keys: ALLOWED_KEYS - end - - node :key, Node::Key, - description: 'Cache key used to define a cache affinity.' - - node :untracked, Node::Boolean, - description: 'Cache all untracked files.' - - node :paths, Node::Paths, - description: 'Specify which paths should be cached across builds.' - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/commands.rb b/lib/gitlab/ci/config/node/commands.rb deleted file mode 100644 index d7657ae314b..00000000000 --- a/lib/gitlab/ci/config/node/commands.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a job script. - # - class Commands < Entry - include Validatable - - validations do - include LegacyValidationHelpers - - validate do - unless string_or_array_of_strings?(config) - errors.add(:config, - 'should be a string or an array of strings') - end - end - - def string_or_array_of_strings?(field) - validate_string(field) || validate_array_of_strings(field) - end - end - - def value - Array(@config) - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb deleted file mode 100644 index 6b7ab2fdaf2..00000000000 --- a/lib/gitlab/ci/config/node/configurable.rb +++ /dev/null @@ -1,78 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This mixin is responsible for adding DSL, which purpose is to - # simplifly process of adding child nodes. - # - # This can be used only if parent node is a configuration entry that - # holds a hash as a configuration value, for example: - # - # job: - # script: ... - # artifacts: ... - # - module Configurable - extend ActiveSupport::Concern - include Validatable - - included do - validations do - validates :config, type: Hash - end - end - - def compose!(deps = nil) - return unless valid? - - self.class.nodes.each do |key, factory| - factory - .value(@config[key]) - .with(key: key, parent: self) - - @entries[key] = factory.create! - end - - yield if block_given? - - @entries.each_value do |entry| - entry.compose!(deps) - end - end - - class_methods do - def nodes - Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }] - end - - private # rubocop:disable Lint/UselessAccessModifier - - def node(key, node, metadata) - factory = Node::Factory.new(node) - .with(description: metadata[:description]) - - (@nodes ||= {}).merge!(key.to_sym => factory) - end - - def helpers(*nodes) - nodes.each do |symbol| - define_method("#{symbol}_defined?") do - @entries[symbol].specified? if @entries[symbol] - end - - define_method("#{symbol}_value") do - return unless @entries[symbol] && @entries[symbol].valid? - - @entries[symbol].value - end - - alias_method symbol.to_sym, "#{symbol}_value".to_sym - end - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb deleted file mode 100644 index 8717eabf81e..00000000000 --- a/lib/gitlab/ci/config/node/entry.rb +++ /dev/null @@ -1,83 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Base abstract class for each configuration entry node. - # - class Entry - class InvalidError < StandardError; end - - attr_reader :config, :metadata - attr_accessor :key, :parent, :description - - def initialize(config, **metadata) - @config = config - @metadata = metadata - @entries = {} - - @validator = self.class.validator.new(self) - @validator.validate(:new) - end - - def [](key) - @entries[key] || Node::Undefined.new - end - - def compose!(deps = nil) - return unless valid? - - yield if block_given? - end - - def leaf? - @entries.none? - end - - def descendants - @entries.values - end - - def ancestors - @parent ? @parent.ancestors + [@parent] : [] - end - - def valid? - errors.none? - end - - def errors - @validator.messages + descendants.flat_map(&:errors) - end - - def value - if leaf? - @config - else - meaningful = @entries.select do |_key, value| - value.specified? && value.relevant? - end - - Hash[meaningful.map { |key, entry| [key, entry.value] }] - end - end - - def specified? - true - end - - def relevant? - true - end - - def self.default - end - - def self.validator - Validator - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb deleted file mode 100644 index 9a95ef43628..00000000000 --- a/lib/gitlab/ci/config/node/environment.rb +++ /dev/null @@ -1,82 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents an environment. - # - class Environment < Entry - include Validatable - - ALLOWED_KEYS = %i[name url action on_stop] - - validations do - validate do - unless hash? || string? - errors.add(:config, 'should be a hash or a string') - end - end - - validates :name, presence: true - validates :name, - type: { - with: String, - message: Gitlab::Regex.environment_name_regex_message } - - validates :name, - format: { - with: Gitlab::Regex.environment_name_regex, - message: Gitlab::Regex.environment_name_regex_message } - - with_options if: :hash? do - validates :config, allowed_keys: ALLOWED_KEYS - - validates :url, - length: { maximum: 255 }, - addressable_url: true, - allow_nil: true - - validates :action, - inclusion: { in: %w[start stop], message: 'should be start or stop' }, - allow_nil: true - - validates :on_stop, type: String, allow_nil: true - end - end - - def hash? - @config.is_a?(Hash) - end - - def string? - @config.is_a?(String) - end - - def name - value[:name] - end - - def url - value[:url] - end - - def action - value[:action] || 'start' - end - - def on_stop - value[:on_stop] - end - - def value - case @config - when String then { name: @config, action: 'start' } - when Hash then @config - else {} - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb deleted file mode 100644 index 5387f29ad59..00000000000 --- a/lib/gitlab/ci/config/node/factory.rb +++ /dev/null @@ -1,73 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Factory class responsible for fabricating node entry objects. - # - class Factory - class InvalidFactory < StandardError; end - - def initialize(node) - @node = node - @metadata = {} - @attributes = {} - end - - def value(value) - @value = value - self - end - - def metadata(metadata) - @metadata.merge!(metadata) - self - end - - def with(attributes) - @attributes.merge!(attributes) - self - end - - def create! - raise InvalidFactory unless defined?(@value) - - ## - # We assume that unspecified entry is undefined. - # See issue #18775. - # - if @value.nil? - Node::Unspecified.new( - fabricate_unspecified - ) - else - fabricate(@node, @value) - end - end - - private - - def fabricate_unspecified - ## - # If node has a default value we fabricate concrete node - # with default value. - # - if @node.default.nil? - fabricate(Node::Undefined) - else - fabricate(@node, @node.default) - end - end - - def fabricate(node, value = nil) - node.new(value, @metadata).tap do |entry| - entry.key = @attributes[:key] - entry.parent = @attributes[:parent] - entry.description = @attributes[:description] - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb deleted file mode 100644 index 2a2943c9288..00000000000 --- a/lib/gitlab/ci/config/node/global.rb +++ /dev/null @@ -1,72 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This class represents a global entry - root node for entire - # GitLab CI Configuration file. - # - class Global < Entry - include Configurable - - node :before_script, Node::Script, - description: 'Script that will be executed before each job.' - - node :image, Node::Image, - description: 'Docker image that will be used to execute jobs.' - - node :services, Node::Services, - description: 'Docker images that will be linked to the container.' - - node :after_script, Node::Script, - description: 'Script that will be executed after each job.' - - node :variables, Node::Variables, - description: 'Environment variables that will be used.' - - node :stages, Node::Stages, - description: 'Configuration of stages for this pipeline.' - - node :types, Node::Stages, - description: 'Deprecated: stages for this pipeline.' - - node :cache, Node::Cache, - description: 'Configure caching between build jobs.' - - helpers :before_script, :image, :services, :after_script, - :variables, :stages, :types, :cache, :jobs - - def compose!(_deps = nil) - super(self) do - compose_jobs! - compose_deprecated_entries! - end - end - - private - - def compose_jobs! - factory = Node::Factory.new(Node::Jobs) - .value(@config.except(*self.class.nodes.keys)) - .with(key: :jobs, parent: self, - description: 'Jobs definition for this pipeline') - - @entries[:jobs] = factory.create! - end - - def compose_deprecated_entries! - ## - # Deprecated `:types` key workaround - if types are defined and - # stages are not defined we use types definition as stages. - # - if types_defined? && !stages_defined? - @entries[:stages] = @entries[:types] - end - - @entries.delete(:types) - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/hidden.rb b/lib/gitlab/ci/config/node/hidden.rb deleted file mode 100644 index fe4ee8a7fc6..00000000000 --- a/lib/gitlab/ci/config/node/hidden.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a hidden CI/CD job. - # - class Hidden < Entry - include Validatable - - validations do - validates :config, presence: true - end - - def relevant? - false - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/image.rb b/lib/gitlab/ci/config/node/image.rb deleted file mode 100644 index 5d3c7c5eab0..00000000000 --- a/lib/gitlab/ci/config/node/image.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a Docker image. - # - class Image < Entry - include Validatable - - validations do - validates :config, type: String - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb deleted file mode 100644 index 603334d6793..00000000000 --- a/lib/gitlab/ci/config/node/job.rb +++ /dev/null @@ -1,140 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a concrete CI/CD job. - # - class Job < Entry - include Configurable - include Attributable - - ALLOWED_KEYS = %i[tags script only except type image services allow_failure - type stage when artifacts cache dependencies before_script - after_script variables environment] - - attributes :tags, :allow_failure, :when, :dependencies - - validations do - validates :config, allowed_keys: ALLOWED_KEYS - - validates :config, presence: true - validates :name, presence: true - validates :name, type: Symbol - - with_options allow_nil: true do - validates :tags, array_of_strings: true - validates :allow_failure, boolean: true - validates :when, - inclusion: { in: %w[on_success on_failure always manual], - message: 'should be on_success, on_failure, ' \ - 'always or manual' } - - validates :dependencies, array_of_strings: true - end - end - - node :before_script, Node::Script, - description: 'Global before script overridden in this job.' - - node :script, Node::Commands, - description: 'Commands that will be executed in this job.' - - node :stage, Node::Stage, - description: 'Pipeline stage this job will be executed into.' - - node :type, Node::Stage, - description: 'Deprecated: stage this job will be executed into.' - - node :after_script, Node::Script, - description: 'Commands that will be executed when finishing job.' - - node :cache, Node::Cache, - description: 'Cache definition for this job.' - - node :image, Node::Image, - description: 'Image that will be used to execute this job.' - - node :services, Node::Services, - description: 'Services that will be used to execute this job.' - - node :only, Node::Trigger, - description: 'Refs policy this job will be executed for.' - - node :except, Node::Trigger, - description: 'Refs policy this job will be executed for.' - - node :variables, Node::Variables, - description: 'Environment variables available for this job.' - - node :artifacts, Node::Artifacts, - description: 'Artifacts configuration for this job.' - - node :environment, Node::Environment, - description: 'Environment configuration for this job.' - - helpers :before_script, :script, :stage, :type, :after_script, - :cache, :image, :services, :only, :except, :variables, - :artifacts, :commands, :environment - - def compose!(deps = nil) - super do - if type_defined? && !stage_defined? - @entries[:stage] = @entries[:type] - end - - @entries.delete(:type) - end - - inherit!(deps) - end - - def name - @metadata[:name] - end - - def value - @config.merge(to_hash.compact) - end - - def commands - (before_script_value.to_a + script_value.to_a).join("\n") - end - - private - - def inherit!(deps) - return unless deps - - self.class.nodes.each_key do |key| - global_entry = deps[key] - job_entry = @entries[key] - - if global_entry.specified? && !job_entry.specified? - @entries[key] = global_entry - end - end - end - - def to_hash - { name: name, - before_script: before_script, - script: script, - commands: commands, - image: image, - services: services, - stage: stage, - cache: cache, - only: only, - except: except, - variables: variables_defined? ? variables : nil, - environment: environment_defined? ? environment : nil, - environment_name: environment_defined? ? environment[:name] : nil, - artifacts: artifacts, - after_script: after_script } - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb deleted file mode 100644 index d10e80d1a7d..00000000000 --- a/lib/gitlab/ci/config/node/jobs.rb +++ /dev/null @@ -1,52 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a set of jobs. - # - class Jobs < Entry - include Validatable - - validations do - validates :config, type: Hash - - validate do - unless has_visible_job? - errors.add(:config, 'should contain at least one visible job') - end - end - - def has_visible_job? - config.any? { |name, _| !hidden?(name) } - end - end - - def hidden?(name) - name.to_s.start_with?('.') - end - - def compose!(deps = nil) - super do - @config.each do |name, config| - node = hidden?(name) ? Node::Hidden : Node::Job - - factory = Node::Factory.new(node) - .value(config || {}) - .metadata(name: name) - .with(key: name, parent: self, - description: "#{name} job definition.") - - @entries[name] = factory.create! - end - - @entries.each_value do |entry| - entry.compose!(deps) - end - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/key.rb b/lib/gitlab/ci/config/node/key.rb deleted file mode 100644 index f8b461ca098..00000000000 --- a/lib/gitlab/ci/config/node/key.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a key. - # - class Key < Entry - include Validatable - - validations do - validates :config, key: true - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/legacy_validation_helpers.rb b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb deleted file mode 100644 index 0c291efe6a5..00000000000 --- a/lib/gitlab/ci/config/node/legacy_validation_helpers.rb +++ /dev/null @@ -1,51 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - module LegacyValidationHelpers - private - - def validate_duration(value) - value.is_a?(String) && ChronicDuration.parse(value) - rescue ChronicDuration::DurationParseError - false - end - - def validate_array_of_strings(values) - values.is_a?(Array) && values.all? { |value| validate_string(value) } - end - - def validate_array_of_strings_or_regexps(values) - values.is_a?(Array) && values.all? { |value| validate_string_or_regexp(value) } - end - - def validate_variables(variables) - variables.is_a?(Hash) && - variables.all? { |key, value| validate_string(key) && validate_string(value) } - end - - def validate_string(value) - value.is_a?(String) || value.is_a?(Symbol) - end - - def validate_string_or_regexp(value) - return true if value.is_a?(Symbol) - return false unless value.is_a?(String) - - if value.first == '/' && value.last == '/' - Regexp.new(value[1...-1]) - else - true - end - rescue RegexpError - false - end - - def validate_boolean(value) - value.in?([true, false]) - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/paths.rb b/lib/gitlab/ci/config/node/paths.rb deleted file mode 100644 index 3c6d3a52966..00000000000 --- a/lib/gitlab/ci/config/node/paths.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents an array of paths. - # - class Paths < Entry - include Validatable - - validations do - validates :config, array_of_strings: true - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/script.rb b/lib/gitlab/ci/config/node/script.rb deleted file mode 100644 index 39328f0fade..00000000000 --- a/lib/gitlab/ci/config/node/script.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a script. - # - class Script < Entry - include Validatable - - validations do - validates :config, array_of_strings: true - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/services.rb b/lib/gitlab/ci/config/node/services.rb deleted file mode 100644 index 481e2b66adc..00000000000 --- a/lib/gitlab/ci/config/node/services.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a configuration of Docker services. - # - class Services < Entry - include Validatable - - validations do - validates :config, array_of_strings: true - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb deleted file mode 100644 index cbc97641f5a..00000000000 --- a/lib/gitlab/ci/config/node/stage.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a stage for a job. - # - class Stage < Entry - include Validatable - - validations do - validates :config, type: String - end - - def self.default - 'test' - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/stages.rb b/lib/gitlab/ci/config/node/stages.rb deleted file mode 100644 index b1fe45357ff..00000000000 --- a/lib/gitlab/ci/config/node/stages.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a configuration for pipeline stages. - # - class Stages < Entry - include Validatable - - validations do - validates :config, array_of_strings: true - end - - def self.default - %w[build test deploy] - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/trigger.rb b/lib/gitlab/ci/config/node/trigger.rb deleted file mode 100644 index d8b31975088..00000000000 --- a/lib/gitlab/ci/config/node/trigger.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a trigger policy for the job. - # - class Trigger < Entry - include Validatable - - validations do - include LegacyValidationHelpers - - validate :array_of_strings_or_regexps - - def array_of_strings_or_regexps - unless validate_array_of_strings_or_regexps(config) - errors.add(:config, 'should be an array of strings or regexps') - end - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb deleted file mode 100644 index 33e78023539..00000000000 --- a/lib/gitlab/ci/config/node/undefined.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This class represents an undefined node. - # - # Implements the Null Object pattern. - # - class Undefined < Entry - def initialize(*) - super(nil) - end - - def value - nil - end - - def valid? - true - end - - def errors - [] - end - - def specified? - false - end - - def relevant? - false - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/unspecified.rb b/lib/gitlab/ci/config/node/unspecified.rb deleted file mode 100644 index a7d1f6131b8..00000000000 --- a/lib/gitlab/ci/config/node/unspecified.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This class represents an unspecified entry node. - # - # It decorates original entry adding method that indicates it is - # unspecified. - # - class Unspecified < SimpleDelegator - def specified? - false - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/validatable.rb b/lib/gitlab/ci/config/node/validatable.rb deleted file mode 100644 index 085e6e988d1..00000000000 --- a/lib/gitlab/ci/config/node/validatable.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - module Validatable - extend ActiveSupport::Concern - - class_methods do - def validator - @validator ||= Class.new(Node::Validator).tap do |validator| - if defined?(@validations) - @validations.each { |rules| validator.class_eval(&rules) } - end - end - end - - private - - def validations(&block) - (@validations ||= []).append(block) - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb deleted file mode 100644 index 43c7e102b50..00000000000 --- a/lib/gitlab/ci/config/node/validator.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - class Validator < SimpleDelegator - include ActiveModel::Validations - include Node::Validators - - def initialize(node) - super(node) - @node = node - end - - def messages - errors.full_messages.map do |error| - "#{location} #{error}".downcase - end - end - - def self.name - 'Validator' - end - - private - - def location - predecessors = ancestors.map(&:key).compact - predecessors.append(key_name).join(':') - end - - def key_name - if key.blank? - @node.class.name.demodulize.underscore.humanize - else - key - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb deleted file mode 100644 index e20908ad3cb..00000000000 --- a/lib/gitlab/ci/config/node/validators.rb +++ /dev/null @@ -1,82 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - module Validators - class AllowedKeysValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - unknown_keys = record.config.try(:keys).to_a - options[:in] - - if unknown_keys.any? - record.errors.add(:config, 'contains unknown keys: ' + - unknown_keys.join(', ')) - end - end - end - - class ArrayOfStringsValidator < ActiveModel::EachValidator - include LegacyValidationHelpers - - def validate_each(record, attribute, value) - unless validate_array_of_strings(value) - record.errors.add(attribute, 'should be an array of strings') - end - end - end - - class BooleanValidator < ActiveModel::EachValidator - include LegacyValidationHelpers - - def validate_each(record, attribute, value) - unless validate_boolean(value) - record.errors.add(attribute, 'should be a boolean value') - end - end - end - - class DurationValidator < ActiveModel::EachValidator - include LegacyValidationHelpers - - def validate_each(record, attribute, value) - unless validate_duration(value) - record.errors.add(attribute, 'should be a duration') - end - end - end - - class KeyValidator < ActiveModel::EachValidator - include LegacyValidationHelpers - - def validate_each(record, attribute, value) - unless validate_string(value) - record.errors.add(attribute, 'should be a string or symbol') - end - end - end - - class TypeValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - type = options[:with] - raise unless type.is_a?(Class) - - unless value.is_a?(type) - message = options[:message] || "should be a #{type.name}" - record.errors.add(attribute, message) - end - end - end - - class VariablesValidator < ActiveModel::EachValidator - include LegacyValidationHelpers - - def validate_each(record, attribute, value) - unless validate_variables(value) - record.errors.add(attribute, 'should be a hash of key value pairs') - end - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/variables.rb b/lib/gitlab/ci/config/node/variables.rb deleted file mode 100644 index 5f813f81f55..00000000000 --- a/lib/gitlab/ci/config/node/variables.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents environment variables. - # - class Variables < Entry - include Validatable - - validations do - validates :config, variables: true - end - - def self.default - {} - end - end - end - end - end -end -- cgit v1.2.1 From a761d293662e48efca9c0607f0624ba8eec1b634 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 14 Nov 2016 10:51:56 +0100 Subject: Change name of method for setting CI config entries --- lib/gitlab/ci/config/entry/cache.rb | 6 +++--- lib/gitlab/ci/config/entry/configurable.rb | 4 ++-- lib/gitlab/ci/config/entry/global.rb | 16 ++++++++-------- lib/gitlab/ci/config/entry/job.rb | 26 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb index 544e0953b17..7653cab668b 100644 --- a/lib/gitlab/ci/config/entry/cache.rb +++ b/lib/gitlab/ci/config/entry/cache.rb @@ -14,13 +14,13 @@ module Gitlab validates :config, allowed_keys: ALLOWED_KEYS end - node :key, Entry::Key, + entry :key, Entry::Key, description: 'Cache key used to define a cache affinity.' - node :untracked, Entry::Boolean, + entry :untracked, Entry::Boolean, description: 'Cache all untracked files.' - node :paths, Entry::Paths, + entry :paths, Entry::Paths, description: 'Specify which paths should be cached across builds.' end end diff --git a/lib/gitlab/ci/config/entry/configurable.rb b/lib/gitlab/ci/config/entry/configurable.rb index b6c7b05e369..0f438faeda2 100644 --- a/lib/gitlab/ci/config/entry/configurable.rb +++ b/lib/gitlab/ci/config/entry/configurable.rb @@ -48,8 +48,8 @@ module Gitlab private # rubocop:disable Lint/UselessAccessModifier - def node(key, node, metadata) - factory = Entry::Factory.new(node) + def entry(key, entry, metadata) + factory = Entry::Factory.new(entry) .with(description: metadata[:description]) (@nodes ||= {}).merge!(key.to_sym => factory) diff --git a/lib/gitlab/ci/config/entry/global.rb b/lib/gitlab/ci/config/entry/global.rb index cdf314a4b37..a4ec8f0ff2f 100644 --- a/lib/gitlab/ci/config/entry/global.rb +++ b/lib/gitlab/ci/config/entry/global.rb @@ -9,28 +9,28 @@ module Gitlab class Global < Node include Configurable - node :before_script, Entry::Script, + entry :before_script, Entry::Script, description: 'Script that will be executed before each job.' - node :image, Entry::Image, + entry :image, Entry::Image, description: 'Docker image that will be used to execute jobs.' - node :services, Entry::Services, + entry :services, Entry::Services, description: 'Docker images that will be linked to the container.' - node :after_script, Entry::Script, + entry :after_script, Entry::Script, description: 'Script that will be executed after each job.' - node :variables, Entry::Variables, + entry :variables, Entry::Variables, description: 'Environment variables that will be used.' - node :stages, Entry::Stages, + entry :stages, Entry::Stages, description: 'Configuration of stages for this pipeline.' - node :types, Entry::Stages, + entry :types, Entry::Stages, description: 'Deprecated: stages for this pipeline.' - node :cache, Entry::Cache, + entry :cache, Entry::Cache, description: 'Configure caching between build jobs.' helpers :before_script, :image, :services, :after_script, diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index b4e69a04706..ab4ef333629 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -34,43 +34,43 @@ module Gitlab end end - node :before_script, Entry::Script, + entry :before_script, Entry::Script, description: 'Global before script overridden in this job.' - node :script, Entry::Commands, + entry :script, Entry::Commands, description: 'Commands that will be executed in this job.' - node :stage, Entry::Stage, + entry :stage, Entry::Stage, description: 'Pipeline stage this job will be executed into.' - node :type, Entry::Stage, + entry :type, Entry::Stage, description: 'Deprecated: stage this job will be executed into.' - node :after_script, Entry::Script, + entry :after_script, Entry::Script, description: 'Commands that will be executed when finishing job.' - node :cache, Entry::Cache, + entry :cache, Entry::Cache, description: 'Cache definition for this job.' - node :image, Entry::Image, + entry :image, Entry::Image, description: 'Image that will be used to execute this job.' - node :services, Entry::Services, + entry :services, Entry::Services, description: 'Services that will be used to execute this job.' - node :only, Entry::Trigger, + entry :only, Entry::Trigger, description: 'Refs policy this job will be executed for.' - node :except, Entry::Trigger, + entry :except, Entry::Trigger, description: 'Refs policy this job will be executed for.' - node :variables, Entry::Variables, + entry :variables, Entry::Variables, description: 'Environment variables available for this job.' - node :artifacts, Entry::Artifacts, + entry :artifacts, Entry::Artifacts, description: 'Artifacts configuration for this job.' - node :environment, Entry::Environment, + entry :environment, Entry::Environment, description: 'Environment configuration for this job.' helpers :before_script, :script, :stage, :type, :after_script, -- cgit v1.2.1 From f0b42c1079341ed0245f5e3971dd4da0f8760c38 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 16:57:14 +0100 Subject: Grapify subscription API --- lib/api/subscriptions.rb | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) (limited to 'lib') diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index c49e2a21b82..00a79c24f96 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -9,23 +9,20 @@ module API 'labels' => proc { |id| find_project_label(id) }, } + params do + requires :id, type: String, desc: 'The ID of a project' + requires :subscribable_id, type: String, desc: 'The ID of a resource' + end resource :projects do subscribable_types.each do |type, finder| type_singularized = type.singularize - type_id_str = :"#{type_singularized}_id" entity_class = Entities.const_get(type_singularized.camelcase) - # Subscribe to a resource - # - # Parameters: - # id (required) - The ID of a project - # subscribable_id (required) - The ID of a resource - # Example Request: - # POST /projects/:id/labels/:subscribable_id/subscription - # POST /projects/:id/issues/:subscribable_id/subscription - # POST /projects/:id/merge_requests/:subscribable_id/subscription - post ":id/#{type}/:#{type_id_str}/subscription" do - resource = instance_exec(params[type_id_str], &finder) + desc 'Subscribe to a resource' do + success entity_class + end + post ":id/#{type}/:subscribable_id/subscription" do + resource = instance_exec(params[:subscribable_id], &finder) if resource.subscribed?(current_user) not_modified! @@ -35,17 +32,11 @@ module API end end - # Unsubscribe from a resource - # - # Parameters: - # id (required) - The ID of a project - # subscribable_id (required) - The ID of a resource - # Example Request: - # DELETE /projects/:id/labels/:subscribable_id/subscription - # DELETE /projects/:id/issues/:subscribable_id/subscription - # DELETE /projects/:id/merge_requests/:subscribable_id/subscription - delete ":id/#{type}/:#{type_id_str}/subscription" do - resource = instance_exec(params[type_id_str], &finder) + desc 'Unsubscribe from a resource' do + success entity_class + end + delete ":id/#{type}/:subscribable_id/subscription" do + resource = instance_exec(params[:subscribable_id], &finder) if !resource.subscribed?(current_user) not_modified! -- cgit v1.2.1 From 510092c83ad6b35dc4cc0ac1f15c643952f519b5 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 10 Nov 2016 16:32:59 +0100 Subject: Use #to_h to convert params to a hash --- lib/api/helpers.rb | 5 +++++ lib/api/milestones.rb | 7 +++---- lib/api/runners.rb | 4 +--- 3 files changed, 9 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 3c9d7b1aaef..6998b6dc039 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -23,6 +23,11 @@ module API warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) end + def declared_params(options = {}) + options = { include_parent_namespaces: false }.merge(options) + declared(params, options).to_h.symbolize_keys + end + def find_user_by_private_token token = private_token return nil unless token.present? diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 8984cf8cdcd..ba4a84275bc 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -62,9 +62,8 @@ module API end post ":id/milestones" do authorize! :admin_milestone, user_project - milestone_params = declared(params, include_parent_namespaces: false) - milestone = ::Milestones::CreateService.new(user_project, current_user, milestone_params).execute + milestone = ::Milestones::CreateService.new(user_project, current_user, declared_params).execute if milestone.valid? present milestone, with: Entities::Milestone @@ -86,9 +85,9 @@ module API end put ":id/milestones/:milestone_id" do authorize! :admin_milestone, user_project - milestone_params = declared(params, include_parent_namespaces: false, include_missing: false) + milestone = user_project.milestones.find(params.delete(:milestone_id)) - milestone = user_project.milestones.find(milestone_params.delete(:milestone_id)) + milestone_params = declared_params(include_missing: false) milestone = ::Milestones::UpdateService.new(user_project, current_user, milestone_params).execute(milestone) if milestone.valid? diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 84c19c432b0..b145cce7e3e 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -57,9 +57,7 @@ module API runner = get_runner(params.delete(:id)) authenticate_update_runner!(runner) - runner_params = declared(params, include_missing: false) - - if runner.update(runner_params) + if runner.update(declared_params(include_missing: false)) present runner, with: Entities::RunnerDetails, current_user: current_user else render_validation_error!(runner) -- cgit v1.2.1 From b3f38797fd429701adf48b80bf917998e2c083a9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 14 Nov 2016 11:46:41 +0100 Subject: Fix indentation in CI variables entry class --- lib/gitlab/ci/config/entry/variables.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/ci/config/entry/variables.rb b/lib/gitlab/ci/config/entry/variables.rb index f615874fa68..c3b0e651c3a 100644 --- a/lib/gitlab/ci/config/entry/variables.rb +++ b/lib/gitlab/ci/config/entry/variables.rb @@ -1,7 +1,7 @@ module Gitlab module Ci class Config - module Entry + module Entry ## # Entry that represents environment variables. # -- cgit v1.2.1 From 76bd09326fe070d4c865b6fbc1718673d096c178 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 14 Nov 2016 14:44:27 +0100 Subject: Use declared_params helper in API --- lib/api/access_requests.rb | 2 +- lib/api/broadcast_messages.rb | 6 ++---- lib/api/commits.rb | 2 +- lib/api/deploy_keys.rb | 2 +- lib/api/environments.rb | 7 +++---- lib/api/labels.rb | 11 +++-------- lib/api/members.rb | 2 +- lib/api/notification_settings.rb | 7 ++----- lib/api/project_hooks.rb | 10 +++------- lib/api/system_hooks.rb | 2 +- lib/api/tags.rb | 3 +-- lib/api/users.rb | 2 +- 12 files changed, 20 insertions(+), 36 deletions(-) (limited to 'lib') diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 87915b19480..ed723b94cfd 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -48,7 +48,7 @@ module API put ':id/access_requests/:user_id/approve' do source = find_source(source_type, params[:id]) - member = ::Members::ApproveAccessRequestService.new(source, current_user, declared(params)).execute + member = ::Members::ApproveAccessRequestService.new(source, current_user, declared_params).execute status :created present member.user, with: Entities::Member, member: member diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb index fb2a4148011..b6281a7f0ac 100644 --- a/lib/api/broadcast_messages.rb +++ b/lib/api/broadcast_messages.rb @@ -36,8 +36,7 @@ module API optional :font, type: String, desc: 'Foreground color' end post do - create_params = declared(params, include_missing: false).to_h - message = BroadcastMessage.create(create_params) + message = BroadcastMessage.create(declared_params(include_missing: false)) if message.persisted? present message, with: Entities::BroadcastMessage @@ -73,9 +72,8 @@ module API end put ':id' do message = find_message - update_params = declared(params, include_missing: false).to_h - if message.update(update_params) + if message.update(declared_params(include_missing: false)) present message, with: Entities::BroadcastMessage else render_validation_error!(message) diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 2f2cf769481..f412e1da1bf 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -53,7 +53,7 @@ module API post ":id/repository/commits" do authorize! :push_code, user_project - attrs = declared(params) + attrs = declared_params attrs[:source_branch] = attrs[:branch_name] attrs[:target_branch] = attrs[:branch_name] attrs[:actions].map! do |action| diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 425df2c176a..85360730841 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -82,7 +82,7 @@ module API end post ":id/#{path}/:key_id/enable" do key = ::Projects::EnableDeployKeyService.new(user_project, - current_user, declared(params)).execute + current_user, declared_params).execute if key present key, with: Entities::SSHKey diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 819f80d8365..00c901937b1 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -32,8 +32,7 @@ module API post ':id/environments' do authorize! :create_environment, user_project - create_params = declared(params, include_parent_namespaces: false).to_h - environment = user_project.environments.create(create_params) + environment = user_project.environments.create(declared_params) if environment.persisted? present environment, with: Entities::Environment @@ -55,8 +54,8 @@ module API authorize! :update_environment, user_project environment = user_project.environments.find(params[:environment_id]) - - update_params = declared(params, include_missing: false).extract!(:name, :external_url).to_h + + update_params = declared_params(include_missing: false).extract!(:name, :external_url) if environment.update(update_params) present environment, with: Entities::Environment else diff --git a/lib/api/labels.rb b/lib/api/labels.rb index 97218054f37..652786d4e3e 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -30,10 +30,7 @@ module API conflict!('Label already exists') if label priority = params.delete(:priority) - label_params = declared(params, - include_parent_namespaces: false, - include_missing: false).to_h - label = user_project.labels.create(label_params) + label = user_project.labels.create(declared_params(include_missing: false)) if label.valid? label.prioritize!(user_project, priority) if priority @@ -77,11 +74,9 @@ module API update_priority = params.key?(:priority) priority = params.delete(:priority) - label_params = declared(params, - include_parent_namespaces: false, - include_missing: false).to_h + label_params = declared_params(include_missing: false) # Rename new name to the actual label attribute name - label_params[:name] = label_params.delete('new_name') if label_params.key?('new_name') + label_params[:name] = label_params.delete(:new_name) if label_params.key?(:new_name) render_validation_error!(label) unless label.update(label_params) diff --git a/lib/api/members.rb b/lib/api/members.rb index b80818f0eb6..2d4d5cedf20 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -120,7 +120,7 @@ module API if member.nil? { message: "Access revoked", id: params[:user_id].to_i } else - ::Members::DestroyService.new(source, current_user, declared(params)).execute + ::Members::DestroyService.new(source, current_user, declared_params).execute present member.user, with: Entities::Member, member: member end diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb index a70a7e71073..c5e9b3ad69b 100644 --- a/lib/api/notification_settings.rb +++ b/lib/api/notification_settings.rb @@ -33,10 +33,9 @@ module API begin notification_setting.transaction do new_notification_email = params.delete(:notification_email) - declared_params = declared(params, include_missing: false).to_h current_user.update(notification_email: new_notification_email) if new_notification_email - notification_setting.update(declared_params) + notification_setting.update(declared_params(include_missing: false)) end rescue ArgumentError => e # catch level enum error render_api_error! e.to_s, 400 @@ -81,9 +80,7 @@ module API notification_setting = current_user.notification_settings_for(source) begin - declared_params = declared(params, include_missing: false).to_h - - notification_setting.update(declared_params) + notification_setting.update(declared_params(include_missing: false)) rescue ArgumentError => e # catch level enum error render_api_error! e.to_s, 400 end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index eef343c2ac6..2b36ef7c426 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -51,8 +51,7 @@ module API use :project_hook_properties end post ":id/hooks" do - new_hook_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h - hook = user_project.hooks.new(new_hook_params) + hook = user_project.hooks.new(declared_params(include_missing: false)) if hook.save present hook, with: Entities::ProjectHook @@ -71,12 +70,9 @@ module API use :project_hook_properties end put ":id/hooks/:hook_id" do - hook = user_project.hooks.find(params[:hook_id]) - - new_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h - new_params.delete('hook_id') + hook = user_project.hooks.find(params.delete(:hook_id)) - if hook.update_attributes(new_params) + if hook.update_attributes(declared_params(include_missing: false)) present hook, with: Entities::ProjectHook else error!("Invalid url given", 422) if hook.errors[:url].present? diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index b6bfff9f20f..708ec8cfe70 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -27,7 +27,7 @@ module API optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" end post do - hook = SystemHook.new declared(params, include_missing: false).to_h + hook = SystemHook.new(declared_params(include_missing: false)) if hook.save present hook, with: Entities::Hook diff --git a/lib/api/tags.rb b/lib/api/tags.rb index bf2a199ce21..cd33f9a9903 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -40,10 +40,9 @@ module API end post ':id/repository/tags' do authorize_push_project - create_params = declared(params) result = CreateTagService.new(user_project, current_user). - execute(create_params[:tag_name], create_params[:ref], create_params[:message], create_params[:release_description]) + execute(params[:tag_name], params[:ref], params[:message], params[:release_description]) if result[:status] == :success present result[:tag], diff --git a/lib/api/users.rb b/lib/api/users.rb index 298c401a816..aea328d2f8f 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -335,7 +335,7 @@ module API requires :id, type: String, desc: 'The user ID' end get ':id/events' do - user = User.find_by(id: declared(params).id) + user = User.find_by(id: params[:id]) not_found!('User') unless user events = user.events. -- cgit v1.2.1 From 84ac742fa6caf6e525e690d6f929f8931902fb9b Mon Sep 17 00:00:00 2001 From: Francesco Coda Zabetta Date: Mon, 14 Nov 2016 15:52:43 +0100 Subject: fix labels API adding missing parameter (current_user) --- lib/api/entities.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1942aeea656..a186cfd94c1 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -437,7 +437,18 @@ module API end class Label < LabelBasic - expose :open_issues_count, :closed_issues_count, :open_merge_requests_count + expose :open_issues_count do |label, options| + label.open_issues_count(options[:current_user]) + end + + expose :closed_issues_count do |label, options| + label.closed_issues_count(options[:current_user]) + end + + expose :open_merge_requests_count do |label, options| + label.open_merge_requests_count(options[:current_user]) + end + expose :priority do |label, options| label.priority(options[:project]) end -- cgit v1.2.1 From 0f61bb2dc5298a7e06f8e211adcf46dd282b9c86 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 27 Aug 2016 20:59:48 -0700 Subject: Fix Error 500 when creating a merge request that contains an image that was deleted and added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steps to reproduce: 1. Start with a repo with an image 2. Add a commit to delete the image 3. Add another commit to replace the image with another image In a diff comparison, we really just compare about what the image was before the diff, not the direct parent of the last commit. This MR fixes that. Closes #3893, gitlab-org/gitlab-ee#678 Signed-off-by: Rémy Coutable --- lib/gitlab/diff/file.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 5110bfbf898..c6bf25b5874 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -55,6 +55,12 @@ module Gitlab repository.commit(deleted_file ? old_ref : new_ref) end + def old_content_commit + return unless diff_refs + + repository.commit(old_ref) + end + def old_ref diff_refs.try(:base_sha) end @@ -111,13 +117,10 @@ module Gitlab diff_lines.count(&:removed?) end - def old_blob(commit = content_commit) + def old_blob(commit = old_content_commit) return unless commit - parent_id = commit.parent_id - return unless parent_id - - repository.blob_at(parent_id, old_path) + repository.blob_at(commit.id, old_path) end def blob(commit = content_commit) -- cgit v1.2.1 From 471ef6cb2760717b917227ae545cc8ec8efa0aa2 Mon Sep 17 00:00:00 2001 From: Francesco Coda Zabetta Date: Mon, 14 Nov 2016 16:04:07 +0100 Subject: fix indentation --- lib/api/entities.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a186cfd94c1..e5d641adccc 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -438,15 +438,15 @@ module API class Label < LabelBasic expose :open_issues_count do |label, options| - label.open_issues_count(options[:current_user]) - end + label.open_issues_count(options[:current_user]) + end - expose :closed_issues_count do |label, options| - label.closed_issues_count(options[:current_user]) - end + expose :closed_issues_count do |label, options| + label.closed_issues_count(options[:current_user]) + end - expose :open_merge_requests_count do |label, options| - label.open_merge_requests_count(options[:current_user]) + expose :open_merge_requests_count do |label, options| + label.open_merge_requests_count(options[:current_user]) end expose :priority do |label, options| -- cgit v1.2.1 From 9dbb0417ba683ff220ba62ecb79e6375496ad79d Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 8 Nov 2016 09:28:52 +0100 Subject: Grapify the merge request API --- lib/api/merge_requests.rb | 272 +++++++++++++++++++--------------------------- 1 file changed, 110 insertions(+), 162 deletions(-) (limited to 'lib') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index bf8504e1101..f9720786e63 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -1,8 +1,12 @@ module API - # MergeRequest API class MergeRequests < Grape::API + DEPRECATION_MESSAGE = 'This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze + before { authenticate! } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do helpers do def handle_merge_request_errors!(errors) @@ -18,27 +22,27 @@ module API render_api_error!(errors, 400) end + + params :optional_params do + optional :description, type: String, desc: 'The description of the merge request' + optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request' + optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' + optional :labels, type: String, desc: 'Comma-separated list of label names' + end end - # List merge requests - # - # Parameters: - # id (required) - The ID of a project - # iid (optional) - Return the project MR having the given `iid` - # state (optional) - Return requests "merged", "opened" or "closed" - # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` - # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - # - # Example: - # GET /projects/:id/merge_requests - # GET /projects/:id/merge_requests?state=opened - # GET /projects/:id/merge_requests?state=closed - # GET /projects/:id/merge_requests?order_by=created_at - # GET /projects/:id/merge_requests?order_by=updated_at - # GET /projects/:id/merge_requests?sort=desc - # GET /projects/:id/merge_requests?sort=asc - # GET /projects/:id/merge_requests?iid=42 - # + desc 'List merge requests' do + success Entities::MergeRequest + end + params do + optional :state, type: String, values: %w[opened closed merged all], default: 'all', + desc: 'Return opened, closed, merged, or all merge requests' + optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', + desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return merge requests sorted in `asc` or `desc` order.' + optional :iid, type: Integer, desc: 'The IID of the merge requests' + end get ":id/merge_requests" do authorize! :read_merge_request, user_project merge_requests = user_project.merge_requests.inc_notes_with_associations @@ -48,10 +52,10 @@ module API end merge_requests = - case params["state"] - when "opened" then merge_requests.opened - when "closed" then merge_requests.closed - when "merged" then merge_requests.merged + case params[:state] + when 'opened' then merge_requests.opened + when 'closed' then merge_requests.closed + when 'merged' then merge_requests.merged else merge_requests end @@ -59,36 +63,28 @@ module API present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user end - # Create MR - # - # Parameters: - # - # id (required) - The ID of a project - this will be the source of the merge request - # source_branch (required) - The source branch - # target_branch (required) - The target branch - # target_project_id - The target project of the merge request defaults to the :id of the project - # assignee_id - Assignee user ID - # title (required) - Title of MR - # description - Description of MR - # labels (optional) - Labels for MR as a comma-separated list - # milestone_id (optional) - Milestone ID - # - # Example: - # POST /projects/:id/merge_requests - # + desc 'Create a merge request' do + success Entities::MergeRequest + end + params do + requires :title, type: String, desc: 'The title of the merge request' + requires :source_branch, type: String, desc: 'The source branch' + requires :target_branch, type: String, desc: 'The target branch' + optional :target_project_id, type: Integer, + desc: 'The target project of the merge request defaults to the :id of the project' + use :optional_params + end post ":id/merge_requests" do authorize! :create_merge_request, user_project - required_attributes! [:source_branch, :target_branch, :title] - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description, :milestone_id] + + mr_params = declared_params # Validate label names in advance - if (errors = validate_label_params(params)).any? + if (errors = validate_label_params(mr_params)).any? render_api_error!({ labels: errors }, 400) end - attrs[:labels] = params[:labels] if params[:labels] - - merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute + merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute if merge_request.valid? present merge_request, with: Entities::MergeRequest, current_user: current_user @@ -97,11 +93,10 @@ module API end end - # Delete a MR - # - # Parameters: - # id (required) - The ID of the project - # merge_request_id (required) - The MR id + desc 'Delete a merge request' + params do + requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' + end delete ":id/merge_requests/:merge_request_id" do merge_request = user_project.merge_requests.find_by(id: params[:merge_request_id]) @@ -112,89 +107,64 @@ module API # Routing "merge_request/:merge_request_id/..." is DEPRECATED and WILL BE REMOVED in version 9.0 # Use "merge_requests/:merge_request_id/..." instead. # - [":id/merge_request/:merge_request_id", ":id/merge_requests/:merge_request_id"].each do |path| - # Show MR - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of MR - # - # Example: - # GET /projects/:id/merge_requests/:merge_request_id - # + params do + requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' + end + { ":id/merge_request/:merge_request_id" => :deprecated, ":id/merge_requests/:merge_request_id" => :ok }.each do |path, status| + desc 'Get a single merge request' do + if status == :deprecated + detail DEPRECATION_MESSAGE + end + success Entities::MergeRequest + end get path do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - authorize! :read_merge_request, merge_request - present merge_request, with: Entities::MergeRequest, current_user: current_user end - # Show MR commits - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of MR - # - # Example: - # GET /projects/:id/merge_requests/:merge_request_id/commits - # + desc 'Get the commits of a merge request' do + success Entities::RepoCommit + end get "#{path}/commits" do - merge_request = user_project.merge_requests. - find(params[:merge_request_id]) + merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :read_merge_request, merge_request present merge_request.commits, with: Entities::RepoCommit end - # Show MR changes - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of MR - # - # Example: - # GET /projects/:id/merge_requests/:merge_request_id/changes - # + desc 'Show the merge request changes' do + success Entities::MergeRequestChanges + end get "#{path}/changes" do - merge_request = user_project.merge_requests. - find(params[:merge_request_id]) + merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :read_merge_request, merge_request present merge_request, with: Entities::MergeRequestChanges, current_user: current_user end - # Update MR - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # target_branch - The target branch - # assignee_id - Assignee user ID - # title - Title of MR - # state_event - Status of MR. (close|reopen|merge) - # description - Description of MR - # labels (optional) - Labels for a MR as a comma-separated list - # milestone_id (optional) - Milestone ID - # Example: - # PUT /projects/:id/merge_requests/:merge_request_id - # + desc 'Update a merge request' do + success Entities::MergeRequest + end + params do + optional :title, type: String, desc: 'The title of the merge request' + optional :target_branch, type: String, desc: 'The target branch' + optional :state_event, type: String, values: %w[close reopen merge], + desc: 'Status of the merge request' + use :optional_params + at_least_one_of :title, :target_branch, :description, :assignee_id, + :milestone_id, :labels, :state_event + end put path do - attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description, :milestone_id] - merge_request = user_project.merge_requests.find(params[:merge_request_id]) + merge_request = user_project.merge_requests.find(params.delete(:merge_request_id)) authorize! :update_merge_request, merge_request - # Ensure source_branch is not specified - if params[:source_branch].present? - render_api_error!('Source branch cannot be changed', 400) - end + mr_params = declared_params(include_missing: false) # Validate label names in advance - if (errors = validate_label_params(params)).any? + if (errors = validate_label_params(mr_params)).any? render_api_error!({ labels: errors }, 400) end - attrs[:labels] = params[:labels] if params[:labels] - - merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request) + merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) if merge_request.valid? present merge_request, with: Entities::MergeRequest, current_user: current_user @@ -203,18 +173,17 @@ module API end end - # Merge MR - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # merge_commit_message (optional) - Custom merge commit message - # should_remove_source_branch (optional) - When true, the source branch will be deleted if possible - # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds - # sha (optional) - When present, must have the HEAD SHA of the source branch - # Example: - # PUT /projects/:id/merge_requests/:merge_request_id/merge - # + desc 'Merge a merge request' do + success Entities::MergeRequest + end + params do + optional :merge_commit_message, type: String, desc: 'Custom merge commit message' + optional :should_remove_source_branch, type: Boolean, + desc: 'When true, the source branch will be deleted if possible' + optional :merge_when_build_succeeds, type: Boolean, + desc: 'When true, this merge request will be merged when the build succeeds' + optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' + end put "#{path}/merge" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) @@ -235,7 +204,7 @@ module API should_remove_source_branch: params[:should_remove_source_branch] } - if to_boolean(params[:merge_when_build_succeeds]) && merge_request.pipeline && merge_request.pipeline.active? + if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active? ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). execute(merge_request) else @@ -246,11 +215,9 @@ module API present merge_request, with: Entities::MergeRequest, current_user: current_user end - # Cancel Merge if Merge When build succeeds is enabled - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # + desc 'Cancel merge if "Merge when build succeeds" is enabled' do + success Entities::MergeRequest + end post "#{path}/cancel_merge_when_build_succeeds" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) @@ -259,17 +226,10 @@ module API ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request) end - # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0. - # Use GET "/projects/:id/merge_requests/:merge_request_id/notes" instead - # - # Get a merge request's comments - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # Examples: - # GET /projects/:id/merge_requests/:merge_request_id/comments - # + desc 'Get the comments of a merge request' do + detail 'Duplicate. DEPRECATED and WILL BE REMOVED in 9.0' + success Entities::MRNote + end get "#{path}/comments" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) @@ -278,23 +238,15 @@ module API present paginate(merge_request.notes.fresh), with: Entities::MRNote end - # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0. - # Use POST "/projects/:id/merge_requests/:merge_request_id/notes" instead - # - # Post comment to merge request - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # note (required) - Text of comment - # Examples: - # POST /projects/:id/merge_requests/:merge_request_id/comments - # + desc 'Post a comment to a merge request' do + detail 'Duplicate. DEPRECATED and WILL BE REMOVED in 9.0' + success Entities::MRNote + end + params do + requires :note, type: String, desc: 'The text of the comment' + end post "#{path}/comments" do - required_attributes! [:note] - merge_request = user_project.merge_requests.find(params[:merge_request_id]) - authorize! :create_note, merge_request opts = { @@ -312,13 +264,9 @@ module API end end - # List issues that will close on merge - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # Examples: - # GET /projects/:id/merge_requests/:merge_request_id/closes_issues + desc 'List issues that will be closed on merge' do + success Entities::MRNote + end get "#{path}/closes_issues" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) -- cgit v1.2.1 From ac12c1d271f8ba365f3b38baad3940e7244bbb96 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 7 Nov 2016 16:54:39 +0100 Subject: Grapify the group API --- lib/api/groups.rb | 156 ++++++++++++++++++++++++++---------------------------- 1 file changed, 74 insertions(+), 82 deletions(-) (limited to 'lib') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 40644fc2adf..3f57b9ab5bc 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,118 +1,111 @@ module API - # groups API class Groups < Grape::API before { authenticate! } + helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the group' + optional :visibility_level, type: Integer, desc: 'The visibility level of the group' + optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group' + optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' + end + end + resource :groups do - # Get a groups list - # - # Parameters: - # skip_groups (optional) - Array of group ids to exclude from list - # all_available (optional, boolean) - Show all group that you have access to - # Example Request: - # GET /groups + desc 'Get a groups list' do + success Entities::Group + end + params do + optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list' + optional :all_available, type: Boolean, desc: 'Show all group that you have access to' + optional :search, type: String, desc: 'Search for a specific group' + end get do - @groups = if current_user.admin - Group.all - elsif params[:all_available] - GroupsFinder.new.execute(current_user) - else - current_user.groups - end + groups = if current_user.admin + Group.all + elsif params[:all_available] + GroupsFinder.new.execute(current_user) + else + current_user.groups + end - @groups = @groups.search(params[:search]) if params[:search].present? - @groups = @groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? - @groups = paginate @groups - present @groups, with: Entities::Group + groups = groups.search(params[:search]) if params[:search].present? + groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? + present paginate(groups), with: Entities::Group end - # Get list of owned groups for authenticated user - # - # Example Request: - # GET /groups/owned + desc 'Get list of owned groups for authenticated user' do + success Entities::Group + end get '/owned' do - @groups = current_user.owned_groups - @groups = paginate @groups - present @groups, with: Entities::Group, user: current_user + groups = current_user.owned_groups + present paginate(groups), with: Entities::Group, user: current_user end - # Create group. Available only for users who can create groups. - # - # Parameters: - # name (required) - The name of the group - # path (required) - The path of the group - # description (optional) - The description of the group - # visibility_level (optional) - The visibility level of the group - # lfs_enabled (optional) - Enable/disable LFS for the projects in this group - # request_access_enabled (optional) - Allow users to request member access - # Example Request: - # POST /groups + desc 'Create a group. Available only for users who can create groups.' do + success Entities::Group + end + params do + requires :name, type: String, desc: 'The name of the group' + requires :path, type: String, desc: 'The path of the group' + use :optional_params + end post do authorize! :create_group - required_attributes! [:name, :path] - attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled] - @group = Group.new(attrs) + group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute - if @group.save - @group.add_owner(current_user) - present @group, with: Entities::Group + if group.persisted? + present group, with: Entities::Group else - render_api_error!("Failed to save group #{@group.errors.messages}", 400) + render_api_error!("Failed to save group #{group.errors.messages}", 400) end end + end - # Update group. Available only for users who can administrate groups. - # - # Parameters: - # id (required) - The ID of a group - # path (optional) - The path of the group - # description (optional) - The description of the group - # visibility_level (optional) - The visibility level of the group - # lfs_enabled (optional) - Enable/disable LFS for the projects in this group - # request_access_enabled (optional) - Allow users to request member access - # Example Request: - # PUT /groups/:id + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups do + desc 'Update a group. Available only for users who can administrate groups.' do + success Entities::Group + end + params do + optional :name, type: String, desc: 'The name of the group' + optional :path, type: String, desc: 'The path of the group' + use :optional_params + at_least_one_of :name, :path, :description, :visibility_level, + :lfs_enabled, :request_access_enabled + end put ':id' do group = find_group(params[:id]) authorize! :admin_group, group - attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled] - - if ::Groups::UpdateService.new(group, current_user, attrs).execute + if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute present group, with: Entities::GroupDetail else render_validation_error!(group) end end - # Get a single group, with containing projects - # - # Parameters: - # id (required) - The ID of a group - # Example Request: - # GET /groups/:id + desc 'Get a single group, with containing projects.' do + success Entities::GroupDetail + end get ":id" do group = find_group(params[:id]) present group, with: Entities::GroupDetail end - # Remove group - # - # Parameters: - # id (required) - The ID of a group - # Example Request: - # DELETE /groups/:id + desc 'Remove a group.' delete ":id" do group = find_group(params[:id]) authorize! :admin_group, group DestroyGroupService.new(group, current_user).execute end - # Get a list of projects in this group - # - # Example Request: - # GET /groups/:id/projects + desc 'Get a list of projects in this group.' do + success Entities::Project + end get ":id/projects" do group = find_group(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) @@ -120,13 +113,12 @@ module API present projects, with: Entities::Project, user: current_user end - # Transfer a project to the Group namespace - # - # Parameters: - # id - group id - # project_id - project id - # Example Request: - # POST /groups/:id/projects/:project_id + desc 'Transfer a project to the group namespace. Available only for admin.' do + success Entities::GroupDetail + end + params do + requires :project_id, type: String, desc: 'The ID of the project' + end post ":id/projects/:project_id" do authenticated_as_admin! group = Group.find_by(id: params[:id]) @@ -134,7 +126,7 @@ module API result = ::Projects::TransferService.new(project, current_user).execute(group) if result - present group + present group, with: Entities::GroupDetail else render_api_error!("Failed to transfer project #{project.errors.messages}", 400) end -- cgit v1.2.1 From ff8194e0ec16092419862011d7cc048baa149c42 Mon Sep 17 00:00:00 2001 From: Dmitry Poray Date: Mon, 7 Nov 2016 20:11:54 +0300 Subject: Add ref parameter for triggerring builds with gitlab webhook from other project. --- lib/api/triggers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index 9a4f1cd342f..569598fbd2c 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -12,7 +12,7 @@ module API requires :token, type: String, desc: 'The unique token of trigger' optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end - post ":id/trigger/builds" do + post ":id/(ref/:ref/)trigger/builds" do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) trigger = Ci::Trigger.find_by_token(params[:token].to_s) not_found! unless project && trigger -- cgit v1.2.1 From 5703d6afeee043b633f05c46e9152d21c7b50d4f Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 15:26:27 +0100 Subject: Grapify the notes API --- lib/api/notes.rb | 124 +++++++++++++++++++++++++------------------------------ 1 file changed, 57 insertions(+), 67 deletions(-) (limited to 'lib') diff --git a/lib/api/notes.rb b/lib/api/notes.rb index c5c214d4d13..b255b47742b 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -5,23 +5,23 @@ module API NOTEABLE_TYPES = [Issue, MergeRequest, Snippet] + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do NOTEABLE_TYPES.each do |noteable_type| noteables_str = noteable_type.to_s.underscore.pluralize - noteable_id_str = "#{noteable_type.to_s.underscore}_id" - - # Get a list of project +noteable+ notes - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue or snippet - # Example Request: - # GET /projects/:id/issues/:noteable_id/notes - # GET /projects/:id/snippets/:noteable_id/notes - get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do - @noteable = user_project.send(noteables_str.to_sym).find(params[noteable_id_str.to_sym]) - - if can?(current_user, noteable_read_ability_name(@noteable), @noteable) + + desc 'Get a list of project +noteable+ notes' do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + end + get ":id/#{noteables_str}/:noteable_id/notes" do + noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) + + if can?(current_user, noteable_read_ability_name(noteable), noteable) # We exclude notes that are cross-references and that cannot be viewed # by the current user. By doing this exclusion at this level and not # at the DB query level (which we cannot in that case), the current @@ -31,7 +31,7 @@ module API # paginate() only works with a relation. This could lead to a # mismatch between the pagination headers info and the actual notes # array returned, but this is really a edge-case. - paginate(@noteable.notes). + paginate(noteable.notes). reject { |n| n.cross_reference_not_visible_for?(current_user) } present notes, with: Entities::Note else @@ -39,44 +39,40 @@ module API end end - # Get a single +noteable+ note - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue or snippet - # note_id (required) - The ID of a note - # Example Request: - # GET /projects/:id/issues/:noteable_id/notes/:note_id - # GET /projects/:id/snippets/:noteable_id/notes/:note_id - get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do - @noteable = user_project.send(noteables_str.to_sym).find(params[noteable_id_str.to_sym]) - @note = @noteable.notes.find(params[:note_id]) - can_read_note = can?(current_user, noteable_read_ability_name(@noteable), @noteable) && !@note.cross_reference_not_visible_for?(current_user) + desc 'Get a single +noteable+ note' do + success Entities::Note + end + params do + requires :note_id, type: Integer, desc: 'The ID of a note' + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + end + get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do + noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) + note = noteable.notes.find(params[:note_id]) + can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user) if can_read_note - present @note, with: Entities::Note + present note, with: Entities::Note else not_found!("Note") end end - # Create a new +noteable+ note - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue or snippet - # body (required) - The content of a note - # created_at (optional) - The date - # Example Request: - # POST /projects/:id/issues/:noteable_id/notes - # POST /projects/:id/snippets/:noteable_id/notes - post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do + desc 'Create a new +noteable+ note' do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :body, type: String, desc: 'The content of a note' + optional :created_at, type: String, desc: 'The creation date of the note' + end + post ":id/#{noteables_str}/:noteable_id/notes" do required_attributes! [:body] opts = { note: params[:body], noteable_type: noteables_str.classify, - noteable_id: params[noteable_id_str] + noteable_id: params[:noteable_id] } if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user) @@ -92,19 +88,15 @@ module API end end - # Modify existing +noteable+ note - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue or snippet - # node_id (required) - The ID of a note - # body (required) - New content of a note - # Example Request: - # PUT /projects/:id/issues/:noteable_id/notes/:note_id - # PUT /projects/:id/snippets/:noteable_id/notes/:node_id - put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do - required_attributes! [:body] - + desc 'Update an existing +noteable+ note' do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :note_id, type: Integer, desc: 'The ID of a note' + requires :body, type: String, desc: 'The content of a note' + end + put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do note = user_project.notes.find(params[:note_id]) authorize! :admin_note, note @@ -113,25 +105,23 @@ module API note: params[:body] } - @note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note) + note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note) - if @note.valid? - present @note, with: Entities::Note + if note.valid? + present note, with: Entities::Note else render_api_error!("Failed to save note #{note.errors.messages}", 400) end end - # Delete a +noteable+ note - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue, MR, or snippet - # node_id (required) - The ID of a note - # Example Request: - # DELETE /projects/:id/issues/:noteable_id/notes/:note_id - # DELETE /projects/:id/snippets/:noteable_id/notes/:node_id - delete ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do + desc 'Delete a +noteable+ note' do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :note_id, type: Integer, desc: 'The ID of a note' + end + delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do note = user_project.notes.find(params[:note_id]) authorize! :admin_note, note -- cgit v1.2.1 From ef3be00a0297dfa31002616df6ee49a0e2132cb7 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 16 Nov 2016 12:46:07 +0100 Subject: Defer saving project services to the database if there are no user changes --- lib/api/helpers.rb | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 3c9d7b1aaef..b4e0457f773 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -81,25 +81,10 @@ module API end def project_service - @project_service ||= begin - underscored_service = params[:service_slug].underscore - - if Service.available_services_names.include?(underscored_service) - user_project.build_missing_services - - service_method = "#{underscored_service}_service" - - send_service(service_method) - end - end - + @project_service ||= user_project.find_or_initialize_service(params[:service_slug].underscore) @project_service || not_found!("Service") end - def send_service(service_method) - user_project.send(service_method) - end - def service_attributes @service_attributes ||= project_service.fields.inject([]) do |arr, hash| arr << hash[:name].to_sym -- cgit v1.2.1 From aa9a289ce55d5099e6a65f25e7c3b38f66148aca Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 11 Nov 2016 15:40:19 +0100 Subject: Make mail_room idle_timeout option configurable. --- lib/gitlab/mail_room.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/gitlab/mail_room.rb b/lib/gitlab/mail_room.rb index a5220d92312..3503fac40e8 100644 --- a/lib/gitlab/mail_room.rb +++ b/lib/gitlab/mail_room.rb @@ -31,6 +31,7 @@ module Gitlab config[:ssl] = false if config[:ssl].nil? config[:start_tls] = false if config[:start_tls].nil? config[:mailbox] = 'inbox' if config[:mailbox].nil? + config[:idle_timeout] = 60 if config[:idle_timeout].nil? if config[:enabled] && config[:address] gitlab_redis = Gitlab::Redis.new(rails_env) -- cgit v1.2.1 From a5632e802b72db01c0fb0b8bec77c0fc28b41427 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 10 Nov 2016 19:27:09 +0200 Subject: Search for a filename in a project --- lib/gitlab/project_search_results.rb | 51 ++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index b8326a64b22..66e6b29e798 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -5,7 +5,7 @@ module Gitlab def initialize(current_user, project, query, repository_ref = nil) @current_user = current_user @project = project - @repository_ref = repository_ref.presence + @repository_ref = repository_ref.presence || project.default_branch @query = query end @@ -40,10 +40,57 @@ module Gitlab @commits_count ||= commits.count end + def self.parse_search_result(result) + ref = nil + filename = nil + basename = nil + startline = 0 + + result.each_line.each_with_index do |line, index| + if line =~ /^.*:.*:\d+:/ + ref, filename, startline = line.split(':') + startline = startline.to_i - index + extname = Regexp.escape(File.extname(filename)) + basename = filename.sub(/#{extname}$/, '') + break + end + end + + data = "" + + result.each_line do |line| + data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') + end + + OpenStruct.new( + filename: filename, + basename: basename, + ref: ref, + startline: startline, + data: data + ) + end + private def blobs - @blobs ||= project.repository.search_files(query, repository_ref) + @blobs ||= begin + blobs = project.repository.search_files_by_content(query, repository_ref).first(100) + found_file_names = Set.new + + results = blobs.map do |blob| + blob = self.class.parse_search_result(blob) + found_file_names << blob.filename + + [blob.filename, blob] + end + + project.repository.search_files_by_name(query, repository_ref).first(100).each do |filename| + results << [filename, nil] unless found_file_names.include?(filename) + end + + results.sort_by(&:first) + end end def wiki_blobs -- cgit v1.2.1 From 596bbf670c95a513cce703dce756e957a143c596 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 15 Nov 2016 03:57:43 +0100 Subject: Send registry_url with build data to GitLab Runner --- lib/ci/api/entities.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index 66c05773b68..e00d91a6b45 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -50,6 +50,10 @@ module Ci expose :variables expose :depends_on_builds, using: Build + + expose :registry_url, if: ->(_, _) { Gitlab.config.registry.enabled } do |_| + Gitlab.config.registry.host_port + end end class Runner < Grape::Entity -- cgit v1.2.1 From c60437786bfe43344b4a5eb040437f73f37c6396 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 13 Nov 2016 20:35:47 +0100 Subject: Create relation between chat user and GitLab user and allow to authorize them [ci skip] --- lib/gitlab/chat_name_token.rb | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 lib/gitlab/chat_name_token.rb (limited to 'lib') diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb new file mode 100644 index 00000000000..c8349839219 --- /dev/null +++ b/lib/gitlab/chat_name_token.rb @@ -0,0 +1,45 @@ +require 'json' + +module Gitlab + class ChatNameToken + attr_reader :token + + TOKEN_LENGTH = 50 + EXPIRY_TIME = 1800 + + def initialize(token = new_token) + @token = token + end + + def get + Gitlab::Redis.with do |redis| + data = redis.get(redis_key) + JSON.parse(data, symbolize_names: true) if data + end + end + + def store!(params) + Gitlab::Redis.with do |redis| + params = params.to_json + redis.set(redis_key, params, ex: EXPIRY_TIME) + token + end + end + + def delete + Gitlab::Redis.with do |redis| + redis.del(redis_key) + end + end + + private + + def new_token + Devise.friendly_token(TOKEN_LENGTH) + end + + def redis_key + "gitlab:chat_names:#{token}" + end + end +end -- cgit v1.2.1 From d64183e1fa26ab77107e3a2a20be1fe4df3a1875 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Nov 2016 14:56:30 +0100 Subject: Add most of specs for chat names --- lib/gitlab/chat_name_token.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb index c8349839219..543f038a4d4 100644 --- a/lib/gitlab/chat_name_token.rb +++ b/lib/gitlab/chat_name_token.rb @@ -5,7 +5,7 @@ module Gitlab attr_reader :token TOKEN_LENGTH = 50 - EXPIRY_TIME = 1800 + EXPIRY_TIME = 10.minutes # 10 minutes def initialize(token = new_token) @token = token -- cgit v1.2.1 From 1c994dbc05c147714479288126742f3fee158fd8 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 15 Nov 2016 15:02:44 +0000 Subject: Fix POST /internal/allowed to cope with gitlab-shell v4.0.0 project paths gitlab-shell v3.6.6 would give project paths like so: * namespace/project gitlab-shell v4.0.0 can give project paths like so: * /namespace1/namespace2/project * /namespace/project * /path/to/repository/storage/namespace1/namespace2/project * /path/to/repository/storage/namespace/project --- lib/api/helpers/internal_helpers.rb | 57 +++++++++++++++++++++++++++++++++++++ lib/api/internal.rb | 38 ++----------------------- 2 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 lib/api/helpers/internal_helpers.rb (limited to 'lib') diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb new file mode 100644 index 00000000000..eb223c1101d --- /dev/null +++ b/lib/api/helpers/internal_helpers.rb @@ -0,0 +1,57 @@ +module API + module Helpers + module InternalHelpers + # Project paths may be any of the following: + # * /repository/storage/path/namespace/project + # * /namespace/project + # * namespace/project + # + # In addition, they may have a '.git' extension and multiple namespaces + # + # Transform all these cases to 'namespace/project' + def clean_project_path(project_path, storage_paths = Repository.storages.values) + project_path = project_path.sub(/\.git\z/, '') + + storage_paths.each do |storage_path| + storage_path = File.expand_path(storage_path) + + if project_path.start_with?(storage_path) + project_path = project_path.sub(storage_path, '') + break + end + end + + project_path.sub(/\A\//, '') + end + + def project_path + @project_path ||= clean_project_path(params[:project]) + end + + def wiki? + @wiki ||= project_path.end_with?('.wiki') && + !Project.find_with_namespace(project_path) + end + + def project + @project ||= begin + # Check for *.wiki repositories. + # Strip out the .wiki from the pathname before finding the + # project. This applies the correct project permissions to + # the wiki repository as well. + project_path.chomp!('.wiki') if wiki? + + Project.find_with_namespace(project_path) + end + end + + def ssh_authentication_abilities + [ + :read_project, + :download_code, + :push_code + ] + end + end + end +end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ccf181402f9..7087ce11401 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -3,6 +3,8 @@ module API class Internal < Grape::API before { authenticate_by_gitlab_shell_token! } + helpers ::API::Helpers::InternalHelpers + namespace 'internal' do # Check if git command is allowed to project # @@ -14,42 +16,6 @@ module API # ref - branch name # forced_push - forced_push # protocol - Git access protocol being used, e.g. HTTP or SSH - # - - helpers do - def project_path - @project_path ||= begin - project_path = params[:project].sub(/\.git\z/, '') - Repository.remove_storage_from_path(project_path) - end - end - - def wiki? - @wiki ||= project_path.end_with?('.wiki') && - !Project.find_with_namespace(project_path) - end - - def project - @project ||= begin - # Check for *.wiki repositories. - # Strip out the .wiki from the pathname before finding the - # project. This applies the correct project permissions to - # the wiki repository as well. - project_path.chomp!('.wiki') if wiki? - - Project.find_with_namespace(project_path) - end - end - - def ssh_authentication_abilities - [ - :read_project, - :download_code, - :push_code - ] - end - end - post "/allowed" do status 200 -- cgit v1.2.1 From 6f714dfb4a9b823ab75508f252d06e19e286d5f2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Nov 2016 23:10:27 +0100 Subject: Improve code design after code review --- lib/gitlab/chat_name_token.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb index 543f038a4d4..1b081aa9b1d 100644 --- a/lib/gitlab/chat_name_token.rb +++ b/lib/gitlab/chat_name_token.rb @@ -5,7 +5,7 @@ module Gitlab attr_reader :token TOKEN_LENGTH = 50 - EXPIRY_TIME = 10.minutes # 10 minutes + EXPIRY_TIME = 10.minutes def initialize(token = new_token) @token = token -- cgit v1.2.1 From f7b0692912e0679a3e2e77b2d1bfaf305fba473a Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 10 Nov 2016 15:24:02 +0900 Subject: add parsing support for incoming html email --- lib/gitlab/email/html_parser.rb | 31 +++++++++++++++++++++++++++++++ lib/gitlab/email/reply_parser.rb | 19 ++++++++++++++----- 2 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 lib/gitlab/email/html_parser.rb (limited to 'lib') diff --git a/lib/gitlab/email/html_parser.rb b/lib/gitlab/email/html_parser.rb new file mode 100644 index 00000000000..51d8edb1b13 --- /dev/null +++ b/lib/gitlab/email/html_parser.rb @@ -0,0 +1,31 @@ +module Gitlab + module Email + class HTMLParser + def self.parse_reply(raw_body) + new(raw_body).filtered_text + end + + attr_reader :raw_body + def initialize(raw_body) + @raw_body = raw_body + end + + def document + @document ||= Nokogiri::HTML(raw_body) + end + + def filter_replies! + document.xpath('//blockquote').each { |n| n.replace('> ') } + document.xpath('//table').each { |n| n.remove } + end + + def filtered_html + @filtered_html ||= (filter_replies!; document.inner_html) + end + + def filtered_text + @filtered_text ||= Html2Text.convert(filtered_html) + end + end + end +end diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 3411eb1d9ce..1ad44425c93 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -23,19 +23,28 @@ module Gitlab private def select_body(message) - text = message.text_part if message.multipart? - text ||= message if message.content_type !~ /text\/html/ + if message.multipart? + text = message.text_part || message.html_part || message + else + text = message + end return "" unless text - text = fix_charset(text) + decoded = fix_charset(text) + + return "" unless decoded # Certain trigger phrases that means we didn't parse correctly - if text =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ + if decoded =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ return "" end - text + if text.content_type =~ %r(text/html) + HTMLParser.parse_reply(decoded) + else + decoded + end end # Force encoding to UTF-8 on a Mail::Message or Mail::Part -- cgit v1.2.1 From 17341244337511a239bb7a262236b9896a0bf76d Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 10 Nov 2016 17:40:53 +0900 Subject: fix rubocop failures --- lib/gitlab/email/html_parser.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/email/html_parser.rb b/lib/gitlab/email/html_parser.rb index 51d8edb1b13..3a723b777f5 100644 --- a/lib/gitlab/email/html_parser.rb +++ b/lib/gitlab/email/html_parser.rb @@ -20,7 +20,10 @@ module Gitlab end def filtered_html - @filtered_html ||= (filter_replies!; document.inner_html) + @filtered_html ||= begin + filter_replies! + document.inner_html + end end def filtered_text -- cgit v1.2.1 From b49a33ff6ba6e1a52ba9af1182018a3a5a0dfdf3 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 11 Nov 2016 08:10:44 +0900 Subject: use Nokogiri::HTML.parse instead of capitalized method --- lib/gitlab/email/html_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/email/html_parser.rb b/lib/gitlab/email/html_parser.rb index 3a723b777f5..598d5ce1a52 100644 --- a/lib/gitlab/email/html_parser.rb +++ b/lib/gitlab/email/html_parser.rb @@ -11,7 +11,7 @@ module Gitlab end def document - @document ||= Nokogiri::HTML(raw_body) + @document ||= Nokogiri::HTML.parse(raw_body) end def filter_replies! -- cgit v1.2.1 From 64e98040a77c05129fd49c01e1466f6027b8bc0e Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 11 Nov 2016 13:38:36 +0900 Subject: completely remove blockquote and table tags --- lib/gitlab/email/html_parser.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/email/html_parser.rb b/lib/gitlab/email/html_parser.rb index 598d5ce1a52..a4ca62bfc41 100644 --- a/lib/gitlab/email/html_parser.rb +++ b/lib/gitlab/email/html_parser.rb @@ -15,8 +15,8 @@ module Gitlab end def filter_replies! - document.xpath('//blockquote').each { |n| n.replace('> ') } - document.xpath('//table').each { |n| n.remove } + document.xpath('//blockquote').each(&:remove) + document.xpath('//table').each(&:remove) end def filtered_html -- cgit v1.2.1 From 4aabf707a94d79f961a0c88f30d77aa7fe49e149 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Wed, 16 Nov 2016 14:39:07 +0900 Subject: rename `part` variable and guard against nil content_type --- lib/gitlab/email/reply_parser.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 1ad44425c93..07860c36af8 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -23,15 +23,13 @@ module Gitlab private def select_body(message) - if message.multipart? - text = message.text_part || message.html_part || message + part = if message.multipart? + message.text_part || message.html_part || message else - text = message + message end - return "" unless text - - decoded = fix_charset(text) + decoded = fix_charset(part) return "" unless decoded @@ -40,7 +38,7 @@ module Gitlab return "" end - if text.content_type =~ %r(text/html) + if (part.content_type || '').include? 'text/html' HTMLParser.parse_reply(decoded) else decoded -- cgit v1.2.1 From 9df189bb4ac67be922dbbb408f16d7a4e7fb3c39 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Wed, 16 Nov 2016 17:45:44 +0900 Subject: fix indentation for rubocop --- lib/gitlab/email/reply_parser.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 07860c36af8..85402c2a278 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -23,10 +23,10 @@ module Gitlab private def select_body(message) - part = if message.multipart? - message.text_part || message.html_part || message + if message.multipart? + part = message.text_part || message.html_part || message else - message + part = message end decoded = fix_charset(part) -- cgit v1.2.1 From ec7db295d230ba1cec0c75ae199d60bd8f89c1d6 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 13 Oct 2016 17:11:28 +0200 Subject: Started refactoring stuff to add events to cycle analytics - Refactored cycle analytics class to extract DB logic - Reuse logic in new events fetcher - Started adding cycle analytics events class and spec (still not functional) --- lib/gitlab/cycle_analytics/events.rb | 15 ++++++ lib/gitlab/cycle_analytics/events_fetcher.rb | 27 ++++++++++ lib/gitlab/cycle_analytics/metrics_fetcher.rb | 72 +++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 lib/gitlab/cycle_analytics/events.rb create mode 100644 lib/gitlab/cycle_analytics/events_fetcher.rb create mode 100644 lib/gitlab/cycle_analytics/metrics_fetcher.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb new file mode 100644 index 00000000000..0cf3067d48e --- /dev/null +++ b/lib/gitlab/cycle_analytics/events.rb @@ -0,0 +1,15 @@ +module Gitlab + module CycleAnalytics + class Events + def initialize(project:, from:) + @project = project + @from = from + @fetcher = EventsFetcher.new(project: project, from: from) + end + + def issue_events + @fetcher.fetch_issues + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb new file mode 100644 index 00000000000..9e62eef706c --- /dev/null +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -0,0 +1,27 @@ +module Gitlab + module CycleAnalytics + class EventsFetcher + include MetricsFetcher + + def initialize(project:, from:) + @project = project + @from = from + end + + def fetch_issues + cte_table = Arel::Table.new("cte_table_for_issue") + + interval_query = Arel::Nodes::As.new( + cte_table, + subtract_datetimes(base_query_for(:issue), *attributes, 'issue')) + + #TODO ActiveRecord::Base.connection.execute(interval_query) + end + + def attributes + [issue_metrics_table[:first_associated_with_milestone_at], + issue_metrics_table[:first_added_to_board_at]] + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb new file mode 100644 index 00000000000..51b4963cf08 --- /dev/null +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -0,0 +1,72 @@ +module Gitlab + module CycleAnalytics + module MetricsFetcher + include Gitlab::Database::Median + include Gitlab::Database::DateTime + + DEPLOYMENT_METRIC_STAGES = %i[production staging] + + private + + def calculate_metric(name, start_time_attrs, end_time_attrs) + cte_table = Arel::Table.new("cte_table_for_#{name}") + + # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time). + # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time). + # We compute the (end_time - start_time) interval, and give it an alias based on the current + # cycle analytics stage. + interval_query = Arel::Nodes::As.new( + cte_table, + subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s)) + + median_datetime(cte_table, interval_query, name) + end + + # Join table with a row for every pair (where the merge request + # closes the given issue) with issue and merge request metrics included. The metrics + # are loaded with an inner join, so issues / merge requests without metrics are + # automatically excluded. + def base_query_for(name) + # Load issues + query = issue_metrics_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])). + join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])). + where(issue_table[:project_id].eq(@project.id)). + where(issue_table[:deleted_at].eq(nil)). + where(issue_table[:created_at].gteq(@from)) + + # Load merge_requests + query = query.join(merge_request_table, Arel::Nodes::OuterJoin). + on(merge_request_table[:id].eq(mr_closing_issues_table[:merge_request_id])). + join(merge_request_metrics_table). + on(merge_request_table[:id].eq(merge_request_metrics_table[:merge_request_id])) + + if DEPLOYMENT_METRIC_STAGES.include?(name) + # Limit to merge requests that have been deployed to production after `@from` + query.where(merge_request_metrics_table[:first_deployed_to_production_at].gteq(@from)) + end + + query + end + + def merge_request_metrics_table + MergeRequest::Metrics.arel_table + end + + def merge_request_table + MergeRequest.arel_table + end + + def mr_closing_issues_table + MergeRequestsClosingIssues.arel_table + end + + def issue_table + Issue.arel_table + end + + def issue_metrics_table + Issue::Metrics.arel_table + end + end + end +end -- cgit v1.2.1 From 470e39d64f02def538bb6aa3e51bcf406fd2e480 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 14 Oct 2016 17:33:21 +0200 Subject: WIP - refactored some arel queries --- lib/gitlab/cycle_analytics/events_fetcher.rb | 17 ++++++++++++----- lib/gitlab/cycle_analytics/metrics_fetcher.rb | 2 +- lib/gitlab/database/date_time.rb | 26 +++++++++++++++----------- 3 files changed, 28 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 9e62eef706c..ceed5823371 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -11,14 +11,21 @@ module Gitlab def fetch_issues cte_table = Arel::Table.new("cte_table_for_issue") - interval_query = Arel::Nodes::As.new( - cte_table, - subtract_datetimes(base_query_for(:issue), *attributes, 'issue')) + # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time). + # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time). + # We compute the (end_time - start_time) interval, and give it an alias based on the current + # cycle analytics stage. - #TODO ActiveRecord::Base.connection.execute(interval_query) + base_query = base_query_for(:issue) + + diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], metric_attributes) + + query = base_query.project(diff_fn.as('issue_diff')) + + ActiveRecord::Base.connection.execute(query.to_sql) end - def attributes + def metric_attributes [issue_metrics_table[:first_associated_with_milestone_at], issue_metrics_table[:first_added_to_board_at]] end diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index 51b4963cf08..97b55656d79 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -28,7 +28,7 @@ module Gitlab # automatically excluded. def base_query_for(name) # Load issues - query = issue_metrics_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])). + query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])). join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])). where(issue_table[:project_id].eq(@project.id)). where(issue_table[:deleted_at].eq(nil)). diff --git a/lib/gitlab/database/date_time.rb b/lib/gitlab/database/date_time.rb index b6a89f715fd..e2524886de2 100644 --- a/lib/gitlab/database/date_time.rb +++ b/lib/gitlab/database/date_time.rb @@ -8,20 +8,24 @@ module Gitlab # Note: For MySQL, the interval is returned in seconds. # For PostgreSQL, the interval is returned as an INTERVAL type. def subtract_datetimes(query_so_far, end_time_attrs, start_time_attrs, as) - diff_fn = if Gitlab::Database.postgresql? - Arel::Nodes::Subtraction.new( - Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs)), - Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs))) - elsif Gitlab::Database.mysql? - Arel::Nodes::NamedFunction.new( - "TIMESTAMPDIFF", - [Arel.sql('second'), - Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs)), - Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs))]) - end + diff_fn = subtract_datetimes_diff(query_so_far, end_time_attrs, start_time_attrs) query_so_far.project(diff_fn.as(as)) end + + def subtract_datetimes_diff(query_so_far, end_time_attrs, start_time_attrs) + if Gitlab::Database.postgresql? + Arel::Nodes::Subtraction.new( + Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs)), + Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs))) + elsif Gitlab::Database.mysql? + Arel::Nodes::NamedFunction.new( + "TIMESTAMPDIFF", + [Arel.sql('second'), + Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs)), + Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs))]) + end + end end end end -- cgit v1.2.1 From 847d2796cfeda0347aae0649fed16250f6188ca9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 17 Oct 2016 14:57:23 +0200 Subject: fixed spec and SQL query --- lib/gitlab/cycle_analytics/events_fetcher.rb | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index ceed5823371..eb3cf9f2476 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -9,26 +9,28 @@ module Gitlab end def fetch_issues - cte_table = Arel::Table.new("cte_table_for_issue") - - # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time). - # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time). - # We compute the (end_time - start_time) interval, and give it an alias based on the current - # cycle analytics stage. - base_query = base_query_for(:issue) - diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], metric_attributes) - query = base_query.project(diff_fn.as('issue_diff')) + query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])). + project(diff_fn.as('issue_diff'), *issue_projections). + order(issue_table[:created_at].desc) - ActiveRecord::Base.connection.execute(query.to_sql) + ActiveRecord::Base.connection.execute(query.to_sql).first end def metric_attributes [issue_metrics_table[:first_associated_with_milestone_at], issue_metrics_table[:first_added_to_board_at]] end + + def issue_projections + [issue_table[:title], issue_table[:iid], issue_table[:created_at], User.arel_table[:name]] + end + + def user_table + User.arel_table + end end end end -- cgit v1.2.1 From c545968ece739e616047c519dbc66e4e428cdd8a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 17 Oct 2016 18:03:17 +0200 Subject: fix timestamp diff and spec --- lib/gitlab/cycle_analytics/events.rb | 6 +++++- lib/gitlab/cycle_analytics/events_fetcher.rb | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 0cf3067d48e..04cd599f092 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -1,6 +1,8 @@ module Gitlab module CycleAnalytics class Events + include ActionView::Helpers::DateHelper + def initialize(project:, from:) @project = project @from = from @@ -8,7 +10,9 @@ module Gitlab end def issue_events - @fetcher.fetch_issues + @fetcher.fetch_issues.each do |event| + event['issue_diff'] = distance_of_time_in_words(event['issue_diff'].to_f) + end end end end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index eb3cf9f2476..14405f1356a 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -13,10 +13,10 @@ module Gitlab diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], metric_attributes) query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])). - project(diff_fn.as('issue_diff'), *issue_projections). + project(extract_epoch(diff_fn).as('issue_diff'), *issue_projections). order(issue_table[:created_at].desc) - ActiveRecord::Base.connection.execute(query.to_sql).first + ActiveRecord::Base.connection.execute(query.to_sql) end def metric_attributes @@ -31,6 +31,10 @@ module Gitlab def user_table User.arel_table end + + def extract_epoch(arel_attribute) + Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))}) + end end end end -- cgit v1.2.1 From 1d6068a1c3c906e0e82f0dce884e59aa0da76506 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 18 Oct 2016 10:06:42 +0200 Subject: fix specs - issue events working --- lib/gitlab/cycle_analytics/events.rb | 7 ++++++- lib/gitlab/cycle_analytics/events_fetcher.rb | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 04cd599f092..d7248b28527 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -11,9 +11,14 @@ module Gitlab def issue_events @fetcher.fetch_issues.each do |event| - event['issue_diff'] = distance_of_time_in_words(event['issue_diff'].to_f) + event['issue_diff'] = interval_in_words(event['issue_diff']) + event['created_at'] = interval_in_words(event['created_at']) end end + + def interval_in_words(diff) + "#{distance_of_time_in_words( diff.to_f)} ago" + end end end end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 14405f1356a..0920ab73ae1 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -16,7 +16,7 @@ module Gitlab project(extract_epoch(diff_fn).as('issue_diff'), *issue_projections). order(issue_table[:created_at].desc) - ActiveRecord::Base.connection.execute(query.to_sql) + ActiveRecord::Base.connection.execute(query.to_sql).to_a end def metric_attributes -- cgit v1.2.1 From 3b5d947730eb1f979bcb9033d63bdceb11fbb8ca Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 19 Oct 2016 12:47:09 +0200 Subject: commit events and spec --- lib/gitlab/cycle_analytics/events.rb | 16 +++++++++++++--- lib/gitlab/cycle_analytics/events_fetcher.rb | 25 +++++++++++++++++++++---- lib/gitlab/cycle_analytics/metrics_fetcher.rb | 4 ++++ 3 files changed, 38 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index d7248b28527..bdd45e72000 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -9,15 +9,25 @@ module Gitlab @fetcher = EventsFetcher.new(project: project, from: from) end + #TODO: backend pagination - specially for commits, etc... + def issue_events - @fetcher.fetch_issues.each do |event| - event['issue_diff'] = interval_in_words(event['issue_diff']) + #TODO figure out what the frontend needs for displaying the avatar + @fetcher.fetch_issue_events.each do |event| + event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['created_at'] = interval_in_words(event['created_at']) end end + def plan_events + # TODO sort out 1st referenced commit and parse stuff + @fetcher.fetch_plan_events + end + + private + def interval_in_words(diff) - "#{distance_of_time_in_words( diff.to_f)} ago" + "#{distance_of_time_in_words(diff.to_f)} ago" end end end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 0920ab73ae1..f676183f08c 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -8,22 +8,39 @@ module Gitlab @from = from end - def fetch_issues + def fetch_issue_events base_query = base_query_for(:issue) - diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], metric_attributes) + diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], issue_attributes) query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])). - project(extract_epoch(diff_fn).as('issue_diff'), *issue_projections). + project(extract_epoch(diff_fn).as('total_time'), *issue_projections). order(issue_table[:created_at].desc) ActiveRecord::Base.connection.execute(query.to_sql).to_a end - def metric_attributes + def fetch_plan_events + base_query = base_query_for(:plan) + diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], plan_attributes) + + query = base_query.join(merge_request_diff_table).on(merge_request_diff_table[:merge_request_id].eq(merge_request_table[:id])). + project(merge_request_diff_table[:st_commits].as(:commits), extract_epoch(diff_fn).as('total_time')). + order(issue_table[:created_at].desc) + + ActiveRecord::Base.connection.execute(query.to_sql).to_a + end + + private + + def issue_attributes [issue_metrics_table[:first_associated_with_milestone_at], issue_metrics_table[:first_added_to_board_at]] end + def plan_attributes + issue_attributes + [issue_metrics_table[:first_mentioned_in_commit_at]] + end + def issue_projections [issue_table[:title], issue_table[:iid], issue_table[:created_at], User.arel_table[:name]] end diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index 97b55656d79..b5ff4c0ef5a 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -56,6 +56,10 @@ module Gitlab MergeRequest.arel_table end + def merge_request_diff_table + MergeRequestDiff.arel_table + end + def mr_closing_issues_table MergeRequestsClosingIssues.arel_table end -- cgit v1.2.1 From 72660d58af13b4a768840307d2870ac08dd088ef Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 19 Oct 2016 15:40:14 +0200 Subject: plan events and spec working. Also added some TODOs to the code. --- lib/gitlab/cycle_analytics/events.rb | 15 +++++++++++++-- lib/gitlab/cycle_analytics/events_fetcher.rb | 8 ++++++-- lib/gitlab/cycle_analytics/metrics_fetcher.rb | 16 ++++++++-------- 3 files changed, 27 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index bdd45e72000..51fa4653634 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -20,12 +20,23 @@ module Gitlab end def plan_events - # TODO sort out 1st referenced commit and parse stuff - @fetcher.fetch_plan_events + @fetcher.fetch_plan_events.each do |event| + event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) + commits = event.delete('commits') + event['commit'] = first_time_reference_commit(commits, event) + end end private + def first_time_reference_commit(commits, event) + st_commit = YAML.load(commits).detect do |commit| + commit['created_at'] == event['first_mentioned_in_commit_at'] + end + + Commit.new(Gitlab::Git::Commit.new(st_commit), @project) + end + def interval_in_words(diff) "#{distance_of_time_in_words(diff.to_f)} ago" end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index f676183f08c..c25bc6c5f32 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -23,8 +23,8 @@ module Gitlab base_query = base_query_for(:plan) diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], plan_attributes) - query = base_query.join(merge_request_diff_table).on(merge_request_diff_table[:merge_request_id].eq(merge_request_table[:id])). - project(merge_request_diff_table[:st_commits].as(:commits), extract_epoch(diff_fn).as('total_time')). + query = base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])). + project(extract_epoch(diff_fn).as('total_time'), *plan_projections). order(issue_table[:created_at].desc) ActiveRecord::Base.connection.execute(query.to_sql).to_a @@ -45,6 +45,10 @@ module Gitlab [issue_table[:title], issue_table[:iid], issue_table[:created_at], User.arel_table[:name]] end + def plan_projections + [mr_diff_table[:st_commits].as('commits'), issue_metrics_table[:first_mentioned_in_commit_at]] + end + def user_table User.arel_table end diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index b5ff4c0ef5a..7fcacf652a6 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -35,28 +35,28 @@ module Gitlab where(issue_table[:created_at].gteq(@from)) # Load merge_requests - query = query.join(merge_request_table, Arel::Nodes::OuterJoin). - on(merge_request_table[:id].eq(mr_closing_issues_table[:merge_request_id])). - join(merge_request_metrics_table). - on(merge_request_table[:id].eq(merge_request_metrics_table[:merge_request_id])) + query = query.join(mr_table, Arel::Nodes::OuterJoin). + on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id])). + join(mr_metrics_table). + on(mr_table[:id].eq(mr_metrics_table[:merge_request_id])) if DEPLOYMENT_METRIC_STAGES.include?(name) # Limit to merge requests that have been deployed to production after `@from` - query.where(merge_request_metrics_table[:first_deployed_to_production_at].gteq(@from)) + query.where(mr_metrics_table[:first_deployed_to_production_at].gteq(@from)) end query end - def merge_request_metrics_table + def mr_metrics_table MergeRequest::Metrics.arel_table end - def merge_request_table + def mr_table MergeRequest.arel_table end - def merge_request_diff_table + def mr_diff_table MergeRequestDiff.arel_table end -- cgit v1.2.1 From 1a4ff5d720fa0ec65f925ef381fced3d5f9a040f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 20 Oct 2016 10:08:53 +0200 Subject: Added code events spec and logic. Also fixed SQL issues and refactored the code a bit. --- lib/gitlab/cycle_analytics/events.rb | 11 +++++++-- lib/gitlab/cycle_analytics/events_fetcher.rb | 34 +++++++++++++++++++++++---- lib/gitlab/cycle_analytics/metrics_fetcher.rb | 2 +- lib/gitlab/database/date_time.rb | 4 ++-- 4 files changed, 41 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 51fa4653634..ea8e78d7ab7 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -9,10 +9,10 @@ module Gitlab @fetcher = EventsFetcher.new(project: project, from: from) end - #TODO: backend pagination - specially for commits, etc... + # TODO: backend pagination - specially for commits, etc... def issue_events - #TODO figure out what the frontend needs for displaying the avatar + # TODO figure out what the frontend needs for displaying the avatar @fetcher.fetch_issue_events.each do |event| event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['created_at'] = interval_in_words(event['created_at']) @@ -27,6 +27,13 @@ module Gitlab end end + def code_events + @fetcher.fetch_code_events.each do |event| + event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) + event['created_at'] = interval_in_words(event['created_at']) + end + end + private def first_time_reference_commit(commits, event) diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index c25bc6c5f32..cb61cda200d 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -16,18 +16,33 @@ module Gitlab project(extract_epoch(diff_fn).as('total_time'), *issue_projections). order(issue_table[:created_at].desc) - ActiveRecord::Base.connection.execute(query.to_sql).to_a + execute(query) end def fetch_plan_events base_query = base_query_for(:plan) - diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], plan_attributes) + diff_fn = subtract_datetimes_diff(base_query, + issue_metrics_table[:first_associated_with_milestone_at], + plan_attributes) query = base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])). project(extract_epoch(diff_fn).as('total_time'), *plan_projections). - order(issue_table[:created_at].desc) + order(issue_metrics_table[:first_associated_with_milestone_at].desc) - ActiveRecord::Base.connection.execute(query.to_sql).to_a + execute(query) + end + + def fetch_code_events + base_query = base_query_for(:code) + diff_fn = subtract_datetimes_diff(base_query, + issue_metrics_table[:first_mentioned_in_commit_at], + issue_table[:created_at]) + + query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])). + project(extract_epoch(diff_fn).as('total_time'), *code_projections). + order(mr_table[:created_at].desc) + + execute(query) end private @@ -38,7 +53,8 @@ module Gitlab end def plan_attributes - issue_attributes + [issue_metrics_table[:first_mentioned_in_commit_at]] + [issue_metrics_table[:first_added_to_board_at], + issue_metrics_table[:first_mentioned_in_commit_at]] end def issue_projections @@ -49,6 +65,10 @@ module Gitlab [mr_diff_table[:st_commits].as('commits'), issue_metrics_table[:first_mentioned_in_commit_at]] end + def code_projections + [mr_table[:title], mr_table[:iid], mr_table[:created_at], User.arel_table[:name]] + end + def user_table User.arel_table end @@ -56,6 +76,10 @@ module Gitlab def extract_epoch(arel_attribute) Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))}) end + + def execute(query) + ActiveRecord::Base.connection.execute(query.to_sql).to_a + end end end end diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index 7fcacf652a6..62988fbdec1 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -17,7 +17,7 @@ module Gitlab # cycle analytics stage. interval_query = Arel::Nodes::As.new( cte_table, - subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s)) + subtract_datetimes(base_query_for(name), start_time_attrs, end_time_attrs, name.to_s)) median_datetime(cte_table, interval_query, name) end diff --git a/lib/gitlab/database/date_time.rb b/lib/gitlab/database/date_time.rb index e2524886de2..38a6fd4276b 100644 --- a/lib/gitlab/database/date_time.rb +++ b/lib/gitlab/database/date_time.rb @@ -7,13 +7,13 @@ module Gitlab # # Note: For MySQL, the interval is returned in seconds. # For PostgreSQL, the interval is returned as an INTERVAL type. - def subtract_datetimes(query_so_far, end_time_attrs, start_time_attrs, as) + def subtract_datetimes(query_so_far, start_time_attrs, end_time_attrs, as) diff_fn = subtract_datetimes_diff(query_so_far, end_time_attrs, start_time_attrs) query_so_far.project(diff_fn.as(as)) end - def subtract_datetimes_diff(query_so_far, end_time_attrs, start_time_attrs) + def subtract_datetimes_diff(query_so_far, start_time_attrs, end_time_attrs) if Gitlab::Database.postgresql? Arel::Nodes::Subtraction.new( Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs)), -- cgit v1.2.1 From ebd5ced7eb296ce10160021d8999d21b36b24da9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 20 Oct 2016 16:20:04 +0200 Subject: Added test events specs and logic. Also fixed some SQL and refactored the pipeline worker spec. --- lib/gitlab/cycle_analytics/events.rb | 7 +++++++ lib/gitlab/cycle_analytics/events_fetcher.rb | 14 +++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index ea8e78d7ab7..739c8772080 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -34,6 +34,13 @@ module Gitlab end end + def test_events + @fetcher.fetch_test_events.each do |event| + event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) + event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline + end + end + private def first_time_reference_commit(commits, event) diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index cb61cda200d..d12161be0d3 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -36,7 +36,7 @@ module Gitlab base_query = base_query_for(:code) diff_fn = subtract_datetimes_diff(base_query, issue_metrics_table[:first_mentioned_in_commit_at], - issue_table[:created_at]) + mr_table[:created_at]) query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])). project(extract_epoch(diff_fn).as('total_time'), *code_projections). @@ -45,6 +45,18 @@ module Gitlab execute(query) end + def fetch_test_events + base_query = base_query_for(:code) + diff_fn = subtract_datetimes_diff(base_query, + mr_metrics_table[:latest_build_started_at], + mr_metrics_table[:latest_build_finished_at]) + + query = base_query.project(extract_epoch(diff_fn).as('total_time'), mr_metrics_table[:ci_commit_id]). + order(mr_table[:created_at].desc) + + execute(query) + end + private def issue_attributes -- cgit v1.2.1 From d99cec7f55fb52dcddc380592e2fbb5ffc735f74 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 21 Oct 2016 08:50:27 +0200 Subject: review events - spec and logic --- lib/gitlab/cycle_analytics/events.rb | 19 +++++++++++-------- lib/gitlab/cycle_analytics/events_fetcher.rb | 13 +++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 739c8772080..42ef864c916 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -13,10 +13,7 @@ module Gitlab def issue_events # TODO figure out what the frontend needs for displaying the avatar - @fetcher.fetch_issue_events.each do |event| - event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) - event['created_at'] = interval_in_words(event['created_at']) - end + @fetcher.fetch_issue_events { |event| parse_event(event) } end def plan_events @@ -28,10 +25,7 @@ module Gitlab end def code_events - @fetcher.fetch_code_events.each do |event| - event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) - event['created_at'] = interval_in_words(event['created_at']) - end + @fetcher.fetch_code_events { |event| parse_event(event) } end def test_events @@ -41,6 +35,15 @@ module Gitlab end end + def review_events + @fetcher.fetch_review_events.each { |event| parse_event(event) } + end + + def parse_event(event) + event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) + event['created_at'] = interval_in_words(event['created_at']) + end + private def first_time_reference_commit(commits, event) diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index d12161be0d3..1be68c771cd 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -57,6 +57,19 @@ module Gitlab execute(query) end + def fetch_review_events + base_query = base_query_for(:code) + diff_fn = subtract_datetimes_diff(base_query, + mr_table[:created_at], + mr_metrics_table[:merged_at]) + + query = base_query.join(user_table).on(mr_table[:author_id].eq(user_table[:id])). + project(extract_epoch(diff_fn).as('total_time'), *code_projections). + order(mr_table[:created_at].desc) + + execute(query) + end + private def issue_attributes -- cgit v1.2.1 From f8acc7ea77bb52531b11abea5eabd68a38236cff Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 21 Oct 2016 09:44:04 +0200 Subject: fixing spec failures --- lib/gitlab/cycle_analytics/events.rb | 8 ++++---- lib/gitlab/database/date_time.rb | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 42ef864c916..a16c15ed3a8 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -13,7 +13,7 @@ module Gitlab def issue_events # TODO figure out what the frontend needs for displaying the avatar - @fetcher.fetch_issue_events { |event| parse_event(event) } + @fetcher.fetch_issue_events.each { |event| parse_event(event) } end def plan_events @@ -25,7 +25,7 @@ module Gitlab end def code_events - @fetcher.fetch_code_events { |event| parse_event(event) } + @fetcher.fetch_code_events.each { |event| parse_event(event) } end def test_events @@ -39,13 +39,13 @@ module Gitlab @fetcher.fetch_review_events.each { |event| parse_event(event) } end + private + def parse_event(event) event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['created_at'] = interval_in_words(event['created_at']) end - private - def first_time_reference_commit(commits, event) st_commit = YAML.load(commits).detect do |commit| commit['created_at'] == event['first_mentioned_in_commit_at'] diff --git a/lib/gitlab/database/date_time.rb b/lib/gitlab/database/date_time.rb index 38a6fd4276b..25e56998038 100644 --- a/lib/gitlab/database/date_time.rb +++ b/lib/gitlab/database/date_time.rb @@ -8,7 +8,7 @@ module Gitlab # Note: For MySQL, the interval is returned in seconds. # For PostgreSQL, the interval is returned as an INTERVAL type. def subtract_datetimes(query_so_far, start_time_attrs, end_time_attrs, as) - diff_fn = subtract_datetimes_diff(query_so_far, end_time_attrs, start_time_attrs) + diff_fn = subtract_datetimes_diff(query_so_far, start_time_attrs, end_time_attrs) query_so_far.project(diff_fn.as(as)) end -- cgit v1.2.1 From 1f701cb5e2fc9ea3af98ab0e6e07a7568a0e54dc Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 21 Oct 2016 11:33:37 +0200 Subject: added staging events and spec --- lib/gitlab/cycle_analytics/events.rb | 7 +++++++ lib/gitlab/cycle_analytics/events_fetcher.rb | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index a16c15ed3a8..3c0b9a7ddfa 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -39,6 +39,13 @@ module Gitlab @fetcher.fetch_review_events.each { |event| parse_event(event) } end + def staging_events + @fetcher.fetch_staging_events.each do |event| + event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) + event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline + end + end + private def parse_event(event) diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 1be68c771cd..bddaede53b7 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -46,7 +46,7 @@ module Gitlab end def fetch_test_events - base_query = base_query_for(:code) + base_query = base_query_for(:test) diff_fn = subtract_datetimes_diff(base_query, mr_metrics_table[:latest_build_started_at], mr_metrics_table[:latest_build_finished_at]) @@ -58,7 +58,7 @@ module Gitlab end def fetch_review_events - base_query = base_query_for(:code) + base_query = base_query_for(:review) diff_fn = subtract_datetimes_diff(base_query, mr_table[:created_at], mr_metrics_table[:merged_at]) @@ -70,6 +70,18 @@ module Gitlab execute(query) end + def fetch_staging_events + base_query = base_query_for(:staging) + diff_fn = subtract_datetimes_diff(base_query, + mr_metrics_table[:merged_at], + mr_metrics_table[:first_deployed_to_production_at]) + + query = base_query.project(extract_epoch(diff_fn).as('total_time'), mr_metrics_table[:ci_commit_id]). + order(mr_table[:created_at].desc) + + execute(query) + end + private def issue_attributes -- cgit v1.2.1 From 954e18922e7cacfa4ad39c6c27d34bd94c3038b2 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 21 Oct 2016 11:37:47 +0200 Subject: fix mySQL error --- lib/gitlab/cycle_analytics/events_fetcher.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index bddaede53b7..742ee2f4fb6 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -111,6 +111,8 @@ module Gitlab end def extract_epoch(arel_attribute) + return arel_attribute unless Gitlab::Database.postgresql? + Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))}) end -- cgit v1.2.1 From 275292de4768ab9893d9d786b9fbb72bf173ba3c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 21 Oct 2016 17:01:53 +0200 Subject: Refactor the SQL/query stuff into a dynamic class. Also merged all the config in a single hash. --- lib/gitlab/cycle_analytics/events.rb | 12 +- lib/gitlab/cycle_analytics/events_fetcher.rb | 154 +++++++++----------------- lib/gitlab/cycle_analytics/events_query.rb | 41 +++++++ lib/gitlab/cycle_analytics/metrics_fetcher.rb | 8 ++ 4 files changed, 108 insertions(+), 107 deletions(-) create mode 100644 lib/gitlab/cycle_analytics/events_query.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 3c0b9a7ddfa..3ab544faf5d 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -13,11 +13,11 @@ module Gitlab def issue_events # TODO figure out what the frontend needs for displaying the avatar - @fetcher.fetch_issue_events.each { |event| parse_event(event) } + @fetcher.fetch(stage: :issue).each { |event| parse_event(event) } end def plan_events - @fetcher.fetch_plan_events.each do |event| + @fetcher.fetch(stage: :plan).each do |event| event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) commits = event.delete('commits') event['commit'] = first_time_reference_commit(commits, event) @@ -25,22 +25,22 @@ module Gitlab end def code_events - @fetcher.fetch_code_events.each { |event| parse_event(event) } + @fetcher.fetch(stage: :code).each { |event| parse_event(event) } end def test_events - @fetcher.fetch_test_events.each do |event| + @fetcher.fetch(stage: :test).each do |event| event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline end end def review_events - @fetcher.fetch_review_events.each { |event| parse_event(event) } + @fetcher.fetch(stage: :review).each { |event| parse_event(event) } end def staging_events - @fetcher.fetch_staging_events.each do |event| + @fetcher.fetch(stage: :staging).each do |event| event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 742ee2f4fb6..75a6779e0e8 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -3,121 +3,73 @@ module Gitlab class EventsFetcher include MetricsFetcher + EVENTS_CONFIG = { + issue: { + start_time_attrs: issue_table[:created_at], + end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at], + issue_metrics_table[:first_added_to_board_at]], + projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name]], + project: @project, + from: @from + }, + plan: { + start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], + end_time_attrs: [issue_metrics_table[:first_added_to_board_at], + issue_metrics_table[:first_mentioned_in_commit_at]], + projections: [mr_diff_table[:st_commits].as('commits'), issue_metrics_table[:first_mentioned_in_commit_at]] + }, + code: { + start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at], + end_time_attrs: mr_table[:created_at], + projections: [mr_table[:title], mr_table[:iid], mr_table[:created_at], user_table[:name]], + order: mr_table[:created_at] + }, + test: { + start_time_attrs: mr_metrics_table[:latest_build_started_at], + end_time_attrs: mr_metrics_table[:latest_build_finished_at], + projections: mr_metrics_table[:ci_commit_id], + order: mr_table[:created_at] + }, + review: { + start_time_attrs: mr_table[:created_at], + end_time_attrs: mr_metrics_table[:merged_at], + projections: [mr_table[:title], mr_table[:iid], mr_table[:created_at], user_table[:name]] + }, + staging: { + start_time_attrs: mr_metrics_table[:merged_at], + end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], + projections: mr_metrics_table[:ci_commit_id] + } + }.freeze + def initialize(project:, from:) @project = project @from = from + @query = EventsQuery.new(project: project, from: from) end - def fetch_issue_events - base_query = base_query_for(:issue) - diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], issue_attributes) - - query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])). - project(extract_epoch(diff_fn).as('total_time'), *issue_projections). - order(issue_table[:created_at].desc) - - execute(query) - end - - def fetch_plan_events - base_query = base_query_for(:plan) - diff_fn = subtract_datetimes_diff(base_query, - issue_metrics_table[:first_associated_with_milestone_at], - plan_attributes) - - query = base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])). - project(extract_epoch(diff_fn).as('total_time'), *plan_projections). - order(issue_metrics_table[:first_associated_with_milestone_at].desc) - - execute(query) - end - - def fetch_code_events - base_query = base_query_for(:code) - diff_fn = subtract_datetimes_diff(base_query, - issue_metrics_table[:first_mentioned_in_commit_at], - mr_table[:created_at]) - - query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])). - project(extract_epoch(diff_fn).as('total_time'), *code_projections). - order(mr_table[:created_at].desc) - - execute(query) - end - - def fetch_test_events - base_query = base_query_for(:test) - diff_fn = subtract_datetimes_diff(base_query, - mr_metrics_table[:latest_build_started_at], - mr_metrics_table[:latest_build_finished_at]) - - query = base_query.project(extract_epoch(diff_fn).as('total_time'), mr_metrics_table[:ci_commit_id]). - order(mr_table[:created_at].desc) - - execute(query) - end - - def fetch_review_events - base_query = base_query_for(:review) - diff_fn = subtract_datetimes_diff(base_query, - mr_table[:created_at], - mr_metrics_table[:merged_at]) - - query = base_query.join(user_table).on(mr_table[:author_id].eq(user_table[:id])). - project(extract_epoch(diff_fn).as('total_time'), *code_projections). - order(mr_table[:created_at].desc) - - execute(query) - end - - def fetch_staging_events - base_query = base_query_for(:staging) - diff_fn = subtract_datetimes_diff(base_query, - mr_metrics_table[:merged_at], - mr_metrics_table[:first_deployed_to_production_at]) - - query = base_query.project(extract_epoch(diff_fn).as('total_time'), mr_metrics_table[:ci_commit_id]). - order(mr_table[:created_at].desc) - - execute(query) - end - - private - - def issue_attributes - [issue_metrics_table[:first_associated_with_milestone_at], - issue_metrics_table[:first_added_to_board_at]] - end - - def plan_attributes - [issue_metrics_table[:first_added_to_board_at], - issue_metrics_table[:first_mentioned_in_commit_at]] - end - - def issue_projections - [issue_table[:title], issue_table[:iid], issue_table[:created_at], User.arel_table[:name]] - end + def fetch(stage:) + custom_query = "#{stage}_custom_query".to_sym - def plan_projections - [mr_diff_table[:st_commits].as('commits'), issue_metrics_table[:first_mentioned_in_commit_at]] + @query.execute(stage, EVENTS_CONFIG[stage]) do |base_query| + public_send(custom_query, base_query) if self.respond_to?(custom_query) + end end - def code_projections - [mr_table[:title], mr_table[:iid], mr_table[:created_at], User.arel_table[:name]] + def issue_custom_query(base_query) + base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])) end - def user_table - User.arel_table + def plan_custom_query(base_query) + base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) end - def extract_epoch(arel_attribute) - return arel_attribute unless Gitlab::Database.postgresql? - - Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))}) + def code_custom_query(base_query) + base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])) end - def execute(query) - ActiveRecord::Base.connection.execute(query.to_sql).to_a + def review_custom_query(base_query) + base_query.join(user_table).on(mr_table[:author_id].eq(user_table[:id])) end end end diff --git a/lib/gitlab/cycle_analytics/events_query.rb b/lib/gitlab/cycle_analytics/events_query.rb new file mode 100644 index 00000000000..39687305bc3 --- /dev/null +++ b/lib/gitlab/cycle_analytics/events_query.rb @@ -0,0 +1,41 @@ +module Gitlab + module CycleAnalytics + class EventsQuery + include MetricsFetcher + + def initialize(project:, from:) + @project = project + @from = from + end + + def execute(stage, config, &block) + @stage = stage + @config = config + query = build_query(&block) + + ActiveRecord::Base.connection.execute(query.to_sql).to_a + end + + private + + def build_query + base_query = base_query_for(@stage) + diff_fn = subtract_datetimes_diff(@config[:base_query], @config[:start_time_attrs], @config[:end_time_attrs]) + + yield base_query if block_given? + + base_query.project(extract_epoch(diff_fn).as('total_time'), *@config[:projections]).order(order.desc) + end + + def order + @config[:order] || @config[:start_time_attrs] + end + + def extract_epoch(arel_attribute) + return arel_attribute unless Gitlab::Database.postgresql? + + Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))}) + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index 62988fbdec1..c1cbedc045b 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -6,6 +6,10 @@ module Gitlab DEPLOYMENT_METRIC_STAGES = %i[production staging] + def self.included(klass) + klass.extend self + end + private def calculate_metric(name, start_time_attrs, end_time_attrs) @@ -71,6 +75,10 @@ module Gitlab def issue_metrics_table Issue::Metrics.arel_table end + + def user_table + User.arel_table + end end end end -- cgit v1.2.1 From 3cdc9af78ebb71e2b91046e9dea8695aa04f6713 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 21 Oct 2016 17:30:38 +0200 Subject: added production events and related spec --- lib/gitlab/cycle_analytics/events.rb | 6 +++++- lib/gitlab/cycle_analytics/events_fetcher.rb | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 3ab544faf5d..912694fa1ad 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -10,9 +10,9 @@ module Gitlab end # TODO: backend pagination - specially for commits, etc... + # TODO figure out what the frontend needs for displaying the avatar def issue_events - # TODO figure out what the frontend needs for displaying the avatar @fetcher.fetch(stage: :issue).each { |event| parse_event(event) } end @@ -46,6 +46,10 @@ module Gitlab end end + def production_events + @fetcher.fetch(stage: :production).each { |event| parse_event(event) } + end + private def parse_event(event) diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 75a6779e0e8..03b1878dc1c 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -8,9 +8,7 @@ module Gitlab start_time_attrs: issue_table[:created_at], end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at], issue_metrics_table[:first_added_to_board_at]], - projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name]], - project: @project, - from: @from + projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name]] }, plan: { start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], @@ -39,12 +37,15 @@ module Gitlab start_time_attrs: mr_metrics_table[:merged_at], end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], projections: mr_metrics_table[:ci_commit_id] - } + }, + production: { + start_time_attrs: issue_table[:created_at], + end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], + projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name]] + }, }.freeze def initialize(project:, from:) - @project = project - @from = from @query = EventsQuery.new(project: project, from: from) end @@ -60,14 +61,13 @@ module Gitlab base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])) end + alias_method :code_custom_query, :issue_custom_query + alias_method :production_custom_query, :issue_custom_query + def plan_custom_query(base_query) base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) end - def code_custom_query(base_query) - base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])) - end - def review_custom_query(base_query) base_query.join(user_table).on(mr_table[:author_id].eq(user_table[:id])) end -- cgit v1.2.1 From ca9ae8bf63ff94c68143823046ab2a7466f30b1c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 27 Oct 2016 17:52:42 +0100 Subject: add email to user related queries so it can be used for displaying avatar in the UI --- lib/gitlab/cycle_analytics/events.rb | 1 - lib/gitlab/cycle_analytics/events_fetcher.rb | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 912694fa1ad..08070c2bac2 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -10,7 +10,6 @@ module Gitlab end # TODO: backend pagination - specially for commits, etc... - # TODO figure out what the frontend needs for displaying the avatar def issue_events @fetcher.fetch(stage: :issue).each { |event| parse_event(event) } diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 03b1878dc1c..2686ac353d6 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -8,7 +8,7 @@ module Gitlab start_time_attrs: issue_table[:created_at], end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at], issue_metrics_table[:first_added_to_board_at]], - projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name]] + projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name], user_table[:email]] }, plan: { start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], @@ -19,7 +19,7 @@ module Gitlab code: { start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at], end_time_attrs: mr_table[:created_at], - projections: [mr_table[:title], mr_table[:iid], mr_table[:created_at], user_table[:name]], + projections: [mr_table[:title], mr_table[:iid], mr_table[:created_at], user_table[:name], user_table[:email]], order: mr_table[:created_at] }, test: { @@ -31,7 +31,7 @@ module Gitlab review: { start_time_attrs: mr_table[:created_at], end_time_attrs: mr_metrics_table[:merged_at], - projections: [mr_table[:title], mr_table[:iid], mr_table[:created_at], user_table[:name]] + projections: [mr_table[:title], mr_table[:iid], mr_table[:created_at], user_table[:name], user_table[:email]] }, staging: { start_time_attrs: mr_metrics_table[:merged_at], @@ -41,7 +41,7 @@ module Gitlab production: { start_time_attrs: issue_table[:created_at], end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], - projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name]] + projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name], user_table[:email]] }, }.freeze -- cgit v1.2.1 From f9fd0ff1ca2a559ef7a82337f66e15e0d2d06e16 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 28 Oct 2016 12:46:38 +0100 Subject: Some refactoring - used class instead of hash for the query configuration --- lib/gitlab/cycle_analytics/events_fetcher.rb | 48 ++------------- lib/gitlab/cycle_analytics/events_query.rb | 4 +- lib/gitlab/cycle_analytics/query_config.rb | 89 ++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 46 deletions(-) create mode 100644 lib/gitlab/cycle_analytics/query_config.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 2686ac353d6..0248ebc7193 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -3,48 +3,6 @@ module Gitlab class EventsFetcher include MetricsFetcher - EVENTS_CONFIG = { - issue: { - start_time_attrs: issue_table[:created_at], - end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at], - issue_metrics_table[:first_added_to_board_at]], - projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name], user_table[:email]] - }, - plan: { - start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], - end_time_attrs: [issue_metrics_table[:first_added_to_board_at], - issue_metrics_table[:first_mentioned_in_commit_at]], - projections: [mr_diff_table[:st_commits].as('commits'), issue_metrics_table[:first_mentioned_in_commit_at]] - }, - code: { - start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at], - end_time_attrs: mr_table[:created_at], - projections: [mr_table[:title], mr_table[:iid], mr_table[:created_at], user_table[:name], user_table[:email]], - order: mr_table[:created_at] - }, - test: { - start_time_attrs: mr_metrics_table[:latest_build_started_at], - end_time_attrs: mr_metrics_table[:latest_build_finished_at], - projections: mr_metrics_table[:ci_commit_id], - order: mr_table[:created_at] - }, - review: { - start_time_attrs: mr_table[:created_at], - end_time_attrs: mr_metrics_table[:merged_at], - projections: [mr_table[:title], mr_table[:iid], mr_table[:created_at], user_table[:name], user_table[:email]] - }, - staging: { - start_time_attrs: mr_metrics_table[:merged_at], - end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], - projections: mr_metrics_table[:ci_commit_id] - }, - production: { - start_time_attrs: issue_table[:created_at], - end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], - projections: [issue_table[:title], issue_table[:iid], issue_table[:created_at], user_table[:name], user_table[:email]] - }, - }.freeze - def initialize(project:, from:) @query = EventsQuery.new(project: project, from: from) end @@ -52,11 +10,13 @@ module Gitlab def fetch(stage:) custom_query = "#{stage}_custom_query".to_sym - @query.execute(stage, EVENTS_CONFIG[stage]) do |base_query| - public_send(custom_query, base_query) if self.respond_to?(custom_query) + @query.execute(stage) do |base_query| + send(custom_query, base_query) if self.respond_to?(custom_query) end end + private + def issue_custom_query(base_query) base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])) end diff --git a/lib/gitlab/cycle_analytics/events_query.rb b/lib/gitlab/cycle_analytics/events_query.rb index 39687305bc3..f78272a4108 100644 --- a/lib/gitlab/cycle_analytics/events_query.rb +++ b/lib/gitlab/cycle_analytics/events_query.rb @@ -8,9 +8,9 @@ module Gitlab @from = from end - def execute(stage, config, &block) + def execute(stage, &block) @stage = stage - @config = config + @config = QueryConfig.get(stage) query = build_query(&block) ActiveRecord::Base.connection.execute(query.to_sql).to_a diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb new file mode 100644 index 00000000000..5689c070eb7 --- /dev/null +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -0,0 +1,89 @@ +module Gitlab + module CycleAnalytics + class QueryConfig + include MetricsFetcher + + def self.get(*args) + new(*args).get + end + + def initialize(stage) + @stage = stage + end + + def get + send(@stage).freeze if self.respond_to?(@stage) + end + + private + + def issue + { start_time_attrs: issue_table[:created_at], + end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at], + issue_metrics_table[:first_added_to_board_at]], + projections: [issue_table[:title], + issue_table[:iid], + issue_table[:created_at], + user_table[:name], + user_table[:email]] + } + end + + def plan + { start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], + end_time_attrs: [issue_metrics_table[:first_added_to_board_at], + issue_metrics_table[:first_mentioned_in_commit_at]], + projections: [mr_diff_table[:st_commits].as('commits'), + issue_metrics_table[:first_mentioned_in_commit_at]] + } + end + + def code + { start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at], + end_time_attrs: mr_table[:created_at], + projections: [mr_table[:title], mr_table[:iid], + mr_table[:created_at], + user_table[:name], + user_table[:email]], + order: mr_table[:created_at] + } + end + + def test + { start_time_attrs: mr_metrics_table[:latest_build_started_at], + end_time_attrs: mr_metrics_table[:latest_build_finished_at], + projections: mr_metrics_table[:ci_commit_id], + order: mr_table[:created_at] + } + end + + def review + { start_time_attrs: mr_table[:created_at], + end_time_attrs: mr_metrics_table[:merged_at], + projections: [mr_table[:title], mr_table[:iid], + mr_table[:created_at], + user_table[:name], + user_table[:email]] + } + end + + def staging + { start_time_attrs: mr_metrics_table[:merged_at], + end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], + projections: mr_metrics_table[:ci_commit_id] + } + end + + def production + { start_time_attrs: issue_table[:created_at], + end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], + projections: [issue_table[:title], + issue_table[:iid], + issue_table[:created_at], + user_table[:name], + user_table[:email]] + } + end + end + end +end -- cgit v1.2.1 From 85c448d76f382538898344b07b2c635c7215ca7b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 28 Oct 2016 16:16:37 +0100 Subject: fix specs --- lib/gitlab/cycle_analytics/events_fetcher.rb | 4 +--- lib/gitlab/cycle_analytics/query_config.rb | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 0248ebc7193..db613a2d5c0 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -11,12 +11,10 @@ module Gitlab custom_query = "#{stage}_custom_query".to_sym @query.execute(stage) do |base_query| - send(custom_query, base_query) if self.respond_to?(custom_query) + public_send(custom_query, base_query) if self.respond_to?(custom_query) end end - private - def issue_custom_query(base_query) base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])) end diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index 5689c070eb7..c096e632724 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -12,11 +12,9 @@ module Gitlab end def get - send(@stage).freeze if self.respond_to?(@stage) + public_send(@stage).freeze if self.respond_to?(@stage) end - private - def issue { start_time_attrs: issue_table[:created_at], end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at], -- cgit v1.2.1 From 9ead268cfaee7cb23fa00c7cf8bcf536cafab91a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 2 Nov 2016 16:21:49 +0100 Subject: some more integration scenarios testing order. Also, fixed bug in the staging and test stages. --- lib/gitlab/cycle_analytics/events.rb | 2 -- lib/gitlab/cycle_analytics/query_config.rb | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 08070c2bac2..0ec5aeb03c7 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -9,8 +9,6 @@ module Gitlab @fetcher = EventsFetcher.new(project: project, from: from) end - # TODO: backend pagination - specially for commits, etc... - def issue_events @fetcher.fetch(stage: :issue).each { |event| parse_event(event) } end diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index c096e632724..3852ed7a2ab 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -50,7 +50,7 @@ module Gitlab def test { start_time_attrs: mr_metrics_table[:latest_build_started_at], end_time_attrs: mr_metrics_table[:latest_build_finished_at], - projections: mr_metrics_table[:ci_commit_id], + projections: [mr_metrics_table[:ci_commit_id]], order: mr_table[:created_at] } end @@ -68,7 +68,7 @@ module Gitlab def staging { start_time_attrs: mr_metrics_table[:merged_at], end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], - projections: mr_metrics_table[:ci_commit_id] + projections: [mr_metrics_table[:ci_commit_id]] } end -- cgit v1.2.1 From 8f7266cd4b6a3e65224b55c2b91509f5ac88d837 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 3 Nov 2016 13:06:14 +0100 Subject: added missing fields to issue. Also, added a light url builder to add URLs easily from arel. Updated specs. --- lib/gitlab/cycle_analytics/events.rb | 5 ++++ lib/gitlab/cycle_analytics/query_config.rb | 7 +++-- lib/gitlab/light_url_builder.rb | 45 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 lib/gitlab/light_url_builder.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 0ec5aeb03c7..66b30250c0f 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -50,8 +50,13 @@ module Gitlab private def parse_event(event) + event['url'] = Gitlab::LightUrlBuilder.build(entity: :issue, project: @project, id: event['id']) event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['created_at'] = interval_in_words(event['created_at']) + event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: event['author_username']) + event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar_url, id: event['author_id']) + + event.except('author_id', 'author_username') end def first_time_reference_commit(commits, event) diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index 3852ed7a2ab..ae3dd43a81b 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -21,9 +21,12 @@ module Gitlab issue_metrics_table[:first_added_to_board_at]], projections: [issue_table[:title], issue_table[:iid], + issue_table[:id], issue_table[:created_at], - user_table[:name], - user_table[:email]] + issue_table[:state], + user_table[:name].as('author_name'), + user_table[:username].as('author_username'), + user_table[:id].as('author_id')] } end diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb new file mode 100644 index 00000000000..9b5d28e731f --- /dev/null +++ b/lib/gitlab/light_url_builder.rb @@ -0,0 +1,45 @@ +module Gitlab + class LightUrlBuilder + include Gitlab::Routing.url_helpers + include GitlabRoutingHelper + include ActionView::RecordIdentifier + + def self.build(*args) + new(*args).url + end + + def initialize(entity:, project: nil, id:, opts: {}) + @entity = entity + @project = project + @id = id + @opts = opts + end + + def url + case @entity + when :issue + issue_url + when :user + user_url(@id) + when :user_avatar_url + user_avatar_url + else + raise NotImplementedError.new("No URL builder defined for #{object.class}") + end + end + + private + + def issue_url + namespace_project_issue_url({ + namespace_id: @project.namespace, + project_id: @project, + id: @id + }.merge!(@opts)) + end + + def user_avatar_url + User.find(@id).avatar_url + end + end +end -- cgit v1.2.1 From 1b5b2eac222cc25bfe7301cdefb69a6635ef0682 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 3 Nov 2016 15:41:46 +0100 Subject: added missing fields to plan events and updated spec --- lib/gitlab/cycle_analytics/events.rb | 11 ++++++++--- lib/gitlab/cycle_analytics/query_config.rb | 3 +-- lib/gitlab/light_url_builder.rb | 20 +++++++++++++++----- 3 files changed, 24 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 66b30250c0f..88aff762b43 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -16,8 +16,13 @@ module Gitlab def plan_events @fetcher.fetch(stage: :plan).each do |event| event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) - commits = event.delete('commits') - event['commit'] = first_time_reference_commit(commits, event) + commit = first_time_reference_commit(event.delete('commits'), event) + event['title'] = commit.title + event['url'] = Gitlab::LightUrlBuilder.build(entity: :commit_url, project: @project, id: commit.id) + event['sha'] = commit.short_id + event['author_name'] = commit.author.name + event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: commit.author.username) + event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar_url, id: commit.author.id) end end @@ -56,7 +61,7 @@ module Gitlab event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: event['author_username']) event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar_url, id: event['author_id']) - event.except('author_id', 'author_username') + event.except!('author_id', 'author_username') end def first_time_reference_commit(commits, event) diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index ae3dd43a81b..a544dbbf556 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -34,8 +34,7 @@ module Gitlab { start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], end_time_attrs: [issue_metrics_table[:first_added_to_board_at], issue_metrics_table[:first_mentioned_in_commit_at]], - projections: [mr_diff_table[:st_commits].as('commits'), - issue_metrics_table[:first_mentioned_in_commit_at]] + projections: [mr_diff_table[:st_commits].as('commits')] } end diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb index 9b5d28e731f..3cbb53241f2 100644 --- a/lib/gitlab/light_url_builder.rb +++ b/lib/gitlab/light_url_builder.rb @@ -23,8 +23,10 @@ module Gitlab user_url(@id) when :user_avatar_url user_avatar_url + when :commit_url + commit_url else - raise NotImplementedError.new("No URL builder defined for #{object.class}") + raise NotImplementedError.new("No URL builder defined for #{object.class}") end end @@ -32,14 +34,22 @@ module Gitlab def issue_url namespace_project_issue_url({ - namespace_id: @project.namespace, - project_id: @project, - id: @id - }.merge!(@opts)) + namespace_id: @project.namespace, + project_id: @project, + id: @id + }.merge!(@opts)) end def user_avatar_url User.find(@id).avatar_url end + + def commit_url + namespace_project_commit_url({ + namespace_id: @project.namespace, + project_id: @project, + id: @id + }.merge!(@opts)) + end end end -- cgit v1.2.1 From 11bad33a4206e482ffac42d75a121182a52e0cb0 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 3 Nov 2016 16:31:30 +0100 Subject: added missing fields to code events and updated spec --- lib/gitlab/cycle_analytics/events.rb | 6 +++--- lib/gitlab/cycle_analytics/query_config.rb | 11 +++++++---- lib/gitlab/light_url_builder.rb | 10 ++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 88aff762b43..0819f0eba4b 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -27,7 +27,7 @@ module Gitlab end def code_events - @fetcher.fetch(stage: :code).each { |event| parse_event(event) } + @fetcher.fetch(stage: :code).each { |event| parse_event(event, entity: :merge_request) } end def test_events @@ -54,8 +54,8 @@ module Gitlab private - def parse_event(event) - event['url'] = Gitlab::LightUrlBuilder.build(entity: :issue, project: @project, id: event['id']) + def parse_event(event, entity: :issue) + event['url'] = Gitlab::LightUrlBuilder.build(entity: entity, project: @project, id: event['id']) event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['created_at'] = interval_in_words(event['created_at']) event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: event['author_username']) diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index a544dbbf556..b1ebb452659 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -23,7 +23,6 @@ module Gitlab issue_table[:iid], issue_table[:id], issue_table[:created_at], - issue_table[:state], user_table[:name].as('author_name'), user_table[:username].as('author_username'), user_table[:id].as('author_id')] @@ -41,10 +40,14 @@ module Gitlab def code { start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at], end_time_attrs: mr_table[:created_at], - projections: [mr_table[:title], mr_table[:iid], + projections: [mr_table[:title], + mr_table[:iid], + mr_table[:id], mr_table[:created_at], - user_table[:name], - user_table[:email]], + mr_table[:state], + user_table[:name].as('author_name'), + user_table[:username].as('author_username'), + user_table[:id].as('author_id')], order: mr_table[:created_at] } end diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb index 3cbb53241f2..7df14acfe85 100644 --- a/lib/gitlab/light_url_builder.rb +++ b/lib/gitlab/light_url_builder.rb @@ -25,6 +25,8 @@ module Gitlab user_avatar_url when :commit_url commit_url + when :merge_request + mr_url else raise NotImplementedError.new("No URL builder defined for #{object.class}") end @@ -51,5 +53,13 @@ module Gitlab id: @id }.merge!(@opts)) end + + def mr_url + namespace_project_merge_request_url({ + namespace_id: @project.namespace, + project_id: @project, + id: @id + }.merge!(@opts)) + end end end -- cgit v1.2.1 From eccb6a5e920920bda11104ca608e652f84a944bf Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 4 Nov 2016 11:56:24 +0100 Subject: Refactored test events Now test events return the builds instead a list of pipelines to avoid calling pipeline.builds per each and get the info. Also, added missing fields/data, URLs, and fixed specs in events spec. --- lib/gitlab/cycle_analytics/events.rb | 11 +++++++++-- lib/gitlab/cycle_analytics/events_fetcher.rb | 4 ++++ lib/gitlab/cycle_analytics/metrics_fetcher.rb | 4 ++++ lib/gitlab/cycle_analytics/query_config.rb | 2 +- lib/gitlab/light_url_builder.rb | 18 ++++++++++++++++++ 5 files changed, 36 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 0819f0eba4b..b39282b1e0f 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -32,8 +32,15 @@ module Gitlab def test_events @fetcher.fetch(stage: :test).each do |event| - event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) - event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline + build = ::Ci::Build.find(event['id']) + event['name'] = build.name + event['url'] = Gitlab::LightUrlBuilder.build(entity: :build_url, project: @project, id: build.id) + event['branch'] = build.ref + event['branch_url'] = Gitlab::LightUrlBuilder.build(entity: :branch_url, project: @project, id: build.ref) + event['sha'] = build.short_sha + event['commit_url'] = Gitlab::LightUrlBuilder.build(entity: :commit_url, project: @project, id: build.sha) + event['date'] = build.started_at + event['total_time'] = build.duration end end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index db613a2d5c0..069b5873736 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -29,6 +29,10 @@ module Gitlab def review_custom_query(base_query) base_query.join(user_table).on(mr_table[:author_id].eq(user_table[:id])) end + + def test_custom_query(base_query) + base_query.join(build_table).on(mr_metrics_table[:ci_commit_id].eq(build_table[:commit_id])) + end end end end diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index c1cbedc045b..11f923cc27d 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -79,6 +79,10 @@ module Gitlab def user_table User.arel_table end + + def build_table + ::CommitStatus.arel_table + end end end end diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index b1ebb452659..bd8ddf8a638 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -55,7 +55,7 @@ module Gitlab def test { start_time_attrs: mr_metrics_table[:latest_build_started_at], end_time_attrs: mr_metrics_table[:latest_build_finished_at], - projections: [mr_metrics_table[:ci_commit_id]], + projections: [build_table[:id]], order: mr_table[:created_at] } end diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb index 7df14acfe85..b33fad12eec 100644 --- a/lib/gitlab/light_url_builder.rb +++ b/lib/gitlab/light_url_builder.rb @@ -16,6 +16,7 @@ module Gitlab end def url + #TODO refactor this case @entity when :issue issue_url @@ -27,6 +28,10 @@ module Gitlab commit_url when :merge_request mr_url + when :build_url + namespace_project_build_url(@project.namespace, @project, @id) + when :branch_url + branch_url else raise NotImplementedError.new("No URL builder defined for #{object.class}") end @@ -61,5 +66,18 @@ module Gitlab id: @id }.merge!(@opts)) end + + + def pipeline_url + namespace_project_build_url({ + namespace_id: @project.namespace, + project_id: @project, + id: @id + }.merge!(@opts)) + end + + def branch_url + "#{project_url(@project)}/commits/#{@id}" + end end end -- cgit v1.2.1 From bd31f24c548878597322b34965789c88ff8d2dde Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 4 Nov 2016 12:57:23 +0100 Subject: Added branch option to test events Also fixed test events ordering issue --- lib/gitlab/cycle_analytics/events.rb | 5 ++--- lib/gitlab/cycle_analytics/events_fetcher.rb | 4 ++-- lib/gitlab/cycle_analytics/events_query.rb | 5 +++-- lib/gitlab/cycle_analytics/metrics_fetcher.rb | 2 ++ lib/gitlab/cycle_analytics/query_config.rb | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index b39282b1e0f..c86b242857e 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -3,10 +3,9 @@ module Gitlab class Events include ActionView::Helpers::DateHelper - def initialize(project:, from:) + def initialize(project:, options:) @project = project - @from = from - @fetcher = EventsFetcher.new(project: project, from: from) + @fetcher = EventsFetcher.new(project: project, options: options) end def issue_events diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 069b5873736..9b62a596aeb 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -3,8 +3,8 @@ module Gitlab class EventsFetcher include MetricsFetcher - def initialize(project:, from:) - @query = EventsQuery.new(project: project, from: from) + def initialize(project:, options:) + @query = EventsQuery.new(project: project, options: options) end def fetch(stage:) diff --git a/lib/gitlab/cycle_analytics/events_query.rb b/lib/gitlab/cycle_analytics/events_query.rb index f78272a4108..7a27e099be3 100644 --- a/lib/gitlab/cycle_analytics/events_query.rb +++ b/lib/gitlab/cycle_analytics/events_query.rb @@ -3,9 +3,10 @@ module Gitlab class EventsQuery include MetricsFetcher - def initialize(project:, from:) + def initialize(project:, options: {}) @project = project - @from = from + @from = options[:from] + @branch = options[:branch] end def execute(stage, &block) diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index 11f923cc27d..f6522905c10 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -38,6 +38,8 @@ module Gitlab where(issue_table[:deleted_at].eq(nil)). where(issue_table[:created_at].gteq(@from)) + query = query.where(build_table[:ref].eq(@branch)) if name == :test && @branch + # Load merge_requests query = query.join(mr_table, Arel::Nodes::OuterJoin). on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id])). diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index bd8ddf8a638..55db37ec796 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -56,7 +56,7 @@ module Gitlab { start_time_attrs: mr_metrics_table[:latest_build_started_at], end_time_attrs: mr_metrics_table[:latest_build_finished_at], projections: [build_table[:id]], - order: mr_table[:created_at] + order: build_table[:created_at] } end -- cgit v1.2.1 From beeb64610e6a1907d6675b535659a519db129453 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 4 Nov 2016 15:59:27 +0100 Subject: Refactored staging events, added missing fields and fixed specs --- lib/gitlab/cycle_analytics/events.rb | 26 +++++++++++++++----------- lib/gitlab/cycle_analytics/events_fetcher.rb | 7 ++++--- lib/gitlab/cycle_analytics/query_config.rb | 15 ++++++++++----- 3 files changed, 29 insertions(+), 19 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index c86b242857e..1a78bea3e18 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -31,15 +31,7 @@ module Gitlab def test_events @fetcher.fetch(stage: :test).each do |event| - build = ::Ci::Build.find(event['id']) - event['name'] = build.name - event['url'] = Gitlab::LightUrlBuilder.build(entity: :build_url, project: @project, id: build.id) - event['branch'] = build.ref - event['branch_url'] = Gitlab::LightUrlBuilder.build(entity: :branch_url, project: @project, id: build.ref) - event['sha'] = build.short_sha - event['commit_url'] = Gitlab::LightUrlBuilder.build(entity: :commit_url, project: @project, id: build.sha) - event['date'] = build.started_at - event['total_time'] = build.duration + parse_build_event(event) end end @@ -49,8 +41,7 @@ module Gitlab def staging_events @fetcher.fetch(stage: :staging).each do |event| - event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) - event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline + parse_build_event(event) end end @@ -70,6 +61,19 @@ module Gitlab event.except!('author_id', 'author_username') end + def parse_build_event(event) + build = ::Ci::Build.find(event['id']) + event['name'] = build.name + event['url'] = Gitlab::LightUrlBuilder.build(entity: :build_url, project: @project, id: build.id) + event['branch'] = build.ref + event['branch_url'] = Gitlab::LightUrlBuilder.build(entity: :branch_url, project: @project, id: build.ref) + event['sha'] = build.short_sha + event['commit_url'] = Gitlab::LightUrlBuilder.build(entity: :commit_url, project: @project, id: build.sha) + event['date'] = build.started_at + event['total_time'] = build.duration + event['author_name'] = build.author.try(:name) + end + def first_time_reference_commit(commits, event) st_commit = YAML.load(commits).detect do |commit| commit['created_at'] == event['first_mentioned_in_commit_at'] diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 9b62a596aeb..5631867d451 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -19,9 +19,6 @@ module Gitlab base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])) end - alias_method :code_custom_query, :issue_custom_query - alias_method :production_custom_query, :issue_custom_query - def plan_custom_query(base_query) base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) end @@ -33,6 +30,10 @@ module Gitlab def test_custom_query(base_query) base_query.join(build_table).on(mr_metrics_table[:ci_commit_id].eq(build_table[:commit_id])) end + + alias_method :code_custom_query, :issue_custom_query + alias_method :production_custom_query, :issue_custom_query + alias_method :staging_custom_query, :test_custom_query end end end diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index 55db37ec796..68692f3a4e6 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -63,17 +63,22 @@ module Gitlab def review { start_time_attrs: mr_table[:created_at], end_time_attrs: mr_metrics_table[:merged_at], - projections: [mr_table[:title], mr_table[:iid], - mr_table[:created_at], - user_table[:name], - user_table[:email]] + projections: [mr_table[:title], + mr_table[:iid], + mr_table[:id], + mr_table[:created_at].as('opened_at'), + mr_table[:state], + user_table[:name].as('author_name'), + user_table[:username].as('author_username'), + user_table[:id].as('author_id')] } end def staging { start_time_attrs: mr_metrics_table[:merged_at], end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], - projections: [mr_metrics_table[:ci_commit_id]] + projections: [build_table[:id]], + order: build_table[:created_at] } end -- cgit v1.2.1 From 83130ae877ffa946ef270743905f8fe1ac123305 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 4 Nov 2016 16:16:15 +0100 Subject: Updated production events with new fields --- lib/gitlab/cycle_analytics/events.rb | 2 +- lib/gitlab/cycle_analytics/query_config.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 1a78bea3e18..7d06c5e890f 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -17,7 +17,7 @@ module Gitlab event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) commit = first_time_reference_commit(event.delete('commits'), event) event['title'] = commit.title - event['url'] = Gitlab::LightUrlBuilder.build(entity: :commit_url, project: @project, id: commit.id) + event['url'] = Gitlab::LightUrlBuilder.build(entity: :commit_url, project: @project, id: commit.id) event['sha'] = commit.short_id event['author_name'] = commit.author.name event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: commit.author.username) diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index 68692f3a4e6..9c8d31c1a97 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -87,9 +87,11 @@ module Gitlab end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], projections: [issue_table[:title], issue_table[:iid], + issue_table[:id], issue_table[:created_at], - user_table[:name], - user_table[:email]] + user_table[:name].as('author_name'), + user_table[:username].as('author_username'), + user_table[:id].as('author_id')] } end end -- cgit v1.2.1 From b4bb33c5b7f30dedbb56f4b9f7dbc9fd58d4bdb9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 4 Nov 2016 17:24:54 +0100 Subject: fix rubocop warning --- lib/gitlab/light_url_builder.rb | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb index b33fad12eec..0b589217273 100644 --- a/lib/gitlab/light_url_builder.rb +++ b/lib/gitlab/light_url_builder.rb @@ -16,24 +16,24 @@ module Gitlab end def url - #TODO refactor this + # TODO refactor this case @entity - when :issue - issue_url - when :user - user_url(@id) - when :user_avatar_url - user_avatar_url - when :commit_url - commit_url - when :merge_request - mr_url - when :build_url - namespace_project_build_url(@project.namespace, @project, @id) - when :branch_url - branch_url - else - raise NotImplementedError.new("No URL builder defined for #{object.class}") + when :issue + issue_url + when :user + user_url(@id) + when :user_avatar_url + user_avatar_url + when :commit_url + commit_url + when :merge_request + mr_url + when :build_url + namespace_project_build_url(@project.namespace, @project, @id) + when :branch_url + branch_url + else + raise NotImplementedError.new("No URL builder defined for #{object.class}") end end @@ -67,7 +67,6 @@ module Gitlab }.merge!(@opts)) end - def pipeline_url namespace_project_build_url({ namespace_id: @project.namespace, -- cgit v1.2.1 From 7ac7cfeb75242c4e3c5ec967425aaf598f879033 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 7 Nov 2016 15:43:55 +0100 Subject: refactored and added missing spec to light URL builder --- lib/gitlab/cycle_analytics/events.rb | 8 ++++---- lib/gitlab/cycle_analytics/metrics_fetcher.rb | 4 ---- lib/gitlab/light_url_builder.rb | 16 ++++------------ 3 files changed, 8 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 7d06c5e890f..68a2de79409 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -52,7 +52,7 @@ module Gitlab private def parse_event(event, entity: :issue) - event['url'] = Gitlab::LightUrlBuilder.build(entity: entity, project: @project, id: event['id']) + event['url'] = Gitlab::LightUrlBuilder.build(entity: entity, project: @project, id: event['iid']) event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['created_at'] = interval_in_words(event['created_at']) event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: event['author_username']) @@ -64,11 +64,11 @@ module Gitlab def parse_build_event(event) build = ::Ci::Build.find(event['id']) event['name'] = build.name - event['url'] = Gitlab::LightUrlBuilder.build(entity: :build_url, project: @project, id: build.id) + event['url'] = Gitlab::LightUrlBuilder.build(entity: :build, project: @project, id: build.id) event['branch'] = build.ref - event['branch_url'] = Gitlab::LightUrlBuilder.build(entity: :branch_url, project: @project, id: build.ref) + event['branch'] = Gitlab::LightUrlBuilder.build(entity: :branch, project: @project, id: build.ref) event['sha'] = build.short_sha - event['commit_url'] = Gitlab::LightUrlBuilder.build(entity: :commit_url, project: @project, id: build.sha) + event['commit'] = Gitlab::LightUrlBuilder.build(entity: :commit, project: @project, id: build.sha) event['date'] = build.started_at event['total_time'] = build.duration event['author_name'] = build.author.try(:name) diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index f6522905c10..6743cbcc9e0 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -6,10 +6,6 @@ module Gitlab DEPLOYMENT_METRIC_STAGES = %i[production staging] - def self.included(klass) - klass.extend self - end - private def calculate_metric(name, start_time_attrs, end_time_attrs) diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb index 0b589217273..7875491a1d8 100644 --- a/lib/gitlab/light_url_builder.rb +++ b/lib/gitlab/light_url_builder.rb @@ -22,15 +22,15 @@ module Gitlab issue_url when :user user_url(@id) - when :user_avatar_url + when :user_avatar user_avatar_url - when :commit_url + when :commit commit_url when :merge_request mr_url - when :build_url + when :build namespace_project_build_url(@project.namespace, @project, @id) - when :branch_url + when :branch branch_url else raise NotImplementedError.new("No URL builder defined for #{object.class}") @@ -67,14 +67,6 @@ module Gitlab }.merge!(@opts)) end - def pipeline_url - namespace_project_build_url({ - namespace_id: @project.namespace, - project_id: @project, - id: @id - }.merge!(@opts)) - end - def branch_url "#{project_url(@project)}/commits/#{@id}" end -- cgit v1.2.1 From c6532ee3706340bcbe1d8ee25a593b22f9aee811 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 7 Nov 2016 16:54:40 +0100 Subject: refactored light url builder --- lib/gitlab/cycle_analytics/query_config.rb | 21 +++++++++++------ lib/gitlab/light_url_builder.rb | 37 ++++++++++++------------------ 2 files changed, 29 insertions(+), 29 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index 9c8d31c1a97..0393567378f 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -16,7 +16,8 @@ module Gitlab end def issue - { start_time_attrs: issue_table[:created_at], + { + start_time_attrs: issue_table[:created_at], end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at], issue_metrics_table[:first_added_to_board_at]], projections: [issue_table[:title], @@ -30,7 +31,8 @@ module Gitlab end def plan - { start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], + { + start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], end_time_attrs: [issue_metrics_table[:first_added_to_board_at], issue_metrics_table[:first_mentioned_in_commit_at]], projections: [mr_diff_table[:st_commits].as('commits')] @@ -38,7 +40,8 @@ module Gitlab end def code - { start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at], + { + start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at], end_time_attrs: mr_table[:created_at], projections: [mr_table[:title], mr_table[:iid], @@ -53,7 +56,8 @@ module Gitlab end def test - { start_time_attrs: mr_metrics_table[:latest_build_started_at], + { + start_time_attrs: mr_metrics_table[:latest_build_started_at], end_time_attrs: mr_metrics_table[:latest_build_finished_at], projections: [build_table[:id]], order: build_table[:created_at] @@ -61,7 +65,8 @@ module Gitlab end def review - { start_time_attrs: mr_table[:created_at], + { + start_time_attrs: mr_table[:created_at], end_time_attrs: mr_metrics_table[:merged_at], projections: [mr_table[:title], mr_table[:iid], @@ -75,7 +80,8 @@ module Gitlab end def staging - { start_time_attrs: mr_metrics_table[:merged_at], + { + start_time_attrs: mr_metrics_table[:merged_at], end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], projections: [build_table[:id]], order: build_table[:created_at] @@ -83,7 +89,8 @@ module Gitlab end def production - { start_time_attrs: issue_table[:created_at], + { + start_time_attrs: issue_table[:created_at], end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], projections: [issue_table[:title], issue_table[:iid], diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb index 7875491a1d8..ceb8715ad2b 100644 --- a/lib/gitlab/light_url_builder.rb +++ b/lib/gitlab/light_url_builder.rb @@ -1,4 +1,6 @@ module Gitlab + # Similar to UrlBuilder, but using IDs to avoid querying the DB for objects + # Useful for using in conjunction with Arel queries. class LightUrlBuilder include Gitlab::Routing.url_helpers include GitlabRoutingHelper @@ -16,28 +18,11 @@ module Gitlab end def url - # TODO refactor this - case @entity - when :issue - issue_url - when :user - user_url(@id) - when :user_avatar - user_avatar_url - when :commit - commit_url - when :merge_request - mr_url - when :build - namespace_project_build_url(@project.namespace, @project, @id) - when :branch - branch_url - else - raise NotImplementedError.new("No URL builder defined for #{object.class}") - end - end + url_method = "#{@entity}_url" + raise NotImplementedError.new("No Light URL builder defined for #{@entity.to_s}") unless respond_to?(url_method) - private + public_send(url_method) + end def issue_url namespace_project_issue_url({ @@ -59,7 +44,7 @@ module Gitlab }.merge!(@opts)) end - def mr_url + def merge_request_url namespace_project_merge_request_url({ namespace_id: @project.namespace, project_id: @project, @@ -70,5 +55,13 @@ module Gitlab def branch_url "#{project_url(@project)}/commits/#{@id}" end + + def user_url + Gitlab::Routing.url_helpers.user_url(@id) + end + + def build_url + namespace_project_build_url(@project.namespace, @project, @id) + end end end -- cgit v1.2.1 From 2e5c1c27ebdf0ed57f3848f312e147dca6fc38c8 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 9 Nov 2016 10:52:57 +0100 Subject: fix events presenter after refactoring --- lib/gitlab/cycle_analytics/events.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 68a2de79409..47b42621f2d 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -17,11 +17,11 @@ module Gitlab event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) commit = first_time_reference_commit(event.delete('commits'), event) event['title'] = commit.title - event['url'] = Gitlab::LightUrlBuilder.build(entity: :commit_url, project: @project, id: commit.id) + event['url'] = Gitlab::LightUrlBuilder.build(entity: :commit, project: @project, id: commit.id) event['sha'] = commit.short_id event['author_name'] = commit.author.name event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: commit.author.username) - event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar_url, id: commit.author.id) + event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar, id: commit.author.id) end end @@ -56,7 +56,7 @@ module Gitlab event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['created_at'] = interval_in_words(event['created_at']) event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: event['author_username']) - event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar_url, id: event['author_id']) + event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar, id: event['author_id']) event.except!('author_id', 'author_username') end @@ -66,9 +66,9 @@ module Gitlab event['name'] = build.name event['url'] = Gitlab::LightUrlBuilder.build(entity: :build, project: @project, id: build.id) event['branch'] = build.ref - event['branch'] = Gitlab::LightUrlBuilder.build(entity: :branch, project: @project, id: build.ref) + event['branch_url'] = Gitlab::LightUrlBuilder.build(entity: :branch, project: @project, id: build.ref) event['sha'] = build.short_sha - event['commit'] = Gitlab::LightUrlBuilder.build(entity: :commit, project: @project, id: build.sha) + event['commit_url'] = Gitlab::LightUrlBuilder.build(entity: :commit, project: @project, id: build.sha) event['date'] = build.started_at event['total_time'] = build.duration event['author_name'] = build.author.try(:name) -- cgit v1.2.1 From 848ea241db74ffc160e61c23775e336c207e65af Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 9 Nov 2016 16:03:47 +0100 Subject: fix MySQL problem with query --- lib/gitlab/cycle_analytics/events_query.rb | 2 +- lib/gitlab/light_url_builder.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_query.rb b/lib/gitlab/cycle_analytics/events_query.rb index 7a27e099be3..9d69e267d8d 100644 --- a/lib/gitlab/cycle_analytics/events_query.rb +++ b/lib/gitlab/cycle_analytics/events_query.rb @@ -14,7 +14,7 @@ module Gitlab @config = QueryConfig.get(stage) query = build_query(&block) - ActiveRecord::Base.connection.execute(query.to_sql).to_a + ActiveRecord::Base.connection.exec_query(query.to_sql) end private diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb index ceb8715ad2b..e4517b5610b 100644 --- a/lib/gitlab/light_url_builder.rb +++ b/lib/gitlab/light_url_builder.rb @@ -19,7 +19,7 @@ module Gitlab def url url_method = "#{@entity}_url" - raise NotImplementedError.new("No Light URL builder defined for #{@entity.to_s}") unless respond_to?(url_method) + raise NotImplementedError.new("No Light URL builder defined for #{@entity}") unless respond_to?(url_method) public_send(url_method) end -- cgit v1.2.1 From 4dfcab6ec3d4b3dae1e68374c1252d139c161534 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 9 Nov 2016 17:19:29 +0100 Subject: fix iid issue making spec to fail - MySQL only --- lib/gitlab/cycle_analytics/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 47b42621f2d..e4c9752f3f9 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -52,7 +52,7 @@ module Gitlab private def parse_event(event, entity: :issue) - event['url'] = Gitlab::LightUrlBuilder.build(entity: entity, project: @project, id: event['iid']) + event['url'] = Gitlab::LightUrlBuilder.build(entity: entity, project: @project, id: event['iid'].to_s) event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['created_at'] = interval_in_words(event['created_at']) event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: event['author_username']) -- cgit v1.2.1 From 81d0146c4cf0d34b1b81da770483ed864482149c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 14 Nov 2016 11:52:29 +0100 Subject: WIP - refactoring URL builder and events presenter into serializers --- lib/gitlab/cycle_analytics/events.rb | 11 ++--------- lib/gitlab/light_url_builder.rb | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index e4c9752f3f9..21e295ceeb6 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -63,15 +63,8 @@ module Gitlab def parse_build_event(event) build = ::Ci::Build.find(event['id']) - event['name'] = build.name - event['url'] = Gitlab::LightUrlBuilder.build(entity: :build, project: @project, id: build.id) - event['branch'] = build.ref - event['branch_url'] = Gitlab::LightUrlBuilder.build(entity: :branch, project: @project, id: build.ref) - event['sha'] = build.short_sha - event['commit_url'] = Gitlab::LightUrlBuilder.build(entity: :commit, project: @project, id: build.sha) - event['date'] = build.started_at - event['total_time'] = build.duration - event['author_name'] = build.author.try(:name) + + #event['author_name'] = build.author.try(:name) end def first_time_reference_commit(commits, event) diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb index e4517b5610b..0c86b122b88 100644 --- a/lib/gitlab/light_url_builder.rb +++ b/lib/gitlab/light_url_builder.rb @@ -53,7 +53,7 @@ module Gitlab end def branch_url - "#{project_url(@project)}/commits/#{@id}" + namespace_project_commit_url(@project.namespace, @project, @id) end def user_url -- cgit v1.2.1 From 3b179bc37b940521522af6d8bf6762c2a9a0e251 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 14 Nov 2016 14:53:06 +0100 Subject: WIP - refactored events to use build serializer, related spec passing --- lib/gitlab/cycle_analytics/events.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 21e295ceeb6..f2fa54f208f 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -30,7 +30,7 @@ module Gitlab end def test_events - @fetcher.fetch(stage: :test).each do |event| + @fetcher.fetch(stage: :test).map do |event| parse_build_event(event) end end @@ -40,7 +40,7 @@ module Gitlab end def staging_events - @fetcher.fetch(stage: :staging).each do |event| + @fetcher.fetch(stage: :staging).map do |event| parse_build_event(event) end end @@ -64,7 +64,7 @@ module Gitlab def parse_build_event(event) build = ::Ci::Build.find(event['id']) - #event['author_name'] = build.author.try(:name) + AnalyticsBuildSerializer.new(project: @project).represent(build).as_json end def first_time_reference_commit(commits, event) -- cgit v1.2.1 From 6a2737e6a82875311f71f451939b2732562533d4 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 15 Nov 2016 09:39:45 +0100 Subject: WIP - refactor events to use a generic build entity for some of the hybrid events --- lib/gitlab/cycle_analytics/events.rb | 14 ++++---------- lib/gitlab/cycle_analytics/events_fetcher.rb | 10 ---------- lib/gitlab/cycle_analytics/query_config.rb | 16 ++++------------ 3 files changed, 8 insertions(+), 32 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index f2fa54f208f..8be102c9fa0 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -1,8 +1,6 @@ module Gitlab module CycleAnalytics class Events - include ActionView::Helpers::DateHelper - def initialize(project:, options:) @project = project @fetcher = EventsFetcher.new(project: project, options: options) @@ -51,20 +49,16 @@ module Gitlab private - def parse_event(event, entity: :issue) - event['url'] = Gitlab::LightUrlBuilder.build(entity: entity, project: @project, id: event['iid'].to_s) - event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) - event['created_at'] = interval_in_words(event['created_at']) - event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: event['author_username']) - event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar, id: event['author_id']) + def parse_event(event) + event['author'] = User.find(event.remove('author_id')) - event.except!('author_id', 'author_username') + AnalyticsGenericSerializer.new(project: @project).represent(event).as_json end def parse_build_event(event) build = ::Ci::Build.find(event['id']) - AnalyticsBuildSerializer.new(project: @project).represent(build).as_json + AnalyticsBuildSerializer.new.represent(build).as_json end def first_time_reference_commit(commits, event) diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 5631867d451..c12fdd58a47 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -15,24 +15,14 @@ module Gitlab end end - def issue_custom_query(base_query) - base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])) - end - def plan_custom_query(base_query) base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) end - def review_custom_query(base_query) - base_query.join(user_table).on(mr_table[:author_id].eq(user_table[:id])) - end - def test_custom_query(base_query) base_query.join(build_table).on(mr_metrics_table[:ci_commit_id].eq(build_table[:commit_id])) end - alias_method :code_custom_query, :issue_custom_query - alias_method :production_custom_query, :issue_custom_query alias_method :staging_custom_query, :test_custom_query end end diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index 0393567378f..d077a3e1a14 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -24,9 +24,7 @@ module Gitlab issue_table[:iid], issue_table[:id], issue_table[:created_at], - user_table[:name].as('author_name'), - user_table[:username].as('author_username'), - user_table[:id].as('author_id')] + issue_table[:author_id]] } end @@ -48,9 +46,7 @@ module Gitlab mr_table[:id], mr_table[:created_at], mr_table[:state], - user_table[:name].as('author_name'), - user_table[:username].as('author_username'), - user_table[:id].as('author_id')], + mr_table[:author_id]], order: mr_table[:created_at] } end @@ -73,9 +69,7 @@ module Gitlab mr_table[:id], mr_table[:created_at].as('opened_at'), mr_table[:state], - user_table[:name].as('author_name'), - user_table[:username].as('author_username'), - user_table[:id].as('author_id')] + mr_table[:author_id]] } end @@ -96,9 +90,7 @@ module Gitlab issue_table[:iid], issue_table[:id], issue_table[:created_at], - user_table[:name].as('author_name'), - user_table[:username].as('author_username'), - user_table[:id].as('author_id')] + issue_table[:author_id]] } end end -- cgit v1.2.1 From 747e5c3b6fb6e744bc1fce04f94930fdbb5f9121 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 15 Nov 2016 09:59:35 +0100 Subject: use request to pass extra parameters --- lib/gitlab/cycle_analytics/events.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 8be102c9fa0..077141110d0 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -49,10 +49,10 @@ module Gitlab private - def parse_event(event) + def parse_event(event, entity: :issue) event['author'] = User.find(event.remove('author_id')) - AnalyticsGenericSerializer.new(project: @project).represent(event).as_json + AnalyticsGenericSerializer.new(project: @project, entity: entity).represent(event).as_json end def parse_build_event(event) -- cgit v1.2.1 From f5600997512f1068cdc36ad7d086e7447dbc6d9d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 15 Nov 2016 10:33:01 +0100 Subject: fix issue events and related spec - now using generic serializer for a hash --- lib/gitlab/cycle_analytics/events.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 077141110d0..3b397bab178 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -7,7 +7,7 @@ module Gitlab end def issue_events - @fetcher.fetch(stage: :issue).each { |event| parse_event(event) } + @fetcher.fetch(stage: :issue).map { |event| parse_event(event) } end def plan_events @@ -24,7 +24,7 @@ module Gitlab end def code_events - @fetcher.fetch(stage: :code).each { |event| parse_event(event, entity: :merge_request) } + @fetcher.fetch(stage: :code).map { |event| parse_event(event, entity: :merge_request) } end def test_events @@ -34,7 +34,7 @@ module Gitlab end def review_events - @fetcher.fetch(stage: :review).each { |event| parse_event(event) } + @fetcher.fetch(stage: :review).map { |event| parse_event(event) } end def staging_events @@ -50,7 +50,7 @@ module Gitlab private def parse_event(event, entity: :issue) - event['author'] = User.find(event.remove('author_id')) + event['author'] = User.find(event.delete('author_id')) AnalyticsGenericSerializer.new(project: @project, entity: entity).represent(event).as_json end -- cgit v1.2.1 From 8743e59f78cd8f8701460796dcc06854281f3f73 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 15 Nov 2016 11:36:39 +0100 Subject: get all stages to use serlalizers - apart from plan - WIP --- lib/gitlab/cycle_analytics/events.rb | 4 ++-- lib/gitlab/cycle_analytics/query_config.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 3b397bab178..69688a2a0ca 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -34,7 +34,7 @@ module Gitlab end def review_events - @fetcher.fetch(stage: :review).map { |event| parse_event(event) } + @fetcher.fetch(stage: :review).map { |event| parse_event(event, entity: :merge_request) } end def staging_events @@ -44,7 +44,7 @@ module Gitlab end def production_events - @fetcher.fetch(stage: :production).each { |event| parse_event(event) } + @fetcher.fetch(stage: :production).map { |event| parse_event(event) } end private diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index d077a3e1a14..a18c263ba53 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -67,7 +67,7 @@ module Gitlab projections: [mr_table[:title], mr_table[:iid], mr_table[:id], - mr_table[:created_at].as('opened_at'), + mr_table[:created_at], mr_table[:state], mr_table[:author_id]] } -- cgit v1.2.1 From 73e9ec631995d5ed91a3eb5e8416c924e7d7c5ad Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 15 Nov 2016 13:23:17 +0100 Subject: serialize all the things! --- lib/gitlab/cycle_analytics/events.rb | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 69688a2a0ca..d3c7d6fe1aa 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -7,55 +7,46 @@ module Gitlab end def issue_events - @fetcher.fetch(stage: :issue).map { |event| parse_event(event) } + @fetcher.fetch(stage: :issue).map { |event| serialize_event(event) } end def plan_events - @fetcher.fetch(stage: :plan).each do |event| - event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) + @fetcher.fetch(stage: :plan).map do |event| commit = first_time_reference_commit(event.delete('commits'), event) - event['title'] = commit.title - event['url'] = Gitlab::LightUrlBuilder.build(entity: :commit, project: @project, id: commit.id) - event['sha'] = commit.short_id - event['author_name'] = commit.author.name - event['author_profile_url'] = Gitlab::LightUrlBuilder.build(entity: :user, id: commit.author.username) - event['author_avatar_url'] = Gitlab::LightUrlBuilder.build(entity: :user_avatar, id: commit.author.id) + + AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json end end def code_events - @fetcher.fetch(stage: :code).map { |event| parse_event(event, entity: :merge_request) } + @fetcher.fetch(stage: :code).map { |event| serialize_event(event, entity: :merge_request) } end def test_events - @fetcher.fetch(stage: :test).map do |event| - parse_build_event(event) - end + @fetcher.fetch(stage: :test).map { |event| serialize_build_event(event) } end def review_events - @fetcher.fetch(stage: :review).map { |event| parse_event(event, entity: :merge_request) } + @fetcher.fetch(stage: :review).map { |event| serialize_event(event, entity: :merge_request) } end def staging_events - @fetcher.fetch(stage: :staging).map do |event| - parse_build_event(event) - end + @fetcher.fetch(stage: :staging).map { |event| serialize_build_event(event) } end def production_events - @fetcher.fetch(stage: :production).map { |event| parse_event(event) } + @fetcher.fetch(stage: :production).map { |event| serialize_event(event) } end private - def parse_event(event, entity: :issue) + def serialize_event(event, entity: :issue) event['author'] = User.find(event.delete('author_id')) AnalyticsGenericSerializer.new(project: @project, entity: entity).represent(event).as_json end - def parse_build_event(event) + def serialize_build_event(event) build = ::Ci::Build.find(event['id']) AnalyticsBuildSerializer.new.represent(build).as_json -- cgit v1.2.1 From ca6da6ea3034a58c0edbc62336d2d49ea6e11fc9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 15 Nov 2016 15:22:35 +0100 Subject: Renamed pipeline column and fixed permissions for builds in events controller --- lib/gitlab/cycle_analytics/events_fetcher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index c12fdd58a47..d19ce6e0f6f 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -20,7 +20,7 @@ module Gitlab end def test_custom_query(base_query) - base_query.join(build_table).on(mr_metrics_table[:ci_commit_id].eq(build_table[:commit_id])) + base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) end alias_method :staging_custom_query, :test_custom_query -- cgit v1.2.1 From f93607a305346607f4296c266d40be1692febbec Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 15 Nov 2016 15:27:50 +0100 Subject: get rid of light url builder and fix wrong spec --- lib/gitlab/light_url_builder.rb | 67 ----------------------------------------- 1 file changed, 67 deletions(-) delete mode 100644 lib/gitlab/light_url_builder.rb (limited to 'lib') diff --git a/lib/gitlab/light_url_builder.rb b/lib/gitlab/light_url_builder.rb deleted file mode 100644 index 0c86b122b88..00000000000 --- a/lib/gitlab/light_url_builder.rb +++ /dev/null @@ -1,67 +0,0 @@ -module Gitlab - # Similar to UrlBuilder, but using IDs to avoid querying the DB for objects - # Useful for using in conjunction with Arel queries. - class LightUrlBuilder - include Gitlab::Routing.url_helpers - include GitlabRoutingHelper - include ActionView::RecordIdentifier - - def self.build(*args) - new(*args).url - end - - def initialize(entity:, project: nil, id:, opts: {}) - @entity = entity - @project = project - @id = id - @opts = opts - end - - def url - url_method = "#{@entity}_url" - raise NotImplementedError.new("No Light URL builder defined for #{@entity}") unless respond_to?(url_method) - - public_send(url_method) - end - - def issue_url - namespace_project_issue_url({ - namespace_id: @project.namespace, - project_id: @project, - id: @id - }.merge!(@opts)) - end - - def user_avatar_url - User.find(@id).avatar_url - end - - def commit_url - namespace_project_commit_url({ - namespace_id: @project.namespace, - project_id: @project, - id: @id - }.merge!(@opts)) - end - - def merge_request_url - namespace_project_merge_request_url({ - namespace_id: @project.namespace, - project_id: @project, - id: @id - }.merge!(@opts)) - end - - def branch_url - namespace_project_commit_url(@project.namespace, @project, @id) - end - - def user_url - Gitlab::Routing.url_helpers.user_url(@id) - end - - def build_url - namespace_project_build_url(@project.namespace, @project, @id) - end - end -end -- cgit v1.2.1 From cbc9f0cd1aa9f379952b6e4d3ad6df9971f9092a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 16 Nov 2016 09:58:23 +0100 Subject: fix issue with commits and also updated routes --- lib/gitlab/cycle_analytics/events.rb | 18 +++++++++++++----- lib/gitlab/cycle_analytics/query_config.rb | 3 ++- 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index d3c7d6fe1aa..f4d6d2049ef 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -12,9 +12,11 @@ module Gitlab def plan_events @fetcher.fetch(stage: :plan).map do |event| - commit = first_time_reference_commit(event.delete('commits'), event) + st_commit = first_time_reference_commit(event.delete('commits'), event) - AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json + next unless st_commit + + serialize_commit(event, st_commit) end end @@ -53,11 +55,17 @@ module Gitlab end def first_time_reference_commit(commits, event) - st_commit = YAML.load(commits).detect do |commit| - commit['created_at'] == event['first_mentioned_in_commit_at'] + YAML.load(commits).find do |commit| + next unless commit[:committed_date] && event['first_mentioned_in_commit_at'] + + commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at']).to_i end + end + + def serialize_commit(event, st_commit) + commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project) - Commit.new(Gitlab::Git::Commit.new(st_commit), @project) + AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json end def interval_in_words(diff) diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb index a18c263ba53..4fddbad95f2 100644 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ b/lib/gitlab/cycle_analytics/query_config.rb @@ -33,7 +33,8 @@ module Gitlab start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], end_time_attrs: [issue_metrics_table[:first_added_to_board_at], issue_metrics_table[:first_mentioned_in_commit_at]], - projections: [mr_diff_table[:st_commits].as('commits')] + projections: [mr_diff_table[:st_commits].as('commits'), + issue_metrics_table[:first_mentioned_in_commit_at]] } end -- cgit v1.2.1 From cf2dcf043c0054785bc0258ab6393104499b8d70 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 16 Nov 2016 12:01:10 +0100 Subject: Refactor all query config stuff into separate classes and added specs --- lib/gitlab/cycle_analytics/base_config.rb | 17 +++++ lib/gitlab/cycle_analytics/code_config.rb | 18 +++++ lib/gitlab/cycle_analytics/events_fetcher.rb | 16 +--- lib/gitlab/cycle_analytics/events_query.rb | 15 ++-- lib/gitlab/cycle_analytics/issue_config.rb | 16 ++++ lib/gitlab/cycle_analytics/plan_config.rb | 17 +++++ lib/gitlab/cycle_analytics/production_config.rb | 15 ++++ lib/gitlab/cycle_analytics/query_config.rb | 99 ------------------------- lib/gitlab/cycle_analytics/review_config.rb | 16 ++++ lib/gitlab/cycle_analytics/staging_config.rb | 14 ++++ lib/gitlab/cycle_analytics/test_config.rb | 14 ++++ 11 files changed, 136 insertions(+), 121 deletions(-) create mode 100644 lib/gitlab/cycle_analytics/base_config.rb create mode 100644 lib/gitlab/cycle_analytics/code_config.rb create mode 100644 lib/gitlab/cycle_analytics/issue_config.rb create mode 100644 lib/gitlab/cycle_analytics/plan_config.rb create mode 100644 lib/gitlab/cycle_analytics/production_config.rb delete mode 100644 lib/gitlab/cycle_analytics/query_config.rb create mode 100644 lib/gitlab/cycle_analytics/review_config.rb create mode 100644 lib/gitlab/cycle_analytics/staging_config.rb create mode 100644 lib/gitlab/cycle_analytics/test_config.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/base_config.rb b/lib/gitlab/cycle_analytics/base_config.rb new file mode 100644 index 00000000000..06ae9a15a7a --- /dev/null +++ b/lib/gitlab/cycle_analytics/base_config.rb @@ -0,0 +1,17 @@ +module Gitlab + module CycleAnalytics + class BaseConfig + extend MetricsFetcher + + class << self + attr_reader :start_time_attrs, :end_time_attrs, :projections + end + + def self.order + @order || @start_time_attrs + end + + def self.query(base_query); end + end + end +end diff --git a/lib/gitlab/cycle_analytics/code_config.rb b/lib/gitlab/cycle_analytics/code_config.rb new file mode 100644 index 00000000000..c27419310db --- /dev/null +++ b/lib/gitlab/cycle_analytics/code_config.rb @@ -0,0 +1,18 @@ +module Gitlab + module CycleAnalytics + class CodeConfig < BaseConfig + @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at] + + @end_time_attrs = mr_table[:created_at] + + @projections = [mr_table[:title], + mr_table[:iid], + mr_table[:id], + mr_table[:created_at], + mr_table[:state], + mr_table[:author_id]] + + @order = mr_table[:created_at] + end + end +end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index d19ce6e0f6f..714afb88833 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -8,22 +8,10 @@ module Gitlab end def fetch(stage:) - custom_query = "#{stage}_custom_query".to_sym - - @query.execute(stage) do |base_query| - public_send(custom_query, base_query) if self.respond_to?(custom_query) + @query.execute(stage) do |stage_class, base_query| + stage_class.query(base_query) end end - - def plan_custom_query(base_query) - base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) - end - - def test_custom_query(base_query) - base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) - end - - alias_method :staging_custom_query, :test_custom_query end end end diff --git a/lib/gitlab/cycle_analytics/events_query.rb b/lib/gitlab/cycle_analytics/events_query.rb index 9d69e267d8d..b61320fc561 100644 --- a/lib/gitlab/cycle_analytics/events_query.rb +++ b/lib/gitlab/cycle_analytics/events_query.rb @@ -11,7 +11,6 @@ module Gitlab def execute(stage, &block) @stage = stage - @config = QueryConfig.get(stage) query = build_query(&block) ActiveRecord::Base.connection.exec_query(query.to_sql) @@ -21,15 +20,11 @@ module Gitlab def build_query base_query = base_query_for(@stage) - diff_fn = subtract_datetimes_diff(@config[:base_query], @config[:start_time_attrs], @config[:end_time_attrs]) + diff_fn = subtract_datetimes_diff(base_query, stage_class.start_time_attrs, stage_class.end_time_attrs) - yield base_query if block_given? + yield(stage_class, base_query) if block_given? - base_query.project(extract_epoch(diff_fn).as('total_time'), *@config[:projections]).order(order.desc) - end - - def order - @config[:order] || @config[:start_time_attrs] + base_query.project(extract_epoch(diff_fn).as('total_time'), *stage_class.projections).order(stage_class.order.desc) end def extract_epoch(arel_attribute) @@ -37,6 +32,10 @@ module Gitlab Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))}) end + + def stage_class + @stage_class ||= "Gitlab::CycleAnalytics::#{@stage.to_s.camelize}Config".constantize + end end end end diff --git a/lib/gitlab/cycle_analytics/issue_config.rb b/lib/gitlab/cycle_analytics/issue_config.rb new file mode 100644 index 00000000000..985ac76feb2 --- /dev/null +++ b/lib/gitlab/cycle_analytics/issue_config.rb @@ -0,0 +1,16 @@ +module Gitlab + module CycleAnalytics + class IssueConfig < BaseConfig + @start_time_attrs = issue_table[:created_at] + + @end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at], + issue_metrics_table[:first_added_to_board_at]] + + @projections = [issue_table[:title], + issue_table[:iid], + issue_table[:id], + issue_table[:created_at], + issue_table[:author_id]] + end + end +end diff --git a/lib/gitlab/cycle_analytics/plan_config.rb b/lib/gitlab/cycle_analytics/plan_config.rb new file mode 100644 index 00000000000..453ee26b4c9 --- /dev/null +++ b/lib/gitlab/cycle_analytics/plan_config.rb @@ -0,0 +1,17 @@ +module Gitlab + module CycleAnalytics + class PlanConfig < BaseConfig + @start_time_attrs = issue_metrics_table[:first_associated_with_milestone_at] + + @end_time_attrs = [issue_metrics_table[:first_added_to_board_at], + issue_metrics_table[:first_mentioned_in_commit_at]] + + @projections = [mr_diff_table[:st_commits].as('commits'), + issue_metrics_table[:first_mentioned_in_commit_at]] + + def self.query(base_query) + base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/production_config.rb b/lib/gitlab/cycle_analytics/production_config.rb new file mode 100644 index 00000000000..dc9f1d5d059 --- /dev/null +++ b/lib/gitlab/cycle_analytics/production_config.rb @@ -0,0 +1,15 @@ +module Gitlab + module CycleAnalytics + class ProductionConfig < BaseConfig + @start_time_attrs = issue_table[:created_at] + + @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] + + @projections = [issue_table[:title], + issue_table[:iid], + issue_table[:id], + issue_table[:created_at], + issue_table[:author_id]] + end + end +end diff --git a/lib/gitlab/cycle_analytics/query_config.rb b/lib/gitlab/cycle_analytics/query_config.rb deleted file mode 100644 index 4fddbad95f2..00000000000 --- a/lib/gitlab/cycle_analytics/query_config.rb +++ /dev/null @@ -1,99 +0,0 @@ -module Gitlab - module CycleAnalytics - class QueryConfig - include MetricsFetcher - - def self.get(*args) - new(*args).get - end - - def initialize(stage) - @stage = stage - end - - def get - public_send(@stage).freeze if self.respond_to?(@stage) - end - - def issue - { - start_time_attrs: issue_table[:created_at], - end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at], - issue_metrics_table[:first_added_to_board_at]], - projections: [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id]] - } - end - - def plan - { - start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at], - end_time_attrs: [issue_metrics_table[:first_added_to_board_at], - issue_metrics_table[:first_mentioned_in_commit_at]], - projections: [mr_diff_table[:st_commits].as('commits'), - issue_metrics_table[:first_mentioned_in_commit_at]] - } - end - - def code - { - start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at], - end_time_attrs: mr_table[:created_at], - projections: [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state], - mr_table[:author_id]], - order: mr_table[:created_at] - } - end - - def test - { - start_time_attrs: mr_metrics_table[:latest_build_started_at], - end_time_attrs: mr_metrics_table[:latest_build_finished_at], - projections: [build_table[:id]], - order: build_table[:created_at] - } - end - - def review - { - start_time_attrs: mr_table[:created_at], - end_time_attrs: mr_metrics_table[:merged_at], - projections: [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state], - mr_table[:author_id]] - } - end - - def staging - { - start_time_attrs: mr_metrics_table[:merged_at], - end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], - projections: [build_table[:id]], - order: build_table[:created_at] - } - end - - def production - { - start_time_attrs: issue_table[:created_at], - end_time_attrs: mr_metrics_table[:first_deployed_to_production_at], - projections: [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id]] - } - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/review_config.rb b/lib/gitlab/cycle_analytics/review_config.rb new file mode 100644 index 00000000000..e91dc1ea104 --- /dev/null +++ b/lib/gitlab/cycle_analytics/review_config.rb @@ -0,0 +1,16 @@ +module Gitlab + module CycleAnalytics + class ReviewConfig < BaseConfig + @start_time_attrs = mr_table[:created_at] + + @end_time_attrs = mr_metrics_table[:merged_at] + + @projections = [mr_table[:title], + mr_table[:iid], + mr_table[:id], + mr_table[:created_at], + mr_table[:state], + mr_table[:author_id]] + end + end +end diff --git a/lib/gitlab/cycle_analytics/staging_config.rb b/lib/gitlab/cycle_analytics/staging_config.rb new file mode 100644 index 00000000000..f6f471286a0 --- /dev/null +++ b/lib/gitlab/cycle_analytics/staging_config.rb @@ -0,0 +1,14 @@ +module Gitlab + module CycleAnalytics + class StagingConfig < BaseConfig + @start_time_attrs = mr_metrics_table[:merged_at] + @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] + @projections = [build_table[:id]] + @order = build_table[:created_at] + + def self.query(base_query) + base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/test_config.rb b/lib/gitlab/cycle_analytics/test_config.rb new file mode 100644 index 00000000000..6cb80f9a62c --- /dev/null +++ b/lib/gitlab/cycle_analytics/test_config.rb @@ -0,0 +1,14 @@ +module Gitlab + module CycleAnalytics + class TestConfig < BaseConfig + @start_time_attrs = mr_metrics_table[:latest_build_started_at] + @end_time_attrs = mr_metrics_table[:latest_build_finished_at] + @projections = [build_table[:id]] + @order = build_table[:created_at] + + def self.query(base_query) + base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) + end + end + end +end -- cgit v1.2.1 From 9e0102e494d27bbb7fcd4ae8f2b0c0bc4ce3e7ce Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 16 Nov 2016 16:09:24 +0100 Subject: small refactor - removing includes no longer needed --- lib/gitlab/cycle_analytics/events_fetcher.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 714afb88833..4c1d19774f5 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -1,8 +1,6 @@ module Gitlab module CycleAnalytics class EventsFetcher - include MetricsFetcher - def initialize(project:, options:) @query = EventsQuery.new(project: project, options: options) end -- cgit v1.2.1 From f9de157e70234748cf4285fafda7b3ec13862f5c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 16 Nov 2016 16:45:13 +0100 Subject: fix date issue with mySQL only --- lib/gitlab/cycle_analytics/events.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index f4d6d2049ef..8e6e6b2f4a3 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -58,7 +58,7 @@ module Gitlab YAML.load(commits).find do |commit| next unless commit[:committed_date] && event['first_mentioned_in_commit_at'] - commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at']).to_i + commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i end end -- cgit v1.2.1 From ed39d61d746925e49d952bd8169499d3200ae68b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 17 Nov 2016 13:22:27 +0100 Subject: refactor events facade so it uses separate classes and refactor query stuff --- lib/gitlab/cycle_analytics/base_config.rb | 17 -------- lib/gitlab/cycle_analytics/base_event.rb | 27 +++++++++++++ lib/gitlab/cycle_analytics/code_config.rb | 18 --------- lib/gitlab/cycle_analytics/code_event.rb | 25 ++++++++++++ lib/gitlab/cycle_analytics/event_config.rb | 16 ++++++++ lib/gitlab/cycle_analytics/events.rb | 54 ++++--------------------- lib/gitlab/cycle_analytics/events_fetcher.rb | 4 +- lib/gitlab/cycle_analytics/events_query.rb | 22 +++++----- lib/gitlab/cycle_analytics/issue_config.rb | 16 -------- lib/gitlab/cycle_analytics/issue_event.rb | 23 +++++++++++ lib/gitlab/cycle_analytics/metrics_fetcher.rb | 42 ++++--------------- lib/gitlab/cycle_analytics/metrics_tables.rb | 37 +++++++++++++++++ lib/gitlab/cycle_analytics/plan_config.rb | 17 -------- lib/gitlab/cycle_analytics/plan_event.rb | 44 ++++++++++++++++++++ lib/gitlab/cycle_analytics/production_config.rb | 15 ------- lib/gitlab/cycle_analytics/production_event.rb | 22 ++++++++++ lib/gitlab/cycle_analytics/review_config.rb | 16 -------- lib/gitlab/cycle_analytics/review_event.rb | 21 ++++++++++ lib/gitlab/cycle_analytics/staging_config.rb | 14 ------- lib/gitlab/cycle_analytics/staging_event.rb | 21 ++++++++++ lib/gitlab/cycle_analytics/test_config.rb | 14 ------- lib/gitlab/cycle_analytics/test_event.rb | 21 ++++++++++ 22 files changed, 283 insertions(+), 223 deletions(-) delete mode 100644 lib/gitlab/cycle_analytics/base_config.rb create mode 100644 lib/gitlab/cycle_analytics/base_event.rb delete mode 100644 lib/gitlab/cycle_analytics/code_config.rb create mode 100644 lib/gitlab/cycle_analytics/code_event.rb create mode 100644 lib/gitlab/cycle_analytics/event_config.rb delete mode 100644 lib/gitlab/cycle_analytics/issue_config.rb create mode 100644 lib/gitlab/cycle_analytics/issue_event.rb create mode 100644 lib/gitlab/cycle_analytics/metrics_tables.rb delete mode 100644 lib/gitlab/cycle_analytics/plan_config.rb create mode 100644 lib/gitlab/cycle_analytics/plan_event.rb delete mode 100644 lib/gitlab/cycle_analytics/production_config.rb create mode 100644 lib/gitlab/cycle_analytics/production_event.rb delete mode 100644 lib/gitlab/cycle_analytics/review_config.rb create mode 100644 lib/gitlab/cycle_analytics/review_event.rb delete mode 100644 lib/gitlab/cycle_analytics/staging_config.rb create mode 100644 lib/gitlab/cycle_analytics/staging_event.rb delete mode 100644 lib/gitlab/cycle_analytics/test_config.rb create mode 100644 lib/gitlab/cycle_analytics/test_event.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/base_config.rb b/lib/gitlab/cycle_analytics/base_config.rb deleted file mode 100644 index 06ae9a15a7a..00000000000 --- a/lib/gitlab/cycle_analytics/base_config.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module CycleAnalytics - class BaseConfig - extend MetricsFetcher - - class << self - attr_reader :start_time_attrs, :end_time_attrs, :projections - end - - def self.order - @order || @start_time_attrs - end - - def self.query(base_query); end - end - end -end diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb new file mode 100644 index 00000000000..254e3621575 --- /dev/null +++ b/lib/gitlab/cycle_analytics/base_event.rb @@ -0,0 +1,27 @@ +module Gitlab + module CycleAnalytics + class BaseEvent + extend MetricsTables + + class << self + attr_reader :stage, :start_time_attrs, :end_time_attrs, :projections + + def order + @order || @start_time_attrs + end + + def query(base_query); end + + def fetch(query) + query.execute(self).map { |event| serialize(event, query) } + end + + private + + def serialize(event, query) + raise NotImplementedError.new("Expected #{self.name} to implement serialize(event)") + end + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/code_config.rb b/lib/gitlab/cycle_analytics/code_config.rb deleted file mode 100644 index c27419310db..00000000000 --- a/lib/gitlab/cycle_analytics/code_config.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Gitlab - module CycleAnalytics - class CodeConfig < BaseConfig - @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at] - - @end_time_attrs = mr_table[:created_at] - - @projections = [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state], - mr_table[:author_id]] - - @order = mr_table[:created_at] - end - end -end diff --git a/lib/gitlab/cycle_analytics/code_event.rb b/lib/gitlab/cycle_analytics/code_event.rb new file mode 100644 index 00000000000..609b45579b4 --- /dev/null +++ b/lib/gitlab/cycle_analytics/code_event.rb @@ -0,0 +1,25 @@ +module Gitlab + module CycleAnalytics + class CodeEvent < BaseEvent + @stage = :code + @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at] + + @end_time_attrs = mr_table[:created_at] + + @projections = [mr_table[:title], + mr_table[:iid], + mr_table[:id], + mr_table[:created_at], + mr_table[:state], + mr_table[:author_id]] + + @order = mr_table[:created_at] + + def self.serialize(event, query) + event['author'] = User.find(event.delete('author_id')) + + AnalyticsMergeRequestSerializer.new(project: query.project).represent(event).as_json + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/event_config.rb b/lib/gitlab/cycle_analytics/event_config.rb new file mode 100644 index 00000000000..2e1400c41b9 --- /dev/null +++ b/lib/gitlab/cycle_analytics/event_config.rb @@ -0,0 +1,16 @@ +module Gitlab + module CycleAnalytics + class TestEvent < BaseEvent + @start_time_attrs = mr_table[:created_at] + + @end_time_attrs = mr_metrics_table[:merged_at] + + @projections = [mr_table[:title], + mr_table[:iid], + mr_table[:id], + mr_table[:created_at], + mr_table[:state], + mr_table[:author_id]] + end + end +end diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 8e6e6b2f4a3..6580c73128c 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -3,73 +3,35 @@ module Gitlab class Events def initialize(project:, options:) @project = project - @fetcher = EventsFetcher.new(project: project, options: options) + @query = EventsQuery.new(project: project, options: options) end def issue_events - @fetcher.fetch(stage: :issue).map { |event| serialize_event(event) } + IssueEvent.fetch(@query) end def plan_events - @fetcher.fetch(stage: :plan).map do |event| - st_commit = first_time_reference_commit(event.delete('commits'), event) - - next unless st_commit - - serialize_commit(event, st_commit) - end + PlanEvent.fetch(@query) end def code_events - @fetcher.fetch(stage: :code).map { |event| serialize_event(event, entity: :merge_request) } + CodeEvent.fetch(@query) end def test_events - @fetcher.fetch(stage: :test).map { |event| serialize_build_event(event) } + TestEvent.fetch(@query) end def review_events - @fetcher.fetch(stage: :review).map { |event| serialize_event(event, entity: :merge_request) } + ReviewEvent.fetch(@query) end def staging_events - @fetcher.fetch(stage: :staging).map { |event| serialize_build_event(event) } + StagingEvent.fetch(@query) end def production_events - @fetcher.fetch(stage: :production).map { |event| serialize_event(event) } - end - - private - - def serialize_event(event, entity: :issue) - event['author'] = User.find(event.delete('author_id')) - - AnalyticsGenericSerializer.new(project: @project, entity: entity).represent(event).as_json - end - - def serialize_build_event(event) - build = ::Ci::Build.find(event['id']) - - AnalyticsBuildSerializer.new.represent(build).as_json - end - - def first_time_reference_commit(commits, event) - YAML.load(commits).find do |commit| - next unless commit[:committed_date] && event['first_mentioned_in_commit_at'] - - commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i - end - end - - def serialize_commit(event, st_commit) - commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project) - - AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json - end - - def interval_in_words(diff) - "#{distance_of_time_in_words(diff.to_f)} ago" + ProductionEvent.fetch(@query) end end end diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb index 4c1d19774f5..813db46f058 100644 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ b/lib/gitlab/cycle_analytics/events_fetcher.rb @@ -6,9 +6,7 @@ module Gitlab end def fetch(stage:) - @query.execute(stage) do |stage_class, base_query| - stage_class.query(base_query) - end + @query.execute(stage) end end end diff --git a/lib/gitlab/cycle_analytics/events_query.rb b/lib/gitlab/cycle_analytics/events_query.rb index b61320fc561..7ecdc07d17f 100644 --- a/lib/gitlab/cycle_analytics/events_query.rb +++ b/lib/gitlab/cycle_analytics/events_query.rb @@ -1,30 +1,30 @@ module Gitlab module CycleAnalytics class EventsQuery - include MetricsFetcher + attr_reader :project def initialize(project:, options: {}) @project = project @from = options[:from] @branch = options[:branch] + @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: @from, branch: @branch) end - def execute(stage, &block) - @stage = stage - query = build_query(&block) + def execute(stage_class) + @stage_class = stage_class ActiveRecord::Base.connection.exec_query(query.to_sql) end private - def build_query - base_query = base_query_for(@stage) - diff_fn = subtract_datetimes_diff(base_query, stage_class.start_time_attrs, stage_class.end_time_attrs) + def query + base_query = @fetcher.base_query_for(@stage_class.stage) + diff_fn = @fetcher.subtract_datetimes_diff(base_query, @stage_class.start_time_attrs, @stage_class.end_time_attrs) - yield(stage_class, base_query) if block_given? + @stage_class.query(base_query) - base_query.project(extract_epoch(diff_fn).as('total_time'), *stage_class.projections).order(stage_class.order.desc) + base_query.project(extract_epoch(diff_fn).as('total_time'), *@stage_class.projections).order(@stage_class.order.desc) end def extract_epoch(arel_attribute) @@ -32,10 +32,6 @@ module Gitlab Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))}) end - - def stage_class - @stage_class ||= "Gitlab::CycleAnalytics::#{@stage.to_s.camelize}Config".constantize - end end end end diff --git a/lib/gitlab/cycle_analytics/issue_config.rb b/lib/gitlab/cycle_analytics/issue_config.rb deleted file mode 100644 index 985ac76feb2..00000000000 --- a/lib/gitlab/cycle_analytics/issue_config.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Gitlab - module CycleAnalytics - class IssueConfig < BaseConfig - @start_time_attrs = issue_table[:created_at] - - @end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at], - issue_metrics_table[:first_added_to_board_at]] - - @projections = [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id]] - end - end -end diff --git a/lib/gitlab/cycle_analytics/issue_event.rb b/lib/gitlab/cycle_analytics/issue_event.rb new file mode 100644 index 00000000000..9cfcdc9b20e --- /dev/null +++ b/lib/gitlab/cycle_analytics/issue_event.rb @@ -0,0 +1,23 @@ +module Gitlab + module CycleAnalytics + class IssueEvent < BaseEvent + @stage = :issue + @start_time_attrs = issue_table[:created_at] + + @end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at], + issue_metrics_table[:first_added_to_board_at]] + + @projections = [issue_table[:title], + issue_table[:iid], + issue_table[:id], + issue_table[:created_at], + issue_table[:author_id]] + + def self.serialize(event, query) + event['author'] = User.find(event.delete('author_id')) + + AnalyticsIssueSerializer.new(project: query.project).represent(event).as_json + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb index 6743cbcc9e0..b71e8735e27 100644 --- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb +++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb @@ -1,12 +1,18 @@ module Gitlab module CycleAnalytics - module MetricsFetcher + class MetricsFetcher include Gitlab::Database::Median include Gitlab::Database::DateTime + include MetricsTables DEPLOYMENT_METRIC_STAGES = %i[production staging] - private + def initialize(project:, from:, branch:) + @project = project + @project = project + @from = from + @branch = branch + end def calculate_metric(name, start_time_attrs, end_time_attrs) cte_table = Arel::Table.new("cte_table_for_#{name}") @@ -49,38 +55,6 @@ module Gitlab query end - - def mr_metrics_table - MergeRequest::Metrics.arel_table - end - - def mr_table - MergeRequest.arel_table - end - - def mr_diff_table - MergeRequestDiff.arel_table - end - - def mr_closing_issues_table - MergeRequestsClosingIssues.arel_table - end - - def issue_table - Issue.arel_table - end - - def issue_metrics_table - Issue::Metrics.arel_table - end - - def user_table - User.arel_table - end - - def build_table - ::CommitStatus.arel_table - end end end end diff --git a/lib/gitlab/cycle_analytics/metrics_tables.rb b/lib/gitlab/cycle_analytics/metrics_tables.rb new file mode 100644 index 00000000000..9d25ef078e8 --- /dev/null +++ b/lib/gitlab/cycle_analytics/metrics_tables.rb @@ -0,0 +1,37 @@ +module Gitlab + module CycleAnalytics + module MetricsTables + def mr_metrics_table + MergeRequest::Metrics.arel_table + end + + def mr_table + MergeRequest.arel_table + end + + def mr_diff_table + MergeRequestDiff.arel_table + end + + def mr_closing_issues_table + MergeRequestsClosingIssues.arel_table + end + + def issue_table + Issue.arel_table + end + + def issue_metrics_table + Issue::Metrics.arel_table + end + + def user_table + User.arel_table + end + + def build_table + ::CommitStatus.arel_table + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/plan_config.rb b/lib/gitlab/cycle_analytics/plan_config.rb deleted file mode 100644 index 453ee26b4c9..00000000000 --- a/lib/gitlab/cycle_analytics/plan_config.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module CycleAnalytics - class PlanConfig < BaseConfig - @start_time_attrs = issue_metrics_table[:first_associated_with_milestone_at] - - @end_time_attrs = [issue_metrics_table[:first_added_to_board_at], - issue_metrics_table[:first_mentioned_in_commit_at]] - - @projections = [mr_diff_table[:st_commits].as('commits'), - issue_metrics_table[:first_mentioned_in_commit_at]] - - def self.query(base_query) - base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/plan_event.rb b/lib/gitlab/cycle_analytics/plan_event.rb new file mode 100644 index 00000000000..b8107b06228 --- /dev/null +++ b/lib/gitlab/cycle_analytics/plan_event.rb @@ -0,0 +1,44 @@ +module Gitlab + module CycleAnalytics + class PlanEvent < BaseEvent + @stage = :plan + @start_time_attrs = issue_metrics_table[:first_associated_with_milestone_at] + + @end_time_attrs = [issue_metrics_table[:first_added_to_board_at], + issue_metrics_table[:first_mentioned_in_commit_at]] + + @projections = [mr_diff_table[:st_commits].as('commits'), + issue_metrics_table[:first_mentioned_in_commit_at]] + + class << self + def query(base_query) + base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) + end + + private + + def serialize(event, query) + st_commit = first_time_reference_commit(event.delete('commits'), event) + + return unless st_commit + + serialize_commit(event, st_commit, query) + end + + def first_time_reference_commit(commits, event) + YAML.load(commits).find do |commit| + next unless commit[:committed_date] && event['first_mentioned_in_commit_at'] + + commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i + end + end + + def serialize_commit(event, st_commit, query) + commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project) + + AnalyticsCommitSerializer.new(project: query.project, total_time: event['total_time']).represent(commit).as_json + end + end + end + end +end \ No newline at end of file diff --git a/lib/gitlab/cycle_analytics/production_config.rb b/lib/gitlab/cycle_analytics/production_config.rb deleted file mode 100644 index dc9f1d5d059..00000000000 --- a/lib/gitlab/cycle_analytics/production_config.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Gitlab - module CycleAnalytics - class ProductionConfig < BaseConfig - @start_time_attrs = issue_table[:created_at] - - @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] - - @projections = [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id]] - end - end -end diff --git a/lib/gitlab/cycle_analytics/production_event.rb b/lib/gitlab/cycle_analytics/production_event.rb new file mode 100644 index 00000000000..b0b2d94f7e7 --- /dev/null +++ b/lib/gitlab/cycle_analytics/production_event.rb @@ -0,0 +1,22 @@ +module Gitlab + module CycleAnalytics + class ProductionEvent < BaseEvent + @stage = :production + @start_time_attrs = issue_table[:created_at] + + @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] + + @projections = [issue_table[:title], + issue_table[:iid], + issue_table[:id], + issue_table[:created_at], + issue_table[:author_id]] + + def self.serialize(event, query) + event['author'] = User.find(event.delete('author_id')) + + AnalyticsIssueSerializer.new(project: query.project).represent(event).as_json + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/review_config.rb b/lib/gitlab/cycle_analytics/review_config.rb deleted file mode 100644 index e91dc1ea104..00000000000 --- a/lib/gitlab/cycle_analytics/review_config.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Gitlab - module CycleAnalytics - class ReviewConfig < BaseConfig - @start_time_attrs = mr_table[:created_at] - - @end_time_attrs = mr_metrics_table[:merged_at] - - @projections = [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state], - mr_table[:author_id]] - end - end -end diff --git a/lib/gitlab/cycle_analytics/review_event.rb b/lib/gitlab/cycle_analytics/review_event.rb new file mode 100644 index 00000000000..dcd138eaa92 --- /dev/null +++ b/lib/gitlab/cycle_analytics/review_event.rb @@ -0,0 +1,21 @@ +module Gitlab + module CycleAnalytics + class ReviewEvent < BaseEvent + @stage = :review + @start_time_attrs = mr_table[:created_at] + @end_time_attrs = mr_metrics_table[:merged_at] + @projections = [mr_table[:title], + mr_table[:iid], + mr_table[:id], + mr_table[:created_at], + mr_table[:state], + mr_table[:author_id]] + + def self.serialize(event, _query) + event['author'] = User.find(event.delete('author_id')) + + AnalyticsMergeRequestSerializer.new(project: query.project).represent(event).as_json + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/staging_config.rb b/lib/gitlab/cycle_analytics/staging_config.rb deleted file mode 100644 index f6f471286a0..00000000000 --- a/lib/gitlab/cycle_analytics/staging_config.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Gitlab - module CycleAnalytics - class StagingConfig < BaseConfig - @start_time_attrs = mr_metrics_table[:merged_at] - @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] - @projections = [build_table[:id]] - @order = build_table[:created_at] - - def self.query(base_query) - base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/staging_event.rb b/lib/gitlab/cycle_analytics/staging_event.rb new file mode 100644 index 00000000000..a8872b50cfd --- /dev/null +++ b/lib/gitlab/cycle_analytics/staging_event.rb @@ -0,0 +1,21 @@ +module Gitlab + module CycleAnalytics + class StagingEvent < BaseEvent + @stage = :staging + @start_time_attrs = mr_metrics_table[:merged_at] + @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] + @projections = [build_table[:id]] + @order = build_table[:created_at] + + def self.query(base_query) + base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) + end + + def self.serialize(event, _query) + build = ::Ci::Build.find(event['id']) + + AnalyticsBuildSerializer.new.represent(build).as_json + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/test_config.rb b/lib/gitlab/cycle_analytics/test_config.rb deleted file mode 100644 index 6cb80f9a62c..00000000000 --- a/lib/gitlab/cycle_analytics/test_config.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Gitlab - module CycleAnalytics - class TestConfig < BaseConfig - @start_time_attrs = mr_metrics_table[:latest_build_started_at] - @end_time_attrs = mr_metrics_table[:latest_build_finished_at] - @projections = [build_table[:id]] - @order = build_table[:created_at] - - def self.query(base_query) - base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/test_event.rb b/lib/gitlab/cycle_analytics/test_event.rb new file mode 100644 index 00000000000..c91d3f47da0 --- /dev/null +++ b/lib/gitlab/cycle_analytics/test_event.rb @@ -0,0 +1,21 @@ +module Gitlab + module CycleAnalytics + class TestEvent < BaseEvent + @stage = :test + @start_time_attrs = mr_metrics_table[:latest_build_started_at] + @end_time_attrs = mr_metrics_table[:latest_build_finished_at] + @projections = [build_table[:id]] + @order = build_table[:created_at] + + def self.query(base_query) + base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) + end + + def self.serialize(event, _query) + build = ::Ci::Build.find(event['id']) + + AnalyticsBuildSerializer.new.represent(build).as_json + end + end + end +end -- cgit v1.2.1 From af80fcb477d13c240d8f58abab7d2b88a11ca7b5 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 17 Nov 2016 14:14:23 +0100 Subject: fix specs --- lib/gitlab/cycle_analytics/base_event.rb | 4 ++-- lib/gitlab/cycle_analytics/issue_event.rb | 2 +- lib/gitlab/cycle_analytics/plan_event.rb | 2 +- lib/gitlab/cycle_analytics/review_event.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb index 254e3621575..3c43fb24c61 100644 --- a/lib/gitlab/cycle_analytics/base_event.rb +++ b/lib/gitlab/cycle_analytics/base_event.rb @@ -10,7 +10,7 @@ module Gitlab @order || @start_time_attrs end - def query(base_query); end + def query(_base_query); end def fetch(query) query.execute(self).map { |event| serialize(event, query) } @@ -18,7 +18,7 @@ module Gitlab private - def serialize(event, query) + def serialize(_event, _query) raise NotImplementedError.new("Expected #{self.name} to implement serialize(event)") end end diff --git a/lib/gitlab/cycle_analytics/issue_event.rb b/lib/gitlab/cycle_analytics/issue_event.rb index 9cfcdc9b20e..9ae2c481158 100644 --- a/lib/gitlab/cycle_analytics/issue_event.rb +++ b/lib/gitlab/cycle_analytics/issue_event.rb @@ -13,7 +13,7 @@ module Gitlab issue_table[:created_at], issue_table[:author_id]] - def self.serialize(event, query) + def self.serialize(event, query)# event['author'] = User.find(event.delete('author_id')) AnalyticsIssueSerializer.new(project: query.project).represent(event).as_json diff --git a/lib/gitlab/cycle_analytics/plan_event.rb b/lib/gitlab/cycle_analytics/plan_event.rb index b8107b06228..4980a7835eb 100644 --- a/lib/gitlab/cycle_analytics/plan_event.rb +++ b/lib/gitlab/cycle_analytics/plan_event.rb @@ -41,4 +41,4 @@ module Gitlab end end end -end \ No newline at end of file +end diff --git a/lib/gitlab/cycle_analytics/review_event.rb b/lib/gitlab/cycle_analytics/review_event.rb index dcd138eaa92..82095b23b71 100644 --- a/lib/gitlab/cycle_analytics/review_event.rb +++ b/lib/gitlab/cycle_analytics/review_event.rb @@ -11,7 +11,7 @@ module Gitlab mr_table[:state], mr_table[:author_id]] - def self.serialize(event, _query) + def self.serialize(event, query) event['author'] = User.find(event.delete('author_id')) AnalyticsMergeRequestSerializer.new(project: query.project).represent(event).as_json -- cgit v1.2.1 From 37a35050191b41cb5150d14292887d2fe88dad37 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 17 Nov 2016 15:00:01 +0100 Subject: fix integration spec --- lib/gitlab/cycle_analytics/issue_event.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/issue_event.rb b/lib/gitlab/cycle_analytics/issue_event.rb index 9ae2c481158..9cfcdc9b20e 100644 --- a/lib/gitlab/cycle_analytics/issue_event.rb +++ b/lib/gitlab/cycle_analytics/issue_event.rb @@ -13,7 +13,7 @@ module Gitlab issue_table[:created_at], issue_table[:author_id]] - def self.serialize(event, query)# + def self.serialize(event, query) event['author'] = User.find(event.delete('author_id')) AnalyticsIssueSerializer.new(project: query.project).represent(event).as_json -- cgit v1.2.1 From e22b5cca02d2885b36e6636ba3e6dcf78ada6f8b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 17 Nov 2016 15:00:56 +0100 Subject: remove class no longer required --- lib/gitlab/cycle_analytics/events_fetcher.rb | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 lib/gitlab/cycle_analytics/events_fetcher.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb deleted file mode 100644 index 813db46f058..00000000000 --- a/lib/gitlab/cycle_analytics/events_fetcher.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Gitlab - module CycleAnalytics - class EventsFetcher - def initialize(project:, options:) - @query = EventsQuery.new(project: project, options: options) - end - - def fetch(stage:) - @query.execute(stage) - end - end - end -end -- cgit v1.2.1 From 9c995725ea6eb8d97f4b7dfbd129e3530b9b1235 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 17 Nov 2016 15:05:13 +0100 Subject: fix typo in error message --- lib/gitlab/cycle_analytics/base_event.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb index 3c43fb24c61..c415b93c80f 100644 --- a/lib/gitlab/cycle_analytics/base_event.rb +++ b/lib/gitlab/cycle_analytics/base_event.rb @@ -19,7 +19,7 @@ module Gitlab private def serialize(_event, _query) - raise NotImplementedError.new("Expected #{self.name} to implement serialize(event)") + raise NotImplementedError.new("Expected #{self.name} to implement serialize(event, query)") end end end -- cgit v1.2.1 From d5b673da79c7c350090dc9982a9a2477fac56eb5 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 17 Nov 2016 18:00:37 +0100 Subject: more refactoring and added some auth checks --- lib/gitlab/cycle_analytics/base_event.rb | 36 ++++++++++------- lib/gitlab/cycle_analytics/code_event.rb | 33 +++++++++------- lib/gitlab/cycle_analytics/event_config.rb | 16 -------- lib/gitlab/cycle_analytics/events.rb | 16 ++++---- lib/gitlab/cycle_analytics/events_query.rb | 2 +- lib/gitlab/cycle_analytics/issue_event.rb | 30 ++++++++------ lib/gitlab/cycle_analytics/plan_event.rb | 54 +++++++++++++------------- lib/gitlab/cycle_analytics/production_event.rb | 28 ++++++++----- lib/gitlab/cycle_analytics/review_event.rb | 30 ++++++++------ lib/gitlab/cycle_analytics/staging_event.rb | 20 ++++++---- lib/gitlab/cycle_analytics/test_event.rb | 20 ++++++---- 11 files changed, 161 insertions(+), 124 deletions(-) delete mode 100644 lib/gitlab/cycle_analytics/event_config.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb index c415b93c80f..1e76fbec855 100644 --- a/lib/gitlab/cycle_analytics/base_event.rb +++ b/lib/gitlab/cycle_analytics/base_event.rb @@ -1,26 +1,36 @@ module Gitlab module CycleAnalytics class BaseEvent - extend MetricsTables + include MetricsTables - class << self - attr_reader :stage, :start_time_attrs, :end_time_attrs, :projections + attr_reader :stage, :start_time_attrs, :end_time_attrs, :projections, :query - def order - @order || @start_time_attrs + def initialize(project:, options:) + @query = EventsQuery.new(project: project, options: options) + @project = project + @options = options + end + + def fetch + @query.execute(self).map do |event| + serialize(event) if has_permission?(event['id']) end + end - def query(_base_query); end + def custom_query(_base_query); end - def fetch(query) - query.execute(self).map { |event| serialize(event, query) } - end + def order + @order || @start_time_attrs + end - private + private - def serialize(_event, _query) - raise NotImplementedError.new("Expected #{self.name} to implement serialize(event, query)") - end + def serialize(_event) + raise NotImplementedError.new("Expected #{self.name} to implement serialize(event)") + end + + def has_permission?(_id) + true end end end diff --git a/lib/gitlab/cycle_analytics/code_event.rb b/lib/gitlab/cycle_analytics/code_event.rb index 609b45579b4..29f62cb22aa 100644 --- a/lib/gitlab/cycle_analytics/code_event.rb +++ b/lib/gitlab/cycle_analytics/code_event.rb @@ -1,24 +1,31 @@ module Gitlab module CycleAnalytics class CodeEvent < BaseEvent - @stage = :code - @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at] + def initialize(*args) + @stage = :code + @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at] + @end_time_attrs = mr_table[:created_at] + @projections = [mr_table[:title], + mr_table[:iid], + mr_table[:id], + mr_table[:created_at], + mr_table[:state], + mr_table[:author_id]] + @order = mr_table[:created_at] - @end_time_attrs = mr_table[:created_at] - - @projections = [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state], - mr_table[:author_id]] + super(*args) + end - @order = mr_table[:created_at] + private - def self.serialize(event, query) + def serialize(event) event['author'] = User.find(event.delete('author_id')) - AnalyticsMergeRequestSerializer.new(project: query.project).represent(event).as_json + AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json + end + + def has_permission?(id) + @options[:current_user].can?(:read_merge_request, MergeRequest.find(id)) end end end diff --git a/lib/gitlab/cycle_analytics/event_config.rb b/lib/gitlab/cycle_analytics/event_config.rb deleted file mode 100644 index 2e1400c41b9..00000000000 --- a/lib/gitlab/cycle_analytics/event_config.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Gitlab - module CycleAnalytics - class TestEvent < BaseEvent - @start_time_attrs = mr_table[:created_at] - - @end_time_attrs = mr_metrics_table[:merged_at] - - @projections = [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state], - mr_table[:author_id]] - end - end -end diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb index 6580c73128c..2d703d76cbb 100644 --- a/lib/gitlab/cycle_analytics/events.rb +++ b/lib/gitlab/cycle_analytics/events.rb @@ -3,35 +3,35 @@ module Gitlab class Events def initialize(project:, options:) @project = project - @query = EventsQuery.new(project: project, options: options) + @options = options end def issue_events - IssueEvent.fetch(@query) + IssueEvent.new(project: @project, options: @options).fetch end def plan_events - PlanEvent.fetch(@query) + PlanEvent.new(project: @project, options: @options).fetch end def code_events - CodeEvent.fetch(@query) + CodeEvent.new(project: @project, options: @options).fetch end def test_events - TestEvent.fetch(@query) + TestEvent.new(project: @project, options: @options).fetch end def review_events - ReviewEvent.fetch(@query) + ReviewEvent.new(project: @project, options: @options).fetch end def staging_events - StagingEvent.fetch(@query) + StagingEvent.new(project: @project, options: @options).fetch end def production_events - ProductionEvent.fetch(@query) + ProductionEvent.new(project: @project, options: @options).fetch end end end diff --git a/lib/gitlab/cycle_analytics/events_query.rb b/lib/gitlab/cycle_analytics/events_query.rb index 7ecdc07d17f..2418832ccc2 100644 --- a/lib/gitlab/cycle_analytics/events_query.rb +++ b/lib/gitlab/cycle_analytics/events_query.rb @@ -22,7 +22,7 @@ module Gitlab base_query = @fetcher.base_query_for(@stage_class.stage) diff_fn = @fetcher.subtract_datetimes_diff(base_query, @stage_class.start_time_attrs, @stage_class.end_time_attrs) - @stage_class.query(base_query) + @stage_class.custom_query(base_query) base_query.project(extract_epoch(diff_fn).as('total_time'), *@stage_class.projections).order(@stage_class.order.desc) end diff --git a/lib/gitlab/cycle_analytics/issue_event.rb b/lib/gitlab/cycle_analytics/issue_event.rb index 9cfcdc9b20e..70c015df419 100644 --- a/lib/gitlab/cycle_analytics/issue_event.rb +++ b/lib/gitlab/cycle_analytics/issue_event.rb @@ -1,22 +1,30 @@ module Gitlab module CycleAnalytics class IssueEvent < BaseEvent - @stage = :issue - @start_time_attrs = issue_table[:created_at] + def initialize(*args) + @stage = :issue + @start_time_attrs = issue_table[:created_at] + @end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at], + issue_metrics_table[:first_added_to_board_at]] + @projections = [issue_table[:title], + issue_table[:iid], + issue_table[:id], + issue_table[:created_at], + issue_table[:author_id]] - @end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at], - issue_metrics_table[:first_added_to_board_at]] + super(*args) + end - @projections = [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id]] + private - def self.serialize(event, query) + def serialize(event) event['author'] = User.find(event.delete('author_id')) - AnalyticsIssueSerializer.new(project: query.project).represent(event).as_json + AnalyticsIssueSerializer.new(project: @project).represent(event).as_json + end + + def has_permission?(id) + @options[:current_user].can?(:read_issue, Issue.find(id)) end end end diff --git a/lib/gitlab/cycle_analytics/plan_event.rb b/lib/gitlab/cycle_analytics/plan_event.rb index 4980a7835eb..b1ae215f348 100644 --- a/lib/gitlab/cycle_analytics/plan_event.rb +++ b/lib/gitlab/cycle_analytics/plan_event.rb @@ -1,43 +1,43 @@ module Gitlab module CycleAnalytics class PlanEvent < BaseEvent - @stage = :plan - @start_time_attrs = issue_metrics_table[:first_associated_with_milestone_at] - - @end_time_attrs = [issue_metrics_table[:first_added_to_board_at], - issue_metrics_table[:first_mentioned_in_commit_at]] - - @projections = [mr_diff_table[:st_commits].as('commits'), - issue_metrics_table[:first_mentioned_in_commit_at]] + def initialize(*args) + @stage = :plan + @start_time_attrs = issue_metrics_table[:first_associated_with_milestone_at] + @end_time_attrs = [issue_metrics_table[:first_added_to_board_at], + issue_metrics_table[:first_mentioned_in_commit_at]] + @projections = [mr_diff_table[:st_commits].as('commits'), + issue_metrics_table[:first_mentioned_in_commit_at]] + + super(*args) + end - class << self - def query(base_query) - base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) - end + def custom_query(base_query) + base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) + end - private + private - def serialize(event, query) - st_commit = first_time_reference_commit(event.delete('commits'), event) + def serialize(event) + st_commit = first_time_reference_commit(event.delete('commits'), event) - return unless st_commit + return unless st_commit - serialize_commit(event, st_commit, query) - end + serialize_commit(event, st_commit, query) + end - def first_time_reference_commit(commits, event) - YAML.load(commits).find do |commit| - next unless commit[:committed_date] && event['first_mentioned_in_commit_at'] + def first_time_reference_commit(commits, event) + YAML.load(commits).find do |commit| + next unless commit[:committed_date] && event['first_mentioned_in_commit_at'] - commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i - end + commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i end + end - def serialize_commit(event, st_commit, query) - commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project) + def serialize_commit(event, st_commit, query) + commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project) - AnalyticsCommitSerializer.new(project: query.project, total_time: event['total_time']).represent(commit).as_json - end + AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json end end end diff --git a/lib/gitlab/cycle_analytics/production_event.rb b/lib/gitlab/cycle_analytics/production_event.rb index b0b2d94f7e7..80c0d08c039 100644 --- a/lib/gitlab/cycle_analytics/production_event.rb +++ b/lib/gitlab/cycle_analytics/production_event.rb @@ -1,21 +1,29 @@ module Gitlab module CycleAnalytics class ProductionEvent < BaseEvent - @stage = :production - @start_time_attrs = issue_table[:created_at] + def initialize(*args) + @stage = :production + @start_time_attrs = issue_table[:created_at] + @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] + @projections = [issue_table[:title], + issue_table[:iid], + issue_table[:id], + issue_table[:created_at], + issue_table[:author_id]] - @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] + super(*args) + end - @projections = [issue_table[:title], - issue_table[:iid], - issue_table[:id], - issue_table[:created_at], - issue_table[:author_id]] + private - def self.serialize(event, query) + def serialize(event) event['author'] = User.find(event.delete('author_id')) - AnalyticsIssueSerializer.new(project: query.project).represent(event).as_json + AnalyticsIssueSerializer.new(project: @project).represent(event).as_json + end + + def has_permission?(id) + @options[:current_user].can?(:read_issue, Issue.find(id)) end end end diff --git a/lib/gitlab/cycle_analytics/review_event.rb b/lib/gitlab/cycle_analytics/review_event.rb index 82095b23b71..cc89ef68be0 100644 --- a/lib/gitlab/cycle_analytics/review_event.rb +++ b/lib/gitlab/cycle_analytics/review_event.rb @@ -1,20 +1,28 @@ module Gitlab module CycleAnalytics class ReviewEvent < BaseEvent - @stage = :review - @start_time_attrs = mr_table[:created_at] - @end_time_attrs = mr_metrics_table[:merged_at] - @projections = [mr_table[:title], - mr_table[:iid], - mr_table[:id], - mr_table[:created_at], - mr_table[:state], - mr_table[:author_id]] + def initialize(*args) + @stage = :review + @start_time_attrs = mr_table[:created_at] + @end_time_attrs = mr_metrics_table[:merged_at] + @projections = [mr_table[:title], + mr_table[:iid], + mr_table[:id], + mr_table[:created_at], + mr_table[:state], + mr_table[:author_id]] - def self.serialize(event, query) + super(*args) + end + + def serialize(event) event['author'] = User.find(event.delete('author_id')) - AnalyticsMergeRequestSerializer.new(project: query.project).represent(event).as_json + AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json + end + + def has_permission?(id) + @options[:current_user].can?(:read_merge_request, MergeRequest.find(id)) end end end diff --git a/lib/gitlab/cycle_analytics/staging_event.rb b/lib/gitlab/cycle_analytics/staging_event.rb index a8872b50cfd..800b2b786dd 100644 --- a/lib/gitlab/cycle_analytics/staging_event.rb +++ b/lib/gitlab/cycle_analytics/staging_event.rb @@ -1,17 +1,23 @@ module Gitlab module CycleAnalytics class StagingEvent < BaseEvent - @stage = :staging - @start_time_attrs = mr_metrics_table[:merged_at] - @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] - @projections = [build_table[:id]] - @order = build_table[:created_at] + def initialize(*args) + @stage = :staging + @start_time_attrs = mr_metrics_table[:merged_at] + @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] + @projections = [build_table[:id]] + @order = build_table[:created_at] - def self.query(base_query) + super(*args) + end + + def custom_query(base_query) base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) end - def self.serialize(event, _query) + private + + def serialize(event) build = ::Ci::Build.find(event['id']) AnalyticsBuildSerializer.new.represent(build).as_json diff --git a/lib/gitlab/cycle_analytics/test_event.rb b/lib/gitlab/cycle_analytics/test_event.rb index c91d3f47da0..00404d7f6b0 100644 --- a/lib/gitlab/cycle_analytics/test_event.rb +++ b/lib/gitlab/cycle_analytics/test_event.rb @@ -1,17 +1,23 @@ module Gitlab module CycleAnalytics class TestEvent < BaseEvent - @stage = :test - @start_time_attrs = mr_metrics_table[:latest_build_started_at] - @end_time_attrs = mr_metrics_table[:latest_build_finished_at] - @projections = [build_table[:id]] - @order = build_table[:created_at] + def initialize(*args) + @stage = :test + @start_time_attrs = mr_metrics_table[:latest_build_started_at] + @end_time_attrs = mr_metrics_table[:latest_build_finished_at] + @projections = [build_table[:id]] + @order = build_table[:created_at] - def self.query(base_query) + super(*args) + end + + def custom_query(base_query) base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) end - def self.serialize(event, _query) + private + + def serialize(event) build = ::Ci::Build.find(event['id']) AnalyticsBuildSerializer.new.represent(build).as_json -- cgit v1.2.1 From b34c063ec428f1fd890a357a3f8ac7c129ee4c46 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 1 Nov 2016 14:06:05 -0200 Subject: Pass project to Entities::Label to check if user is subscribed --- lib/api/entities.rb | 2 +- lib/api/subscriptions.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 6e370e961c4..e7042677635 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -454,7 +454,7 @@ module API end expose :subscribed do |label, options| - label.subscribed?(options[:current_user]) + label.subscribed?(options[:current_user], options[:project]) end end diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index 00a79c24f96..10749b34004 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -24,11 +24,11 @@ module API post ":id/#{type}/:subscribable_id/subscription" do resource = instance_exec(params[:subscribable_id], &finder) - if resource.subscribed?(current_user) + if resource.subscribed?(current_user, user_project) not_modified! else - resource.subscribe(current_user) - present resource, with: entity_class, current_user: current_user + resource.subscribe(current_user, user_project) + present resource, with: entity_class, current_user: current_user, project: user_project end end @@ -38,11 +38,11 @@ module API delete ":id/#{type}/:subscribable_id/subscription" do resource = instance_exec(params[:subscribable_id], &finder) - if !resource.subscribed?(current_user) + if !resource.subscribed?(current_user, user_project) not_modified! else - resource.unsubscribe(current_user) - present resource, with: entity_class, current_user: current_user + resource.unsubscribe(current_user, user_project) + present resource, with: entity_class, current_user: current_user, project: user_project end end end -- cgit v1.2.1 From 0c052f116c9e093936847280e833ca8985d2d94c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 4 Nov 2016 16:19:08 -0200 Subject: Remove default value for `project` argument on subscribable concern --- lib/api/entities.rb | 4 ++-- lib/api/issues.rb | 10 +++++----- lib/api/merge_requests.rb | 10 +++++----- lib/api/milestones.rb | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e7042677635..33cb6fd3704 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -218,7 +218,7 @@ module API expose :assignee, :author, using: Entities::UserBasic expose :subscribed do |issue, options| - issue.subscribed?(options[:current_user]) + issue.subscribed?(options[:current_user], options[:project] || issue.project) end expose :user_notes_count expose :upvotes, :downvotes @@ -248,7 +248,7 @@ module API expose :diff_head_sha, as: :sha expose :merge_commit_sha expose :subscribed do |merge_request, options| - merge_request.subscribed?(options[:current_user]) + merge_request.subscribed?(options[:current_user], options[:project]) end expose :user_notes_count expose :should_remove_source_branch?, as: :should_remove_source_branch diff --git a/lib/api/issues.rb b/lib/api/issues.rb index c9689e6f8ef..eea5b91d4f9 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -120,7 +120,7 @@ module API issues = issues.reorder(issuable_order_by => issuable_sort) - present paginate(issues), with: Entities::Issue, current_user: current_user + present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project end # Get a single project issue @@ -132,7 +132,7 @@ module API # GET /projects/:id/issues/:issue_id get ":id/issues/:issue_id" do @issue = find_project_issue(params[:issue_id]) - present @issue, with: Entities::Issue, current_user: current_user + present @issue, with: Entities::Issue, current_user: current_user, project: user_project end # Create a new project issue @@ -174,7 +174,7 @@ module API end if issue.valid? - present issue, with: Entities::Issue, current_user: current_user + present issue, with: Entities::Issue, current_user: current_user, project: user_project else render_validation_error!(issue) end @@ -217,7 +217,7 @@ module API issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) if issue.valid? - present issue, with: Entities::Issue, current_user: current_user + present issue, with: Entities::Issue, current_user: current_user, project: user_project else render_validation_error!(issue) end @@ -239,7 +239,7 @@ module API begin issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project) - present issue, with: Entities::Issue, current_user: current_user + present issue, with: Entities::Issue, current_user: current_user, project: user_project rescue ::Issues::MoveService::MoveError => error render_api_error!(error.message, 400) end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index f9720786e63..4176c7eec06 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -60,7 +60,7 @@ module API end merge_requests = merge_requests.reorder(issuable_order_by => issuable_sort) - present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user + present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Create a merge request' do @@ -87,7 +87,7 @@ module API merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute if merge_request.valid? - present merge_request, with: Entities::MergeRequest, current_user: current_user + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project else handle_merge_request_errors! merge_request.errors end @@ -120,7 +120,7 @@ module API get path do merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :read_merge_request, merge_request - present merge_request, with: Entities::MergeRequest, current_user: current_user + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Get the commits of a merge request' do @@ -167,7 +167,7 @@ module API merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) if merge_request.valid? - present merge_request, with: Entities::MergeRequest, current_user: current_user + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project else handle_merge_request_errors! merge_request.errors end @@ -212,7 +212,7 @@ module API execute(merge_request) end - present merge_request, with: Entities::MergeRequest, current_user: current_user + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Cancel merge if "Merge when build succeeds" is enabled' do diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index ba4a84275bc..937c118779d 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -114,7 +114,7 @@ module API } issues = IssuesFinder.new(current_user, finder_params).execute - present paginate(issues), with: Entities::Issue, current_user: current_user + present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project end end end -- cgit v1.2.1 From 8bacdfb2434d0ac64d3a18bd9f9b19e3596ee19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 17 Nov 2016 18:39:55 +0100 Subject: Use the public CE repo URL instead of the one used in the runner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will prevent the task to advertise using https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@gitlab.com/gitlab-org/gitlab-ce.git when https://gitlab.com/gitlab-org/gitlab-ce.git is enough Signed-off-by: Rémy Coutable --- lib/tasks/gitlab/dev.rake | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake index 3117075b08b..7db0779def8 100644 --- a/lib/tasks/gitlab/dev.rake +++ b/lib/tasks/gitlab/dev.rake @@ -4,10 +4,7 @@ namespace :gitlab do task :ee_compat_check, [:branch] => :environment do |_, args| opts = if ENV['CI'] - { - branch: ENV['CI_BUILD_REF_NAME'], - ce_repo: ENV['CI_BUILD_REPO'] - } + { branch: ENV['CI_BUILD_REF_NAME'] } else unless args[:branch] puts "Must specify a branch as an argument".color(:red) -- cgit v1.2.1 From 79122896318ae50c835418888f7f781fa2e463f4 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 17 Nov 2016 12:41:48 +0000 Subject: Allow sorting groups in API Allow `order_by` and `sort` parameters to `/api/v3/groups.json`. At present, only ordering by name and path is supported, and the default sort is name ascending (alphabetical order). --- lib/api/groups.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 3f57b9ab5bc..48ad3b80ae0 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -19,6 +19,8 @@ module API optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list' optional :all_available, type: Boolean, desc: 'Show all group that you have access to' optional :search, type: String, desc: 'Search for a specific group' + optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path' + optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' end get do groups = if current_user.admin @@ -31,6 +33,8 @@ module API groups = groups.search(params[:search]) if params[:search].present? groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? + groups = groups.reorder(params[:order_by] => params[:sort].to_sym) + present paginate(groups), with: Entities::Group end -- cgit v1.2.1 From 98773ef9745ae64da953a62fd4fbcbf290a37ea7 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 17 Nov 2016 20:42:18 +0100 Subject: preload ids or objects for users, merge request and issues --- lib/gitlab/cycle_analytics/author_updater.rb | 27 ++++++++++++++++++++++++++ lib/gitlab/cycle_analytics/base_event.rb | 24 ++++++++++++++++++++--- lib/gitlab/cycle_analytics/code_event.rb | 6 ++---- lib/gitlab/cycle_analytics/issue_event.rb | 6 ++---- lib/gitlab/cycle_analytics/production_event.rb | 6 ++---- lib/gitlab/cycle_analytics/review_event.rb | 6 ++---- 6 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 lib/gitlab/cycle_analytics/author_updater.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/author_updater.rb b/lib/gitlab/cycle_analytics/author_updater.rb new file mode 100644 index 00000000000..ab18f292bc3 --- /dev/null +++ b/lib/gitlab/cycle_analytics/author_updater.rb @@ -0,0 +1,27 @@ +module Gitlab + module CycleAnalytics + class AuthorUpdater + def self.update!(*args) + new(*args).update! + end + + def initialize(event_result) + @event_result = event_result + end + + def update! + @event_result.each do |event| + event['author'] = users[event.delete('author_id').to_i].first + end + end + + def user_ids + @event_result.map { |event| event['author_id'] } + end + + def users + @users ||= User.find(user_ids).group_by { |user| user['id'] } + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb index 1e76fbec855..7395561a3fc 100644 --- a/lib/gitlab/cycle_analytics/base_event.rb +++ b/lib/gitlab/cycle_analytics/base_event.rb @@ -12,7 +12,9 @@ module Gitlab end def fetch - @query.execute(self).map do |event| + update_author! if event_result.first['author_id'] + + event_result.map do |event| serialize(event) if has_permission?(event['id']) end end @@ -25,12 +27,28 @@ module Gitlab private + def update_author! + AuthorUpdater.update!(event_result) + end + + def event_result + @event_result ||= @query.execute(self).to_a + end + def serialize(_event) raise NotImplementedError.new("Expected #{self.name} to implement serialize(event)") end - def has_permission?(_id) - true + def has_permission?(id) + allowed_ids.nil? || allowed_ids.include?(id.to_i) + end + + def allowed_ids + nil + end + + def event_result_ids + event_result.map { |event| event['id'] } end end end diff --git a/lib/gitlab/cycle_analytics/code_event.rb b/lib/gitlab/cycle_analytics/code_event.rb index 29f62cb22aa..02a3a44d544 100644 --- a/lib/gitlab/cycle_analytics/code_event.rb +++ b/lib/gitlab/cycle_analytics/code_event.rb @@ -19,13 +19,11 @@ module Gitlab private def serialize(event) - event['author'] = User.find(event.delete('author_id')) - AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json end - def has_permission?(id) - @options[:current_user].can?(:read_merge_request, MergeRequest.find(id)) + def allowed_ids + @allowed_ids ||= MergeRequestsFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) end end end diff --git a/lib/gitlab/cycle_analytics/issue_event.rb b/lib/gitlab/cycle_analytics/issue_event.rb index 70c015df419..36a990d6222 100644 --- a/lib/gitlab/cycle_analytics/issue_event.rb +++ b/lib/gitlab/cycle_analytics/issue_event.rb @@ -18,13 +18,11 @@ module Gitlab private def serialize(event) - event['author'] = User.find(event.delete('author_id')) - AnalyticsIssueSerializer.new(project: @project).represent(event).as_json end - def has_permission?(id) - @options[:current_user].can?(:read_issue, Issue.find(id)) + def allowed_ids + @allowed_ids ||= IssuesFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) end end end diff --git a/lib/gitlab/cycle_analytics/production_event.rb b/lib/gitlab/cycle_analytics/production_event.rb index 80c0d08c039..fcf2dbe3490 100644 --- a/lib/gitlab/cycle_analytics/production_event.rb +++ b/lib/gitlab/cycle_analytics/production_event.rb @@ -17,13 +17,11 @@ module Gitlab private def serialize(event) - event['author'] = User.find(event.delete('author_id')) - AnalyticsIssueSerializer.new(project: @project).represent(event).as_json end - def has_permission?(id) - @options[:current_user].can?(:read_issue, Issue.find(id)) + def allowed_ids + @allowed_ids ||= IssuesFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) end end end diff --git a/lib/gitlab/cycle_analytics/review_event.rb b/lib/gitlab/cycle_analytics/review_event.rb index cc89ef68be0..30650537afe 100644 --- a/lib/gitlab/cycle_analytics/review_event.rb +++ b/lib/gitlab/cycle_analytics/review_event.rb @@ -16,13 +16,11 @@ module Gitlab end def serialize(event) - event['author'] = User.find(event.delete('author_id')) - AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json end - def has_permission?(id) - @options[:current_user].can?(:read_merge_request, MergeRequest.find(id)) + def allowed_ids + @allowed_ids ||= MergeRequestsFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) end end end -- cgit v1.2.1 From 24c2aa39ad0f55321ef2c28ce10d740bf6b78980 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 17 Nov 2016 20:54:02 +0100 Subject: fix blank state error --- lib/gitlab/cycle_analytics/base_event.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb index 7395561a3fc..cedc73142fa 100644 --- a/lib/gitlab/cycle_analytics/base_event.rb +++ b/lib/gitlab/cycle_analytics/base_event.rb @@ -12,7 +12,7 @@ module Gitlab end def fetch - update_author! if event_result.first['author_id'] + update_author! event_result.map do |event| serialize(event) if has_permission?(event['id']) @@ -28,6 +28,8 @@ module Gitlab private def update_author! + return unless event_result.any? && event_result.first['author_id'] + AuthorUpdater.update!(event_result) end -- cgit v1.2.1 From 718b08429ce3372756186f9822091e887d91e9b3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 13 Nov 2016 20:35:47 +0100 Subject: Create relation between chat user and GitLab user and allow to authorize them [ci skip] --- lib/gitlab/chat_name_token.rb | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 lib/gitlab/chat_name_token.rb (limited to 'lib') diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb new file mode 100644 index 00000000000..c8349839219 --- /dev/null +++ b/lib/gitlab/chat_name_token.rb @@ -0,0 +1,45 @@ +require 'json' + +module Gitlab + class ChatNameToken + attr_reader :token + + TOKEN_LENGTH = 50 + EXPIRY_TIME = 1800 + + def initialize(token = new_token) + @token = token + end + + def get + Gitlab::Redis.with do |redis| + data = redis.get(redis_key) + JSON.parse(data, symbolize_names: true) if data + end + end + + def store!(params) + Gitlab::Redis.with do |redis| + params = params.to_json + redis.set(redis_key, params, ex: EXPIRY_TIME) + token + end + end + + def delete + Gitlab::Redis.with do |redis| + redis.del(redis_key) + end + end + + private + + def new_token + Devise.friendly_token(TOKEN_LENGTH) + end + + def redis_key + "gitlab:chat_names:#{token}" + end + end +end -- cgit v1.2.1 From bb988fd21498102e203f49c0968153d5ea83d03f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Nov 2016 14:56:30 +0100 Subject: Add most of specs for chat names --- lib/gitlab/chat_name_token.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb index c8349839219..543f038a4d4 100644 --- a/lib/gitlab/chat_name_token.rb +++ b/lib/gitlab/chat_name_token.rb @@ -5,7 +5,7 @@ module Gitlab attr_reader :token TOKEN_LENGTH = 50 - EXPIRY_TIME = 1800 + EXPIRY_TIME = 10.minutes # 10 minutes def initialize(token = new_token) @token = token -- cgit v1.2.1 From 53271b486d296fae2e290d6948a05aeb47dbea89 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 14 Nov 2016 15:10:35 +0100 Subject: Make chat authorization to work [ci skip] --- lib/api/services.rb | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/api/services.rb b/lib/api/services.rb index fc8598daa32..b4b3bb6e41a 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -1,10 +1,10 @@ module API # Projects API class Services < Grape::API - before { authenticate! } - before { authorize_admin_project } - resource :projects do + before { authenticate! } + before { authorize_admin_project } + # Set service for project # # Example Request: @@ -59,5 +59,28 @@ module API present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin? end end + + resource :projects do + post ':id/services/:service_slug/trigger' do + project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) + + underscored_service = params[:service_slug].underscore + + not_found!('Service') unless Service.available_services_names.include?(underscored_service) + service_method = "#{underscored_service}_service" + + service = project.public_send(service_method) + + result = if service.try(:active?) && service.respond_to?(:trigger) + service.trigger(params) + end + + if result + present result, status: result[:status] || 200 + else + not_found!('Service') + end + end + end end end -- cgit v1.2.1 From 106b1e39c0d00f25e34dda0eba962c8cf1d43f1b Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 15 Nov 2016 09:55:56 +0100 Subject: First steps on refactoring Mattermost Slash commands Now, each subcommand has its own service, plus I've introduced presenters to be able to delegate the generation of the views. --- lib/mattermost/presenter.rb | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 lib/mattermost/presenter.rb (limited to 'lib') diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb new file mode 100644 index 00000000000..d8e3b3805f9 --- /dev/null +++ b/lib/mattermost/presenter.rb @@ -0,0 +1,84 @@ +module Mattermost + class Presenter + class << self + COMMAND_PREFIX = '/gitlab'.freeze + + def authorize_chat_name(params) + url = ChatNames::RequestService.new(service, params).execute + + { + response_type: :ephemeral, + message: "You are not authorized. Click this [link](#{url}) to authorize." + } + end + + # TODO figure out how I know which are available or not + def help_message(commands) + messages = ["Available commands:"] + + commands.each do |sub_command, attrs| + messages << "\t#{COMMAND_PREFIX} #{attrs[:help_message]}" + end + + { + response_type: :ephemeral, + text: messages.join("\n") + } + end + + def not_found + { + response_type: :ephemeral, + text: "404 not found! GitLab couldn't find what your were looking for! :boom:", + } + end + end + + attr_reader :result + + def initialize(result) + @result = result + end + + def present + if result.respond_to?(:count) + if result.count > 1 + return respond_collection(result) + elsif result.count == 0 + return not_found + else + result = result.first + end + end + + single_resource + end + + private + + def single_resource + message = title(resource) + message << "\n\n#{resource.description}" if resource.description + + { + response_type: :in_channel, + text: message + } + end + + def multiple_resources(resources) + message = "Multiple results were found:\n" + message << resource.map { |resource| " #{title(resource)}" }.join("\n") + + { + response_type: :ephemeral, + text: message + } + end + + def title(resource) + url = url_for([resource.project.namespace.becomes(Namespace), resource.project, resource]) + "### [#{resource.to_reference} #{resource.title}](#{url})" + end + end +end -- cgit v1.2.1 From 8c8bc07d32f1103bb7996b499ead6ad6eb5bd337 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 15 Nov 2016 21:50:27 +0100 Subject: Refactor and test Slash commands This is the structure Kamil proposed, which leaves us with a bunch of smaller classes. This commits deletes outdated files and tests everything from the SlashCommandService and down (child classes and subcommands) --- lib/mattermost/presenter.rb | 80 ++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 37 deletions(-) (limited to 'lib') diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index d8e3b3805f9..e1502e3f9ba 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -32,53 +32,59 @@ module Mattermost text: "404 not found! GitLab couldn't find what your were looking for! :boom:", } end - end - - attr_reader :result - def initialize(result) - @result = result - end - - def present - if result.respond_to?(:count) - if result.count > 1 - return respond_collection(result) - elsif result.count == 0 - return not_found - else - result = result.first + def present(resource) + return not_found unless resource + + if resource.respond_to?(:count) + if resource.count > 1 + return multiple_resources(resource) + elsif resource.count == 0 + return not_found + else + resource = resource.first + end end + + single_resource(resource) end - single_resource - end + private - private + def single_resource(resource) + message = title(resource) + message << "\n\n#{resource.description}" if resource.description - def single_resource - message = title(resource) - message << "\n\n#{resource.description}" if resource.description + { + response_type: :in_channel, + text: message + } + end - { - response_type: :in_channel, - text: message - } - end + def multiple_resources(resources) + message = "Multiple results were found:\n" + message << resources.map { |resource| " #{title(resource)}" }.join("\n") - def multiple_resources(resources) - message = "Multiple results were found:\n" - message << resource.map { |resource| " #{title(resource)}" }.join("\n") + { + response_type: :ephemeral, + text: message + } + end - { - response_type: :ephemeral, - text: message - } - end + def title(resource) + "### [#{resource.to_reference} #{resource.title}](#{url(resource)})" + end + + def url(resource) + helper = Rails.application.routes.url_helpers - def title(resource) - url = url_for([resource.project.namespace.becomes(Namespace), resource.project, resource]) - "### [#{resource.to_reference} #{resource.title}](#{url})" + case resource + when Issue + helper.namespace_project_issue_url(resource.project.namespace.becomes(Namespace), resource.project, resource) + when MergeRequest + helper.namespace_project_merge_request_url(resource.project.namespace.becomes(Namespace), resource.project, resource) + end + end end end end -- cgit v1.2.1 From 1b4fdb9893af28606b7594ee656438c7ef21e9d8 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 16 Nov 2016 18:28:38 +0100 Subject: Rename from service, and move to lib/gitlab --- lib/gitlab/chat_commands/base_command.rb | 55 +++++++++++++++++++++++ lib/gitlab/chat_commands/command.rb | 49 ++++++++++++++++++++ lib/gitlab/chat_commands/issue_command.rb | 17 +++++++ lib/gitlab/chat_commands/issue_create.rb | 16 +++++++ lib/gitlab/chat_commands/issue_search.rb | 17 +++++++ lib/gitlab/chat_commands/issue_show.rb | 17 +++++++ lib/gitlab/chat_commands/merge_request_command.rb | 17 +++++++ lib/gitlab/chat_commands/merge_request_search.rb | 17 +++++++ lib/gitlab/chat_commands/merge_request_show.rb | 17 +++++++ lib/mattermost/presenter.rb | 7 ++- 10 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 lib/gitlab/chat_commands/base_command.rb create mode 100644 lib/gitlab/chat_commands/command.rb create mode 100644 lib/gitlab/chat_commands/issue_command.rb create mode 100644 lib/gitlab/chat_commands/issue_create.rb create mode 100644 lib/gitlab/chat_commands/issue_search.rb create mode 100644 lib/gitlab/chat_commands/issue_show.rb create mode 100644 lib/gitlab/chat_commands/merge_request_command.rb create mode 100644 lib/gitlab/chat_commands/merge_request_search.rb create mode 100644 lib/gitlab/chat_commands/merge_request_show.rb (limited to 'lib') diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb new file mode 100644 index 00000000000..13dc2a0f3e8 --- /dev/null +++ b/lib/gitlab/chat_commands/base_command.rb @@ -0,0 +1,55 @@ +module Gitlab + module ChatCommands + class BaseCommand + QUERY_LIMIT = 5 + + def self.match(_) + raise NotImplementedError + end + + def self.help_message + raise NotImplementedError + end + + def self.available?(_) + raise NotImplementedError + end + + def execute(_) + raise NotImplementedError + end + + def collection + raise NotImplementedError + end + + attr_accessor :project, :current_user, :params + + def initialize(project, user, params = {}) + @project, @current_user, @params = project, user, params.dup + end + + private + + def can?(object, action, subject) + Ability.allowed?(object, action, subject) + end + + def present(resource) + Mattermost::Presenter.present(resource) + end + + def find_by_iid(iid) + resource = collection.find_by(iid: iid) + + readable?(resource) ? resource : nil + end + + def search_results(query) + collection.search(query).limit(QUERY_LIMIT).select do |resource| + readable?(resource) + end + end + end + end +end diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb new file mode 100644 index 00000000000..06d09ab0e24 --- /dev/null +++ b/lib/gitlab/chat_commands/command.rb @@ -0,0 +1,49 @@ +module Gitlab + module ChatCommands + class Command < BaseCommand + COMMANDS = [ + Gitlab::ChatCommands::IssueShow, + Gitlab::ChatCommands::IssueSearch, + Gitlab::ChatCommands::IssueCreate, + + Gitlab::ChatCommands::MergeRequestShow, + Gitlab::ChatCommands::MergeRequestSearch, + ].freeze + + def execute + klass, match = fetch_klass + + return help(help_messages) unless klass.try(:available?, project) + + klass.new(project, current_user, params).execute(match) + end + + private + + def fetch_klass + match = nil + service = COMMANDS.find do |klass| + if klass.available?(project) + false + else + match = klass.match(command) + end + end + + [service, match] + end + + def help_messages + COMMANDS.map do |klass| + next unless klass.available?(project) + + klass.help_message + end.compact + end + + def command + params[:text] + end + end + end +end diff --git a/lib/gitlab/chat_commands/issue_command.rb b/lib/gitlab/chat_commands/issue_command.rb new file mode 100644 index 00000000000..2426b3714b7 --- /dev/null +++ b/lib/gitlab/chat_commands/issue_command.rb @@ -0,0 +1,17 @@ +module Gitlab + module ChatCommands + class IssueCommand < BaseCommand + def self.available?(project) + project.issues_enabled? && project.default_issues_tracker? + end + + def collection + project.issues + end + + def readable?(issue) + can?(current_user, :read_issue, issue) + end + end + end +end diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb new file mode 100644 index 00000000000..c424e845402 --- /dev/null +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -0,0 +1,16 @@ +module Gitlab + module ChatCommands + class IssueCreate < BaseCommand + def self.match(text) + /\Aissue\s+create\s+(?[^\n]*)\n*(?<description>.*)\z/.match(text) + end + + def execute(match) + title = match[:title] + description = match[:description] + + present Issues::CreateService.new(project, current_user, title: title, description: description).execute + end + end + end +end diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb new file mode 100644 index 00000000000..4169e2a7a88 --- /dev/null +++ b/lib/gitlab/chat_commands/issue_search.rb @@ -0,0 +1,17 @@ +module Gitlab + module ChatCommands + class IssueSearch < IssueCommand + def self.match(text) + /\Aissue\s+search\s+(?<query>.*)/.match(text) + end + + def self.help_message + "issue search <query>" + end + + def execute(match) + present search_results(match[:query]) + end + end + end +end diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb new file mode 100644 index 00000000000..e5530df31cc --- /dev/null +++ b/lib/gitlab/chat_commands/issue_show.rb @@ -0,0 +1,17 @@ +module Gitlab + module ChatCommands + class IssueShow < IssueCommand + def self.match(text) + /\Aissue\s+show\s+(?<iid>\d+)/.match(text) + end + + def self.help_message + "issue show <id>" + end + + def execute(match) + present find_by_iid(match[:iid]) + end + end + end +end diff --git a/lib/gitlab/chat_commands/merge_request_command.rb b/lib/gitlab/chat_commands/merge_request_command.rb new file mode 100644 index 00000000000..e0f69a49afd --- /dev/null +++ b/lib/gitlab/chat_commands/merge_request_command.rb @@ -0,0 +1,17 @@ +module Gitlab + module ChatCommands + class MergeRequestCommand < BaseCommand + def self.available?(project) + project.merge_requests_enabled? + end + + def collection + project.merge_requests + end + + def readable?(_) + can?(current_user, :read_merge_request, project) + end + end + end +end diff --git a/lib/gitlab/chat_commands/merge_request_search.rb b/lib/gitlab/chat_commands/merge_request_search.rb new file mode 100644 index 00000000000..caecb1a788e --- /dev/null +++ b/lib/gitlab/chat_commands/merge_request_search.rb @@ -0,0 +1,17 @@ +module Gitlab + module ChatCommands + class MergeRequestSearch < MergeRequestCommand + def self.match(text) + /\Amergerequest\s+search\s+(?<query>.*)/.match(text) + end + + def self.help_message + "mergerequest search <query>" + end + + def execute(match) + present search_results(match[:query]) + end + end + end +end diff --git a/lib/gitlab/chat_commands/merge_request_show.rb b/lib/gitlab/chat_commands/merge_request_show.rb new file mode 100644 index 00000000000..7ed5445e4c2 --- /dev/null +++ b/lib/gitlab/chat_commands/merge_request_show.rb @@ -0,0 +1,17 @@ +module Gitlab + module ChatCommands + class MergeRequestShow < MergeRequestCommand + def self.match(text) + /\Amergerequest\s+show\s+(?<iid>\d+)/.match(text) + end + + def self.help_message + "mergerequest show <id>" + end + + def execute(match) + present find_by_iid(match[:iid]) + end + end + end +end diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index e1502e3f9ba..3db55d6bd51 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -12,12 +12,11 @@ module Mattermost } end - # TODO figure out how I know which are available or not - def help_message(commands) + def help(messages) messages = ["Available commands:"] - commands.each do |sub_command, attrs| - messages << "\t#{COMMAND_PREFIX} #{attrs[:help_message]}" + messages.each do |messsage| + messages << "- #{message}" end { -- cgit v1.2.1 From d4def9cbcd664b7067e7f9f4ea8be54463bd1d50 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 17 Nov 2016 12:06:45 +0100 Subject: Incorporate feedback, improve presenter class [ci skip] --- lib/api/services.rb | 8 ++- lib/gitlab/chat_commands/base_command.rb | 4 ++ lib/gitlab/chat_commands/command.rb | 22 +++--- lib/gitlab/chat_commands/issue_create.rb | 2 + lib/gitlab/chat_commands/issue_search.rb | 2 +- lib/gitlab/chat_commands/merge_request_command.rb | 4 +- lib/gitlab/chat_commands/merge_request_search.rb | 2 +- lib/mattermost/presenter.rb | 81 ++++++++++++----------- 8 files changed, 70 insertions(+), 55 deletions(-) (limited to 'lib') diff --git a/lib/api/services.rb b/lib/api/services.rb index b4b3bb6e41a..094fca49c28 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -61,6 +61,10 @@ module API end resource :projects do + + desc 'Trigger a slash command' do + detail 'Added in GitLab 8.13' + end post ':id/services/:service_slug/trigger' do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) @@ -71,9 +75,7 @@ module API service = project.public_send(service_method) - result = if service.try(:active?) && service.respond_to?(:trigger) - service.trigger(params) - end + result = service.try(:active?) && service.try(:trigger, params) if result present result, status: result[:status] || 200 diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index 13dc2a0f3e8..b5d58af3588 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -38,6 +38,10 @@ module Gitlab def present(resource) Mattermost::Presenter.present(resource) end + + def help(messages) + Mattermost::Presenter.help(messages) + end def find_by_iid(iid) resource = collection.find_by(iid: iid) diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 06d09ab0e24..f1490c045c3 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -13,7 +13,7 @@ module Gitlab def execute klass, match = fetch_klass - return help(help_messages) unless klass.try(:available?, project) + return help(help_messages, params[:command]) unless klass.try(:available?, project) klass.new(project, current_user, params).execute(match) end @@ -22,23 +22,23 @@ module Gitlab def fetch_klass match = nil - service = COMMANDS.find do |klass| - if klass.available?(project) - false - else - match = klass.match(command) - end + service = available_commands.find do |klass| + match = klass.match(command) end [service, match] end def help_messages - COMMANDS.map do |klass| - next unless klass.available?(project) - + available_commands.map do |klass| klass.help_message - end.compact + end + end + + def available_commands + COMMANDS.select do |klass| + klass.available?(project) + end end def command diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index c424e845402..b5cf85b58f1 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -6,6 +6,8 @@ module Gitlab end def execute(match) + present nil unless can?(current_user, :create_issue, project) + title = match[:title] description = match[:description] diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb index 4169e2a7a88..f64f3ad2680 100644 --- a/lib/gitlab/chat_commands/issue_search.rb +++ b/lib/gitlab/chat_commands/issue_search.rb @@ -2,7 +2,7 @@ module Gitlab module ChatCommands class IssueSearch < IssueCommand def self.match(text) - /\Aissue\s+search\s+(?<query>.*)/.match(text) + /\Aissue\s+search\s+(?<query>.*)\s*/.match(text) end def self.help_message diff --git a/lib/gitlab/chat_commands/merge_request_command.rb b/lib/gitlab/chat_commands/merge_request_command.rb index e0f69a49afd..ad485483b8a 100644 --- a/lib/gitlab/chat_commands/merge_request_command.rb +++ b/lib/gitlab/chat_commands/merge_request_command.rb @@ -9,8 +9,8 @@ module Gitlab project.merge_requests end - def readable?(_) - can?(current_user, :read_merge_request, project) + def readable?(merge_request) + can?(current_user, :read_merge_request, merge_request) end end end diff --git a/lib/gitlab/chat_commands/merge_request_search.rb b/lib/gitlab/chat_commands/merge_request_search.rb index caecb1a788e..19a29546736 100644 --- a/lib/gitlab/chat_commands/merge_request_search.rb +++ b/lib/gitlab/chat_commands/merge_request_search.rb @@ -2,7 +2,7 @@ module Gitlab module ChatCommands class MergeRequestSearch < MergeRequestCommand def self.match(text) - /\Amergerequest\s+search\s+(?<query>.*)/.match(text) + /\Amergerequest\s+search\s+(?<query>.*)\s*/.match(text) end def self.help_message diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index 3db55d6bd51..0f2beb2cd6b 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -1,35 +1,24 @@ module Mattermost class Presenter class << self - COMMAND_PREFIX = '/gitlab'.freeze + def authorize_chat_name(url) + message = "Hi there! We've yet to get acquainted! Please [introduce yourself](#{url})!" - def authorize_chat_name(params) - url = ChatNames::RequestService.new(service, params).execute - - { - response_type: :ephemeral, - message: "You are not authorized. Click this [link](#{url}) to authorize." - } + ephemeral_response(message) end - def help(messages) - messages = ["Available commands:"] + def help(messages, command) + message = ["Available commands:"] messages.each do |messsage| - messages << "- #{message}" + message << "- #{command} #{message}" end - { - response_type: :ephemeral, - text: messages.join("\n") - } + ephemeral_response(messages.join("\n")) end def not_found - { - response_type: :ephemeral, - text: "404 not found! GitLab couldn't find what your were looking for! :boom:", - } + ephemeral_response("404 not found! GitLab couldn't find what your were looking for! :boom:") end def present(resource) @@ -51,38 +40,56 @@ module Mattermost private def single_resource(resource) - message = title(resource) + return error(resource) if resource.errors.any? + + message = "### #{title(resource)}" message << "\n\n#{resource.description}" if resource.description - { - response_type: :in_channel, - text: message - } + in_channel_response(message) end def multiple_resources(resources) message = "Multiple results were found:\n" - message << resources.map { |resource| " #{title(resource)}" }.join("\n") + message << resources.map { |resource| "- #{title(resource)}" }.join("\n") - { - response_type: :ephemeral, - text: message - } + ephemeral_response(message) + end + + def error(resource) + message = "The action was not succesfull because:\n" + message << resource.errors.messages.map { |message| "- #{message}" }.join("\n") + + ephemeral_response(resource.errors.messages.join("\n") end def title(resource) - "### [#{resource.to_reference} #{resource.title}](#{url(resource)})" + "[#{resource.to_reference} #{resource.title}](#{url(resource)})" end def url(resource) - helper = Rails.application.routes.url_helpers + polymorphic_url( + [ + resource.project.namespace.becomes(Namespace), + resource.project, + resource + ], + id: resource_id, + routing_type: :url + ) + end - case resource - when Issue - helper.namespace_project_issue_url(resource.project.namespace.becomes(Namespace), resource.project, resource) - when MergeRequest - helper.namespace_project_merge_request_url(resource.project.namespace.becomes(Namespace), resource.project, resource) - end + def ephemeral_response(message) + { + response_type: :ephemeral, + text: message + } + end + + def in_channel_response(message) + { + response_type: :in_channel, + text: message + } end end end -- cgit v1.2.1 From 6737ada0c8d980ed1bd8f425e885fa1b89930616 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 17 Nov 2016 12:57:27 +0100 Subject: Remove some commands for now --- lib/gitlab/chat_commands/command.rb | 4 ---- lib/gitlab/chat_commands/issue_create.rb | 6 +++++- lib/gitlab/chat_commands/issue_search.rb | 17 ----------------- lib/gitlab/chat_commands/merge_request_command.rb | 17 ----------------- lib/gitlab/chat_commands/merge_request_search.rb | 17 ----------------- lib/gitlab/chat_commands/merge_request_show.rb | 17 ----------------- lib/mattermost/presenter.rb | 12 ++++++------ 7 files changed, 11 insertions(+), 79 deletions(-) delete mode 100644 lib/gitlab/chat_commands/issue_search.rb delete mode 100644 lib/gitlab/chat_commands/merge_request_command.rb delete mode 100644 lib/gitlab/chat_commands/merge_request_search.rb delete mode 100644 lib/gitlab/chat_commands/merge_request_show.rb (limited to 'lib') diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index f1490c045c3..0ed51d9b8fc 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -3,11 +3,7 @@ module Gitlab class Command < BaseCommand COMMANDS = [ Gitlab::ChatCommands::IssueShow, - Gitlab::ChatCommands::IssueSearch, Gitlab::ChatCommands::IssueCreate, - - Gitlab::ChatCommands::MergeRequestShow, - Gitlab::ChatCommands::MergeRequestSearch, ].freeze def execute diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index b5cf85b58f1..0e2b4c0e9cd 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -1,10 +1,14 @@ module Gitlab module ChatCommands - class IssueCreate < BaseCommand + class IssueCreate < IssueCommand def self.match(text) /\Aissue\s+create\s+(?<title>[^\n]*)\n*(?<description>.*)\z/.match(text) end + def self.help_message + 'issue create <title>\n<description>' + end + def execute(match) present nil unless can?(current_user, :create_issue, project) diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb deleted file mode 100644 index f64f3ad2680..00000000000 --- a/lib/gitlab/chat_commands/issue_search.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module ChatCommands - class IssueSearch < IssueCommand - def self.match(text) - /\Aissue\s+search\s+(?<query>.*)\s*/.match(text) - end - - def self.help_message - "issue search <query>" - end - - def execute(match) - present search_results(match[:query]) - end - end - end -end diff --git a/lib/gitlab/chat_commands/merge_request_command.rb b/lib/gitlab/chat_commands/merge_request_command.rb deleted file mode 100644 index ad485483b8a..00000000000 --- a/lib/gitlab/chat_commands/merge_request_command.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module ChatCommands - class MergeRequestCommand < BaseCommand - def self.available?(project) - project.merge_requests_enabled? - end - - def collection - project.merge_requests - end - - def readable?(merge_request) - can?(current_user, :read_merge_request, merge_request) - end - end - end -end diff --git a/lib/gitlab/chat_commands/merge_request_search.rb b/lib/gitlab/chat_commands/merge_request_search.rb deleted file mode 100644 index 19a29546736..00000000000 --- a/lib/gitlab/chat_commands/merge_request_search.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module ChatCommands - class MergeRequestSearch < MergeRequestCommand - def self.match(text) - /\Amergerequest\s+search\s+(?<query>.*)\s*/.match(text) - end - - def self.help_message - "mergerequest search <query>" - end - - def execute(match) - present search_results(match[:query]) - end - end - end -end diff --git a/lib/gitlab/chat_commands/merge_request_show.rb b/lib/gitlab/chat_commands/merge_request_show.rb deleted file mode 100644 index 7ed5445e4c2..00000000000 --- a/lib/gitlab/chat_commands/merge_request_show.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module ChatCommands - class MergeRequestShow < MergeRequestCommand - def self.match(text) - /\Amergerequest\s+show\s+(?<iid>\d+)/.match(text) - end - - def self.help_message - "mergerequest show <id>" - end - - def execute(match) - present find_by_iid(match[:iid]) - end - end - end -end diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index 0f2beb2cd6b..b3d6c025109 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -1,6 +1,8 @@ module Mattermost class Presenter class << self + include Rails.application.routes.url_helpers + def authorize_chat_name(url) message = "Hi there! We've yet to get acquainted! Please [introduce yourself](#{url})!" @@ -59,7 +61,7 @@ module Mattermost message = "The action was not succesfull because:\n" message << resource.errors.messages.map { |message| "- #{message}" }.join("\n") - ephemeral_response(resource.errors.messages.join("\n") + ephemeral_response(resource.errors.messages.join("\n")) end def title(resource) @@ -67,14 +69,12 @@ module Mattermost end def url(resource) - polymorphic_url( + url_for( [ resource.project.namespace.becomes(Namespace), resource.project, - resource - ], - id: resource_id, - routing_type: :url + resource + ] ) end -- cgit v1.2.1 From 1607efa40081702488e22e560db2c1e30cd80093 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 17 Nov 2016 15:30:04 +0100 Subject: Add tests for increased converage --- lib/gitlab/chat_commands/base_command.rb | 8 -------- lib/gitlab/chat_commands/command.rb | 16 +++++++++++++--- lib/gitlab/chat_commands/issue_create.rb | 6 +++--- lib/gitlab/chat_commands/issue_show.rb | 2 +- lib/mattermost/presenter.rb | 5 +++-- 5 files changed, 20 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index b5d58af3588..81b15bd1f7a 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -35,14 +35,6 @@ module Gitlab Ability.allowed?(object, action, subject) end - def present(resource) - Mattermost::Presenter.present(resource) - end - - def help(messages) - Mattermost::Presenter.help(messages) - end - def find_by_iid(iid) resource = collection.find_by(iid: iid) diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 0ed51d9b8fc..43144975901 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -9,9 +9,11 @@ module Gitlab def execute klass, match = fetch_klass - return help(help_messages, params[:command]) unless klass.try(:available?, project) - - klass.new(project, current_user, params).execute(match) + if klass + present klass.new(project, current_user, params).execute(match) + else + help(help_messages) + end end private @@ -40,6 +42,14 @@ module Gitlab def command params[:text] end + + def present(resource) + Mattermost::Presenter.present(resource) + end + + def help(messages) + Mattermost::Presenter.help(messages, params[:command]) + end end end end diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index 0e2b4c0e9cd..1e311e09771 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -1,6 +1,6 @@ module Gitlab module ChatCommands - class IssueCreate < IssueCommand + class IssueCreate < IssueCommand def self.match(text) /\Aissue\s+create\s+(?<title>[^\n]*)\n*(?<description>.*)\z/.match(text) end @@ -10,12 +10,12 @@ module Gitlab end def execute(match) - present nil unless can?(current_user, :create_issue, project) + return nil unless can?(current_user, :create_issue, project) title = match[:title] description = match[:description] - present Issues::CreateService.new(project, current_user, title: title, description: description).execute + Issues::CreateService.new(project, current_user, title: title, description: description).execute end end end diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb index e5530df31cc..f5bceb038e5 100644 --- a/lib/gitlab/chat_commands/issue_show.rb +++ b/lib/gitlab/chat_commands/issue_show.rb @@ -10,7 +10,7 @@ module Gitlab end def execute(match) - present find_by_iid(match[:iid]) + find_by_iid(match[:iid]) end end end diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index b3d6c025109..84b7b8edd9e 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -4,19 +4,20 @@ module Mattermost include Rails.application.routes.url_helpers def authorize_chat_name(url) - message = "Hi there! We've yet to get acquainted! Please [introduce yourself](#{url})!" + message = "Hi there! We've yet to get acquainted! Please introduce yourself by [connection your GitLab profile](#{url})!" ephemeral_response(message) end def help(messages, command) + return ephemeral_response("No commands configured") unless messages.count > 1 message = ["Available commands:"] messages.each do |messsage| message << "- #{command} #{message}" end - ephemeral_response(messages.join("\n")) + ephemeral_response(message.join("\n")) end def not_found -- cgit v1.2.1 From 166ee0965bacc20e2ad1187321654499a9b0f825 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Thu, 17 Nov 2016 21:27:12 +0100 Subject: More refactoring, push present to base command --- lib/api/services.rb | 4 +-- lib/gitlab/chat_commands/base_command.rb | 18 +++++------ lib/gitlab/chat_commands/command.rb | 30 ++++++++++------- lib/gitlab/chat_commands/issue_command.rb | 2 +- lib/gitlab/chat_commands/issue_create.rb | 6 ++-- lib/mattermost/presenter.rb | 53 ++++++++++++++++++++----------- 6 files changed, 67 insertions(+), 46 deletions(-) (limited to 'lib') diff --git a/lib/api/services.rb b/lib/api/services.rb index 094fca49c28..b0a94508d10 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -61,7 +61,6 @@ module API end resource :projects do - desc 'Trigger a slash command' do detail 'Added in GitLab 8.13' end @@ -78,7 +77,8 @@ module API result = service.try(:active?) && service.try(:trigger, params) if result - present result, status: result[:status] || 200 + status result[:status] || 200 + present result else not_found!('Service') end diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index 81b15bd1f7a..f84aca5365d 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -15,6 +15,14 @@ module Gitlab raise NotImplementedError end + def self.allowed?(_, _) + true + end + + def self.can?(object, action, subject) + Ability.allowed?(object, action, subject) + end + def execute(_) raise NotImplementedError end @@ -31,21 +39,11 @@ module Gitlab private - def can?(object, action, subject) - Ability.allowed?(object, action, subject) - end - def find_by_iid(iid) resource = collection.find_by(iid: iid) readable?(resource) ? resource : nil end - - def search_results(query) - collection.search(query).limit(QUERY_LIMIT).select do |resource| - readable?(resource) - end - end end end end diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 43144975901..5f131703d40 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -7,10 +7,14 @@ module Gitlab ].freeze def execute - klass, match = fetch_klass - - if klass - present klass.new(project, current_user, params).execute(match) + command, match = match_command + + if command + if command.allowed?(project, current_user) + present command.new(project, current_user, params).execute(match) + else + access_denied + end else help(help_messages) end @@ -18,7 +22,7 @@ module Gitlab private - def fetch_klass + def match_command match = nil service = available_commands.find do |klass| match = klass.match(command) @@ -28,9 +32,7 @@ module Gitlab end def help_messages - available_commands.map do |klass| - klass.help_message - end + available_commands.map(&:help_message) end def available_commands @@ -43,13 +45,17 @@ module Gitlab params[:text] end - def present(resource) - Mattermost::Presenter.present(resource) - end - def help(messages) Mattermost::Presenter.help(messages, params[:command]) end + + def access_denied + Mattermost::Presenter.access_denied + end + + def present(resource) + Mattermost::Presenter.present(resource) + end end end end diff --git a/lib/gitlab/chat_commands/issue_command.rb b/lib/gitlab/chat_commands/issue_command.rb index 2426b3714b7..f1bc36239d5 100644 --- a/lib/gitlab/chat_commands/issue_command.rb +++ b/lib/gitlab/chat_commands/issue_command.rb @@ -10,7 +10,7 @@ module Gitlab end def readable?(issue) - can?(current_user, :read_issue, issue) + self.class.can?(current_user, :read_issue, issue) end end end diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index 1e311e09771..98338ebfa27 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -9,9 +9,11 @@ module Gitlab 'issue create <title>\n<description>' end - def execute(match) - return nil unless can?(current_user, :create_issue, project) + def self.allowed?(project, user) + can?(user, :create_issue, project) + end + def execute(match) title = match[:title] description = match[:description] diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index 84b7b8edd9e..7722022c658 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -4,24 +4,19 @@ module Mattermost include Rails.application.routes.url_helpers def authorize_chat_name(url) - message = "Hi there! We've yet to get acquainted! Please introduce yourself by [connection your GitLab profile](#{url})!" + message = ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." ephemeral_response(message) end - def help(messages, command) - return ephemeral_response("No commands configured") unless messages.count > 1 - message = ["Available commands:"] + def help(commands, trigger) + if commands.count == 0 + ephemeral_response("No commands configured") unless messages.count > 1 + else + message = header_with_list("Available commands", commands) - messages.each do |messsage| - message << "- #{command} #{message}" + ephemeral_response(message) end - - ephemeral_response(message.join("\n")) - end - - def not_found - ephemeral_response("404 not found! GitLab couldn't find what your were looking for! :boom:") end def present(resource) @@ -40,8 +35,16 @@ module Mattermost single_resource(resource) end + def access_denied + ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") + end + private + def not_found + ephemeral_response("404 not found! GitLab couldn't find what your were looking for! :boom:") + end + def single_resource(resource) return error(resource) if resource.errors.any? @@ -52,23 +55,33 @@ module Mattermost end def multiple_resources(resources) - message = "Multiple results were found:\n" - message << resources.map { |resource| "- #{title(resource)}" }.join("\n") + resources.map! { |resource| title(resource) } + + message = header_with_list("Multiple results were found:", resources) ephemeral_response(message) end def error(resource) - message = "The action was not succesfull because:\n" - message << resource.errors.messages.map { |message| "- #{message}" }.join("\n") + message = header_with_list("The action was not succesful, because:", resource.errors.messages) - ephemeral_response(resource.errors.messages.join("\n")) + ephemeral_response(message) end def title(resource) "[#{resource.to_reference} #{resource.title}](#{url(resource)})" end + def header_with_list(header, items) + message = [header] + + items.each do |item| + message << "- #{item}" + end + + message.join("\n") + end + def url(resource) url_for( [ @@ -82,14 +95,16 @@ module Mattermost def ephemeral_response(message) { response_type: :ephemeral, - text: message + text: message, + status: 200 } end def in_channel_response(message) { response_type: :in_channel, - text: message + text: message, + status: 200 } end end -- cgit v1.2.1 From 3228798b37cc98f6c7b92371cbf6d1800d1c280b Mon Sep 17 00:00:00 2001 From: Andrew Smith <espadav8@gmail.com> Date: Wed, 2 Nov 2016 21:38:43 +1000 Subject: Refactor github import to reduce number of API calls Ref #24073 --- lib/gitlab/github_import/importer.rb | 50 ++++++++++++++++------------- lib/gitlab/github_import/issue_formatter.rb | 4 +-- 2 files changed, 29 insertions(+), 25 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 90cf38a8513..281b65bdeba 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -20,10 +20,18 @@ module Gitlab end def execute + # The ordering of importing is important here due to the way GitHub structures their data + # 1. Labels are required by other items while not having a dependency on anything else + # so need to be first + # 2. Pull requests must come before issues. Every pull request is also an issue but not + # all issues are pull requests. Only the issue entity has labels defined in GitHub. GitLab + # doesn't structure data like this so we need to make sure that we've created the MRs + # before we attempt to add the labels defined in the GitHub issue for the related, already + # imported, pull request import_labels import_milestones - import_issues import_pull_requests + import_issues import_comments(:issues) import_comments(:pull_requests) import_wiki @@ -79,13 +87,17 @@ module Gitlab issues.each do |raw| gh_issue = IssueFormatter.new(project, raw) - if gh_issue.valid? - begin - issue = gh_issue.create! - apply_labels(issue, raw) - rescue => e - errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } - end + begin + issuable = + if gh_issue.pull_request? + MergeRequest.find_by_iid(gh_issue.number) + else + gh_issue.create! + end + + apply_labels(issuable, raw) + rescue => e + errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } end end end @@ -101,8 +113,7 @@ module Gitlab restore_source_branch(pull_request) unless pull_request.source_branch_exists? restore_target_branch(pull_request) unless pull_request.target_branch_exists? - merge_request = pull_request.create! - apply_labels(merge_request, raw) + pull_request.create! rescue => e errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message } ensure @@ -133,21 +144,14 @@ module Gitlab remove_branch(pull_request.target_branch_name) unless pull_request.target_branch_exists? end - def apply_labels(issuable, raw_issuable) - # GH returns labels for issues but not for pull requests! - labels = if issuable.is_a?(MergeRequest) - client.labels_for_issue(repo, raw_issuable.number) - else - raw_issuable.labels - end + def apply_labels(issuable, raw) + return unless raw.labels.count > 0 - if labels.count > 0 - label_ids = labels - .map { |attrs| @labels[attrs.name] } - .compact + label_ids = raw.labels + .map { |attrs| @labels[attrs.name] } + .compact - issuable.update_attribute(:label_ids, label_ids) - end + issuable.update_attribute(:label_ids, label_ids) end def import_comments(issuable_type) diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb index 8c32ac59fc5..887690bcc7c 100644 --- a/lib/gitlab/github_import/issue_formatter.rb +++ b/lib/gitlab/github_import/issue_formatter.rb @@ -32,8 +32,8 @@ module Gitlab raw_data.number end - def valid? - raw_data.pull_request.nil? + def pull_request? + raw_data.pull_request.present? end private -- cgit v1.2.1 From 78b6d6624be3a6d59710bf766e7253c2e6787f5a Mon Sep 17 00:00:00 2001 From: Timothy Andrew <mail@timothyandrew.net> Date: Wed, 16 Nov 2016 17:54:43 +0530 Subject: Allow registering users where the username contains dots (.). Javascript does not support the negative lookbehind assertion (?<!) used in the Ruby regex (to disallow usernames ending in `.git` or `.atom`. Getting the client side code to fully support this format is non-trivial, since we'd either have to heavily complicate the regex used, or modify the frontend code to support more complex validation schemes (it currently uses HTML5 validations). The pragmatic choice is to create a `Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE` regex to serve as a Javascript-compatible version of `NAMESPACE_REGEX_STR`. The client-side code will not display an error for usernames ending in `.git` and `.atom`, but these will be caught by the server-side validation. --- lib/gitlab/regex.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 155ca47e04c..47ea8b7e82e 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -2,7 +2,14 @@ module Gitlab module Regex extend self - NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(?<!\.git|\.atom)'.freeze + # The namespace regex is used in Javascript to validate usernames in the "Register" form. However, Javascript + # does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`. + # Since this is a non-trivial problem to solve in Javascript (heavily complicate the regex, modify view code to + # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_SIMPLE` serves as a Javascript-compatible version of + # `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation + # will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation. + NAMESPACE_REGEX_STR_SIMPLE = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze + NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR_SIMPLE})(?<!\.git|\.atom)".freeze def namespace_regex @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze -- cgit v1.2.1 From 0d04724fa1cd670124b8ad9a3860bfa476c50f99 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Fri, 18 Nov 2016 10:00:40 +0100 Subject: More coverage on service level --- lib/api/helpers.rb | 9 +++++++++ lib/api/services.rb | 7 +------ lib/mattermost/presenter.rb | 6 +++++- 3 files changed, 15 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 84cc9200d1b..d6526ec4fdc 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -90,6 +90,15 @@ module API @project_service || not_found!("Service") end + def service_by_slug(project, slug) + underscored_service = slug.underscore + + not_found!('Service') unless Service.available_services_names.include?(underscored_service) + service_method = "#{underscored_service}_service" + + service = project.public_send(service_method) + end + def service_attributes @service_attributes ||= project_service.fields.inject([]) do |arr, hash| arr << hash[:name].to_sym diff --git a/lib/api/services.rb b/lib/api/services.rb index b0a94508d10..163187d450d 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -67,12 +67,7 @@ module API post ':id/services/:service_slug/trigger' do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) - underscored_service = params[:service_slug].underscore - - not_found!('Service') unless Service.available_services_names.include?(underscored_service) - service_method = "#{underscored_service}_service" - - service = project.public_send(service_method) + service = service_by_slug(project, params[:service_slug]) result = service.try(:active?) && service.try(:trigger, params) diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index 7722022c658..d7455d39bce 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -4,7 +4,11 @@ module Mattermost include Rails.application.routes.url_helpers def authorize_chat_name(url) - message = ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." + message = if url + ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." + else + ":sweat_smile: Couldn't identify you, nor can I autorize you!" + end ephemeral_response(message) end -- cgit v1.2.1 From cd222357ade5f47f8d52f22dea66965c580a5843 Mon Sep 17 00:00:00 2001 From: James Lopez <james@jameslopez.es> Date: Fri, 18 Nov 2016 10:13:42 +0100 Subject: refactor events --- lib/gitlab/cycle_analytics/test_event.rb | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/test_event.rb b/lib/gitlab/cycle_analytics/test_event.rb index 00404d7f6b0..d553d0b5aec 100644 --- a/lib/gitlab/cycle_analytics/test_event.rb +++ b/lib/gitlab/cycle_analytics/test_event.rb @@ -1,26 +1,12 @@ module Gitlab module CycleAnalytics - class TestEvent < BaseEvent + class TestEvent < StagingEvent def initialize(*args) + super(*args) + @stage = :test @start_time_attrs = mr_metrics_table[:latest_build_started_at] @end_time_attrs = mr_metrics_table[:latest_build_finished_at] - @projections = [build_table[:id]] - @order = build_table[:created_at] - - super(*args) - end - - def custom_query(base_query) - base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) - end - - private - - def serialize(event) - build = ::Ci::Build.find(event['id']) - - AnalyticsBuildSerializer.new.represent(build).as_json end end end -- cgit v1.2.1 From e971009923b59f1d93a085eb0993e6ebabe03c19 Mon Sep 17 00:00:00 2001 From: Robert Schilling <rschilling@student.tugraz.at> Date: Thu, 17 Nov 2016 16:05:57 +0100 Subject: Grapify the repository API --- lib/api/repositories.rb | 97 ++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 58 deletions(-) (limited to 'lib') diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 0bb2f74809a..c287ee34a68 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -1,11 +1,13 @@ require 'mime/types' module API - # Projects API class Repositories < Grape::API before { authenticate! } before { authorize! :download_code, user_project } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do helpers do def handle_project_member_errors(errors) @@ -16,43 +18,35 @@ module API end end - # Get a project repository tree - # - # Parameters: - # id (required) - The ID of a project - # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used - # recursive (optional) - Used to get a recursive tree - # Example Request: - # GET /projects/:id/repository/tree + desc 'Get a project repository tree' do + success Entities::RepoTreeObject + end + params do + optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used' + optional :path, type: String, desc: 'The path of the tree' + optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree' + end get ':id/repository/tree' do ref = params[:ref_name] || user_project.try(:default_branch) || 'master' path = params[:path] || nil - recursive = to_boolean(params[:recursive]) commit = user_project.commit(ref) not_found!('Tree') unless commit - tree = user_project.repository.tree(commit.id, path, recursive: recursive) + tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive]) present tree.sorted_entries, with: Entities::RepoTreeObject end - # Get a raw file contents - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit or branch name - # filepath (required) - The path to the file to display - # Example Request: - # GET /projects/:id/repository/blobs/:sha + desc 'Get a raw file contents' + params do + requires :sha, type: String, desc: 'The commit, branch name, or tag name' + requires :filepath, type: String, desc: 'The path to the file to display' + end get [ ":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob" ] do - required_attributes! [:filepath] - - ref = params[:sha] - repo = user_project.repository - commit = repo.commit(ref) + commit = repo.commit(params[:sha]) not_found! "Commit" unless commit blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath]) @@ -61,20 +55,15 @@ module API send_git_blob repo, blob end - # Get a raw blob contents by blob sha - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The blob's sha - # Example Request: - # GET /projects/:id/repository/raw_blobs/:sha + desc 'Get a raw blob contents by blob sha' + params do + requires :sha, type: String, desc: 'The commit, branch name, or tag name' + end get ':id/repository/raw_blobs/:sha' do - ref = params[:sha] - repo = user_project.repository begin - blob = Gitlab::Git::Blob.raw(repo, ref) + blob = Gitlab::Git::Blob.raw(repo, params[:sha]) rescue not_found! 'Blob' end @@ -84,15 +73,12 @@ module API send_git_blob repo, blob end - # Get a an archive of the repository - # - # Parameters: - # id (required) - The ID of a project - # sha (optional) - the commit sha to download defaults to the tip of the default branch - # Example Request: - # GET /projects/:id/repository/archive - get ':id/repository/archive', - requirements: { format: Gitlab::Regex.archive_formats_regex } do + desc 'Get an archive of the repository' + params do + optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded' + optional :format, type: String, desc: 'The archive format' + end + get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do authorize! :download_code, user_project begin @@ -102,27 +88,22 @@ module API end end - # Compare two branches, tags or commits - # - # Parameters: - # id (required) - The ID of a project - # from (required) - the commit sha or branch name - # to (required) - the commit sha or branch name - # Example Request: - # GET /projects/:id/repository/compare?from=master&to=feature + desc 'Compare two branches, tags, or commits' do + success Entities::Compare + end + params do + requires :from, type: String, desc: 'The commit, branch name, or tag name to start comparison' + requires :to, type: String, desc: 'The commit, branch name, or tag name to stop comparison' + end get ':id/repository/compare' do authorize! :download_code, user_project - required_attributes! [:from, :to] compare = Gitlab::Git::Compare.new(user_project.repository.raw_repository, params[:from], params[:to]) present compare, with: Entities::Compare end - # Get repository contributors - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/contributors + desc 'Get repository contributors' do + success Entities::Contributor + end get ':id/repository/contributors' do authorize! :download_code, user_project -- cgit v1.2.1 From f749fb7fe0574d07eeb38561b9af62754e518281 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Fri, 18 Nov 2016 11:38:54 +0100 Subject: Improve style, add more tests --- lib/api/helpers.rb | 13 ++----------- lib/api/services.rb | 6 ++++-- lib/gitlab/chat_commands/base_command.rb | 6 +++--- lib/mattermost/presenter.rb | 6 +++--- 4 files changed, 12 insertions(+), 19 deletions(-) (limited to 'lib') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index d6526ec4fdc..2c593dbb4ea 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -85,20 +85,11 @@ module API end end - def project_service - @project_service ||= user_project.find_or_initialize_service(params[:service_slug].underscore) + def project_service(project = user_project) + @project_service ||= project.find_or_initialize_service(params[:service_slug].underscore) @project_service || not_found!("Service") end - def service_by_slug(project, slug) - underscored_service = slug.underscore - - not_found!('Service') unless Service.available_services_names.include?(underscored_service) - service_method = "#{underscored_service}_service" - - service = project.public_send(service_method) - end - def service_attributes @service_attributes ||= project_service.fields.inject([]) do |arr, hash| arr << hash[:name].to_sym diff --git a/lib/api/services.rb b/lib/api/services.rb index 163187d450d..e3c6a998631 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -67,7 +67,9 @@ module API post ':id/services/:service_slug/trigger' do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) - service = service_by_slug(project, params[:service_slug]) + not_found! unless project + + service = project_service(project) result = service.try(:active?) && service.try(:trigger, params) @@ -75,7 +77,7 @@ module API status result[:status] || 200 present result else - not_found!('Service') + not_found! end end end diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index f84aca5365d..e59d69b72b9 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -3,7 +3,7 @@ module Gitlab class BaseCommand QUERY_LIMIT = 5 - def self.match(_) + def self.match(_text) raise NotImplementedError end @@ -11,11 +11,11 @@ module Gitlab raise NotImplementedError end - def self.available?(_) + def self.available?(_project) raise NotImplementedError end - def self.allowed?(_, _) + def self.allowed?(_user, _ability) true end diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index d7455d39bce..b4e7358770f 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -1,7 +1,7 @@ module Mattermost class Presenter class << self - include Rails.application.routes.url_helpers + include Gitlab::Routing.url_helpers def authorize_chat_name(url) message = if url @@ -14,7 +14,7 @@ module Mattermost end def help(commands, trigger) - if commands.count == 0 + if commands.empty? ephemeral_response("No commands configured") unless messages.count > 1 else message = header_with_list("Available commands", commands) @@ -50,7 +50,7 @@ module Mattermost end def single_resource(resource) - return error(resource) if resource.errors.any? + return error(resource) if resource.errors.any? || !resource.persisted? message = "### #{title(resource)}" message << "\n\n#{resource.description}" if resource.description -- cgit v1.2.1 From cb353d655bd8802c14a1e12e0fe78a1f7cc2e4ed Mon Sep 17 00:00:00 2001 From: James Lopez <james@jameslopez.es> Date: Fri, 18 Nov 2016 12:05:29 +0100 Subject: added new build updater, specs and refactored allowed_ids --- lib/gitlab/cycle_analytics/author_updater.rb | 24 +++-------------- lib/gitlab/cycle_analytics/build_updater.rb | 9 +++++++ lib/gitlab/cycle_analytics/code_event.rb | 6 ++--- lib/gitlab/cycle_analytics/issue_allowed.rb | 9 +++++++ lib/gitlab/cycle_analytics/issue_event.rb | 6 ++--- .../cycle_analytics/merge_request_allowed.rb | 9 +++++++ lib/gitlab/cycle_analytics/production_event.rb | 6 ++--- lib/gitlab/cycle_analytics/review_event.rb | 6 ++--- lib/gitlab/cycle_analytics/staging_event.rb | 10 +++++--- lib/gitlab/cycle_analytics/updater.rb | 30 ++++++++++++++++++++++ 10 files changed, 75 insertions(+), 40 deletions(-) create mode 100644 lib/gitlab/cycle_analytics/build_updater.rb create mode 100644 lib/gitlab/cycle_analytics/issue_allowed.rb create mode 100644 lib/gitlab/cycle_analytics/merge_request_allowed.rb create mode 100644 lib/gitlab/cycle_analytics/updater.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/author_updater.rb b/lib/gitlab/cycle_analytics/author_updater.rb index ab18f292bc3..87d26701d3a 100644 --- a/lib/gitlab/cycle_analytics/author_updater.rb +++ b/lib/gitlab/cycle_analytics/author_updater.rb @@ -1,26 +1,8 @@ module Gitlab module CycleAnalytics - class AuthorUpdater - def self.update!(*args) - new(*args).update! - end - - def initialize(event_result) - @event_result = event_result - end - - def update! - @event_result.each do |event| - event['author'] = users[event.delete('author_id').to_i].first - end - end - - def user_ids - @event_result.map { |event| event['author_id'] } - end - - def users - @users ||= User.find(user_ids).group_by { |user| user['id'] } + class AuthorUpdater < Updater + def self.update!(event_result) + new(event_result, User, :author).update! end end end diff --git a/lib/gitlab/cycle_analytics/build_updater.rb b/lib/gitlab/cycle_analytics/build_updater.rb new file mode 100644 index 00000000000..c1190d9e7a9 --- /dev/null +++ b/lib/gitlab/cycle_analytics/build_updater.rb @@ -0,0 +1,9 @@ +module Gitlab + module CycleAnalytics + class BuildUpdater < Updater + def self.update!(event_result) + new(event_result, ::Ci::Build, :build, 'id').update! + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/code_event.rb b/lib/gitlab/cycle_analytics/code_event.rb index 02a3a44d544..2afdf0b8518 100644 --- a/lib/gitlab/cycle_analytics/code_event.rb +++ b/lib/gitlab/cycle_analytics/code_event.rb @@ -1,6 +1,8 @@ module Gitlab module CycleAnalytics class CodeEvent < BaseEvent + include MergeRequestAllowed + def initialize(*args) @stage = :code @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at] @@ -21,10 +23,6 @@ module Gitlab def serialize(event) AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json end - - def allowed_ids - @allowed_ids ||= MergeRequestsFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) - end end end end diff --git a/lib/gitlab/cycle_analytics/issue_allowed.rb b/lib/gitlab/cycle_analytics/issue_allowed.rb new file mode 100644 index 00000000000..a7652a70641 --- /dev/null +++ b/lib/gitlab/cycle_analytics/issue_allowed.rb @@ -0,0 +1,9 @@ +module Gitlab + module CycleAnalytics + module IssueAllowed + def allowed_ids + @allowed_ids ||= IssuesFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/issue_event.rb b/lib/gitlab/cycle_analytics/issue_event.rb index 36a990d6222..705b7e5ce24 100644 --- a/lib/gitlab/cycle_analytics/issue_event.rb +++ b/lib/gitlab/cycle_analytics/issue_event.rb @@ -1,6 +1,8 @@ module Gitlab module CycleAnalytics class IssueEvent < BaseEvent + include IssueAllowed + def initialize(*args) @stage = :issue @start_time_attrs = issue_table[:created_at] @@ -20,10 +22,6 @@ module Gitlab def serialize(event) AnalyticsIssueSerializer.new(project: @project).represent(event).as_json end - - def allowed_ids - @allowed_ids ||= IssuesFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) - end end end end diff --git a/lib/gitlab/cycle_analytics/merge_request_allowed.rb b/lib/gitlab/cycle_analytics/merge_request_allowed.rb new file mode 100644 index 00000000000..28f6db44759 --- /dev/null +++ b/lib/gitlab/cycle_analytics/merge_request_allowed.rb @@ -0,0 +1,9 @@ +module Gitlab + module CycleAnalytics + module MergeRequestAllowed + def allowed_ids + @allowed_ids ||= MergeRequestsFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/production_event.rb b/lib/gitlab/cycle_analytics/production_event.rb index fcf2dbe3490..4868c3c6237 100644 --- a/lib/gitlab/cycle_analytics/production_event.rb +++ b/lib/gitlab/cycle_analytics/production_event.rb @@ -1,6 +1,8 @@ module Gitlab module CycleAnalytics class ProductionEvent < BaseEvent + include IssueAllowed + def initialize(*args) @stage = :production @start_time_attrs = issue_table[:created_at] @@ -19,10 +21,6 @@ module Gitlab def serialize(event) AnalyticsIssueSerializer.new(project: @project).represent(event).as_json end - - def allowed_ids - @allowed_ids ||= IssuesFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) - end end end end diff --git a/lib/gitlab/cycle_analytics/review_event.rb b/lib/gitlab/cycle_analytics/review_event.rb index 30650537afe..b394a02cc52 100644 --- a/lib/gitlab/cycle_analytics/review_event.rb +++ b/lib/gitlab/cycle_analytics/review_event.rb @@ -1,6 +1,8 @@ module Gitlab module CycleAnalytics class ReviewEvent < BaseEvent + include MergeRequestAllowed + def initialize(*args) @stage = :review @start_time_attrs = mr_table[:created_at] @@ -18,10 +20,6 @@ module Gitlab def serialize(event) AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json end - - def allowed_ids - @allowed_ids ||= MergeRequestsFinder.new(@options[:current_user], project_id: @project.id).execute.where(id: event_result_ids).pluck(:id) - end end end end diff --git a/lib/gitlab/cycle_analytics/staging_event.rb b/lib/gitlab/cycle_analytics/staging_event.rb index 800b2b786dd..6c42b1a3a60 100644 --- a/lib/gitlab/cycle_analytics/staging_event.rb +++ b/lib/gitlab/cycle_analytics/staging_event.rb @@ -11,6 +11,12 @@ module Gitlab super(*args) end + def fetch + BuildUpdater.update!(event_result) + + super + end + def custom_query(base_query) base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) end @@ -18,9 +24,7 @@ module Gitlab private def serialize(event) - build = ::Ci::Build.find(event['id']) - - AnalyticsBuildSerializer.new.represent(build).as_json + AnalyticsBuildSerializer.new.represent(event['build']).as_json end end end diff --git a/lib/gitlab/cycle_analytics/updater.rb b/lib/gitlab/cycle_analytics/updater.rb new file mode 100644 index 00000000000..38cbfa03030 --- /dev/null +++ b/lib/gitlab/cycle_analytics/updater.rb @@ -0,0 +1,30 @@ +module Gitlab + module CycleAnalytics + class Updater + def self.update!(*args) + new(*args).update! + end + + def initialize(event_result, update_klass, update_key, column = nil) + @event_result = event_result + @update_klass = update_klass + @update_key = update_key.to_s + @column = column || "#{@update_key}_id" + end + + def update! + @event_result.each do |event| + event[@update_key] = items[event.delete(@column).to_i].first + end + end + + def result_ids + @event_result.map { |event| event[@column] } + end + + def items + @items ||= @update_klass.find(result_ids).group_by { |item| item['id'] } + end + end + end +end -- cgit v1.2.1 From dd826a5f20837f33263c658e41a4def0fc932069 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Fri, 18 Nov 2016 12:08:30 +0100 Subject: Return a consistent not found message This prevents leakage of project names on an endpoint which is unauthenticated and thus open to the world. --- lib/api/services.rb | 5 +++-- lib/mattermost/presenter.rb | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/api/services.rb b/lib/api/services.rb index e3c6a998631..4d23499aa39 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -67,7 +67,8 @@ module API post ':id/services/:service_slug/trigger' do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) - not_found! unless project + # This is not accurate, but done to prevent leakage of the project names + not_found!('Service') unless project service = project_service(project) @@ -77,7 +78,7 @@ module API status result[:status] || 200 present result else - not_found! + not_found!('Service') end end end diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index b4e7358770f..f76d0376a98 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -14,8 +14,8 @@ module Mattermost end def help(commands, trigger) - if commands.empty? - ephemeral_response("No commands configured") unless messages.count > 1 + if commands.zero? + ephemeral_response("No commands configured") else message = header_with_list("Available commands", commands) -- cgit v1.2.1 From f5b792e22eb7bd4ecafcd2ad3bc7a388abb36ffc Mon Sep 17 00:00:00 2001 From: James Lopez <james@jameslopez.es> Date: Fri, 18 Nov 2016 13:00:38 +0100 Subject: refactored updater and updated specs --- lib/gitlab/cycle_analytics/author_updater.rb | 9 --------- lib/gitlab/cycle_analytics/base_event.rb | 2 +- lib/gitlab/cycle_analytics/build_updater.rb | 9 --------- lib/gitlab/cycle_analytics/staging_event.rb | 2 +- lib/gitlab/cycle_analytics/updater.rb | 14 +++++++------- 5 files changed, 9 insertions(+), 27 deletions(-) delete mode 100644 lib/gitlab/cycle_analytics/author_updater.rb delete mode 100644 lib/gitlab/cycle_analytics/build_updater.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/author_updater.rb b/lib/gitlab/cycle_analytics/author_updater.rb deleted file mode 100644 index 87d26701d3a..00000000000 --- a/lib/gitlab/cycle_analytics/author_updater.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Gitlab - module CycleAnalytics - class AuthorUpdater < Updater - def self.update!(event_result) - new(event_result, User, :author).update! - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb index cedc73142fa..486139b1687 100644 --- a/lib/gitlab/cycle_analytics/base_event.rb +++ b/lib/gitlab/cycle_analytics/base_event.rb @@ -30,7 +30,7 @@ module Gitlab def update_author! return unless event_result.any? && event_result.first['author_id'] - AuthorUpdater.update!(event_result) + Updater.update!(event_result, from: 'author_id', to: 'author', klass: User) end def event_result diff --git a/lib/gitlab/cycle_analytics/build_updater.rb b/lib/gitlab/cycle_analytics/build_updater.rb deleted file mode 100644 index c1190d9e7a9..00000000000 --- a/lib/gitlab/cycle_analytics/build_updater.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Gitlab - module CycleAnalytics - class BuildUpdater < Updater - def self.update!(event_result) - new(event_result, ::Ci::Build, :build, 'id').update! - end - end - end -end diff --git a/lib/gitlab/cycle_analytics/staging_event.rb b/lib/gitlab/cycle_analytics/staging_event.rb index 6c42b1a3a60..a1f30b716f6 100644 --- a/lib/gitlab/cycle_analytics/staging_event.rb +++ b/lib/gitlab/cycle_analytics/staging_event.rb @@ -12,7 +12,7 @@ module Gitlab end def fetch - BuildUpdater.update!(event_result) + Updater.update!(event_result, from: 'id', to: 'build', klass: ::Ci::Build) super end diff --git a/lib/gitlab/cycle_analytics/updater.rb b/lib/gitlab/cycle_analytics/updater.rb index 38cbfa03030..953268ebd46 100644 --- a/lib/gitlab/cycle_analytics/updater.rb +++ b/lib/gitlab/cycle_analytics/updater.rb @@ -5,25 +5,25 @@ module Gitlab new(*args).update! end - def initialize(event_result, update_klass, update_key, column = nil) + def initialize(event_result, from:, to:, klass:) @event_result = event_result - @update_klass = update_klass - @update_key = update_key.to_s - @column = column || "#{@update_key}_id" + @klass = klass + @from = from + @to = to end def update! @event_result.each do |event| - event[@update_key] = items[event.delete(@column).to_i].first + event[@to] = items[event.delete(@from).to_i].first end end def result_ids - @event_result.map { |event| event[@column] } + @event_result.map { |event| event[@from] } end def items - @items ||= @update_klass.find(result_ids).group_by { |item| item['id'] } + @items ||= @klass.find(result_ids).group_by { |item| item['id'] } end end end -- cgit v1.2.1 From 65724301e6129440d0fba9cf9779297bc702c95b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon <grzesiek.bizon@gmail.com> Date: Fri, 18 Nov 2016 14:14:41 +0100 Subject: Make CI job script a required configuration entry --- lib/gitlab/ci/config/entry/job.rb | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index ab4ef333629..20dcc024b4e 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -13,12 +13,10 @@ module Gitlab type stage when artifacts cache dependencies before_script after_script variables environment] - attributes :tags, :allow_failure, :when, :dependencies - validations do validates :config, allowed_keys: ALLOWED_KEYS - validates :config, presence: true + validates :script, presence: true validates :name, presence: true validates :name, type: Symbol @@ -77,6 +75,8 @@ module Gitlab :cache, :image, :services, :only, :except, :variables, :artifacts, :commands, :environment + attributes :script, :tags, :allow_failure, :when, :dependencies + def compose!(deps = nil) super do if type_defined? && !stage_defined? @@ -118,20 +118,20 @@ module Gitlab def to_hash { name: name, - before_script: before_script, - script: script, + before_script: before_script_value, + script: script_value, commands: commands, - image: image, - services: services, - stage: stage, - cache: cache, - only: only, - except: except, - variables: variables_defined? ? variables : nil, - environment: environment_defined? ? environment : nil, - environment_name: environment_defined? ? environment[:name] : nil, - artifacts: artifacts, - after_script: after_script } + image: image_value, + services: services_value, + stage: stage_value, + cache: cache_value, + only: only_value, + except: except_value, + variables: variables_defined? ? variables_value : nil, + environment: environment_defined? ? environment_value : nil, + environment_name: environment_defined? ? environment_value[:name] : nil, + artifacts: artifacts_value, + after_script: after_script_value } end end end -- cgit v1.2.1 From c72c76fde3882b7c2f778bf85132cd2c80f01f5b Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Fri, 18 Nov 2016 13:29:47 +0100 Subject: Fix typos --- lib/mattermost/presenter.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index f76d0376a98..bfbb089eb02 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -14,9 +14,10 @@ module Mattermost end def help(commands, trigger) - if commands.zero? + if commands.none? ephemeral_response("No commands configured") else + commands.map! { |command| "#{trigger} #{command}" } message = header_with_list("Available commands", commands) ephemeral_response(message) @@ -46,7 +47,7 @@ module Mattermost private def not_found - ephemeral_response("404 not found! GitLab couldn't find what your were looking for! :boom:") + ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:") end def single_resource(resource) @@ -67,7 +68,7 @@ module Mattermost end def error(resource) - message = header_with_list("The action was not succesful, because:", resource.errors.messages) + message = header_with_list("The action was not successful, because:", resource.errors.messages) ephemeral_response(message) end -- cgit v1.2.1 From bf25b5f69d8343b1d1a39a49815c9c888896a758 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon <grzesiek.bizon@gmail.com> Date: Fri, 18 Nov 2016 14:30:27 +0100 Subject: Remove CI config helper with same name as an entry --- lib/gitlab/ci/config.rb | 41 +++++++++++++++++++++++++----- lib/gitlab/ci/config/entry/configurable.rb | 2 -- 2 files changed, 35 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 06599238d22..f7ff7ea212e 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -4,12 +4,6 @@ module Gitlab # Base GitLab CI Configuration facade # class Config - ## - # Temporary delegations that should be removed after refactoring - # - delegate :before_script, :image, :services, :after_script, :variables, - :stages, :cache, :jobs, to: :@global - def initialize(config) @config = Loader.new(config).load! @@ -28,6 +22,41 @@ module Gitlab def to_hash @config end + + ## + # Temporary method that should be removed after refactoring + # + def before_script + @global.before_script_value + end + + def image + @global.image_value + end + + def services + @global.services_value + end + + def after_script + @global.after_script_value + end + + def variables + @global.variables_value + end + + def stages + @global.stages_value + end + + def cache + @global.cache_value + end + + def jobs + @global.jobs_value + end end end end diff --git a/lib/gitlab/ci/config/entry/configurable.rb b/lib/gitlab/ci/config/entry/configurable.rb index 0f438faeda2..833ae4a0ff3 100644 --- a/lib/gitlab/ci/config/entry/configurable.rb +++ b/lib/gitlab/ci/config/entry/configurable.rb @@ -66,8 +66,6 @@ module Gitlab @entries[symbol].value end - - alias_method symbol.to_sym, "#{symbol}_value".to_sym end end end -- cgit v1.2.1 From f20eadcbbeb88e98c2608cbaf23f0d09ca002a98 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Tue, 8 Nov 2016 11:28:30 +0100 Subject: Fix faulty deployment refs In the 8.13 RC cycle, so before the release, there was a time in which references in git where stored by id instead of iid. This could be fixed by time, if the iid catches up with the id, it overwrites it. But in the mean time we have wrong refs in the folder. This commit fixes that. For all projects we have deployments we'll find the ones where the ref has a higher number than the iid is now and calls `#create_ref` on the corresponding deployment. --- lib/tasks/gitlab/cleanup.rake | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'lib') diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index b7cbdc6cd78..4a696a52b4d 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -91,5 +91,28 @@ namespace :gitlab do puts "To block these users run this command with BLOCK=true".color(:yellow) end end + + # This is a rake task which removes faulty refs. These refs where only + # created in the 8.13.RC cycle, and fixed in the stable builds which were + # released. So likely this should only be run once on gitlab.com + # Faulty refs are moved so they are kept around, else some features break. + desc 'GitLab | Cleanup | Remove faulty deployment refs' + task move_faulty_deployment_refs: :environment do + projects = Project.where(id: Deployment.select(:project_id).distinct) + + projects.find_each do |project| + rugged = project.repository.rugged + + max_iid = project.deployments.maximum(:iid) + + rugged.references.each('refs/environments/**/*') do |ref| + id = ref.name.split('/').last.to_i + next unless id > max_iid + + project.deployments.find(id).create_ref + rugged.references.delete(ref) + end + end + end end end -- cgit v1.2.1 From fd05e26618dd0c123ca476b6f5a3d85f1cfe397a Mon Sep 17 00:00:00 2001 From: Ahmad Sherif <me@ahmadsherif.com> Date: Tue, 11 Oct 2016 14:25:17 +0200 Subject: Precalculate user's authorized projects in database Closes #23150 --- lib/gitlab/database.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lib') diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 55b8f888d53..2d5c9232425 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -35,6 +35,13 @@ module Gitlab order end + def self.serialized_transaction + opts = {} + opts[:isolation] = :serializable unless Rails.env.test? && connection.transaction_open? + + connection.transaction(opts) { yield } + end + def self.random Gitlab::Database.postgresql? ? "RANDOM()" : "RAND()" end -- cgit v1.2.1 From fbfc7523cb0119ac3a0d02cd04dc12e453ad3ad6 Mon Sep 17 00:00:00 2001 From: Ido Leibovich <ileibovich@yotpo.com> Date: Mon, 31 Oct 2016 22:38:24 +0200 Subject: Add api endpoint for creating a pipeline Add a new endpoint in the new API for creating a new pipeline, and return the details of that pipeline. --- lib/api/pipelines.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'lib') diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 2a0c8e1f2c0..e69b0569612 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -22,6 +22,27 @@ module API pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope]) present paginate(pipelines), with: Entities::Pipeline end + + desc 'Create a new pipeline' do + detail 'This feature was introduced in GitLab 8.14' + success Entities::Pipeline + end + params do + requires :ref, type: String, desc: 'Reference' + end + post ':id/pipeline' do + authorize! :create_pipeline, user_project + + new_pipeline = Ci::CreatePipelineService.new(user_project, + current_user, + declared_params(include_missing: false)) + .execute(ignore_skip_ci: true, save_on_errors: false) + if new_pipeline.persisted? + present new_pipeline, with: Entities::Pipeline + else + render_validation_error!(new_pipeline) + end + end desc 'Gets a specific pipeline for the project' do detail 'This feature was introduced in GitLab 8.11' -- cgit v1.2.1 From 0fd397bba1a36136c3737165c9057bc59dcbca77 Mon Sep 17 00:00:00 2001 From: James Lopez <james@jameslopez.es> Date: Mon, 21 Nov 2016 10:48:07 +0100 Subject: Added permissions per stage to cycle analytics endpoint --- lib/gitlab/cycle_analytics/permissions.rb | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/gitlab/cycle_analytics/permissions.rb (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb new file mode 100644 index 00000000000..121b723f7be --- /dev/null +++ b/lib/gitlab/cycle_analytics/permissions.rb @@ -0,0 +1,49 @@ +module Gitlab + module CycleAnalytics + class Permissions + STAGE_PERMISSIONS = { + read_build: [:test, :staging], + read_issue: [:issue, :production], + read_merge_request: [:code, :review] + }.freeze + + def self.get(*args) + new(*args).get + end + + def initialize(user:, project:) + @user = user + @project = project + @stage_permission_hash = {} + end + + def get + ::CycleAnalytics::STAGES.each do |stage| + @stage_permission_hash[stage] = authorized_stage?(stage) + end + + @stage_permission_hash + end + + private + + def authorized_stage?(stage) + return false unless authorize_project(:read_cycle_analytics) + + permissions_for_stage(stage).keys.each do |permission| + return false unless authorize_project(permission) + end + + true + end + + def permissions_for_stage(stage) + STAGE_PERMISSIONS.select { |_permission, stages| stages.include?(stage) } + end + + def authorize_project(permission) + Ability.allowed?(@user, permission, @project) + end + end + end +end -- cgit v1.2.1 From 4cb3c0b404f256be8bc80430eaf25b468e30ee44 Mon Sep 17 00:00:00 2001 From: Robert Schilling <rschilling@student.tugraz.at> Date: Thu, 27 Oct 2016 10:20:06 +0200 Subject: Grapify the users API --- lib/api/users.rb | 508 +++++++++++++++++++++++++++---------------------------- 1 file changed, 250 insertions(+), 258 deletions(-) (limited to 'lib') diff --git a/lib/api/users.rb b/lib/api/users.rb index 298c401a816..c07539194ed 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -4,89 +4,93 @@ module API before { authenticate! } resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do - # Get a users list - # - # Example Request: - # GET /users - # GET /users?search=Admin - # GET /users?username=root - # GET /users?active=true - # GET /users?external=true - # GET /users?blocked=true + helpers do + params :optional_attributes do + optional :skype, type: String, desc: 'The Skype username' + optional :linkedin, type: String, desc: 'The LinkedIn username' + optional :twitter, type: String, desc: 'The Twitter username' + optional :website_url, type: String, desc: 'The website of the user' + optional :organization, type: String, desc: 'The organization of the user' + optional :projects_limit, type: Integer, desc: 'The number of projects a user can create' + optional :extern_uid, type: Integer, desc: 'The external authentication provider UID' + optional :provider, type: String, desc: 'The external provider' + optional :bio, type: String, desc: 'The biography of the user' + optional :location, type: String, desc: 'The location of the user' + optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator' + optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' + optional :confirm, type: Boolean, desc: 'Flag indicating the account needs to be confirmed' + optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' + all_or_none_of :extern_uid, :provider + end + end + + desc 'Get the list of users' do + success Entities::UserBasic + end + params do + optional :username, type: String, desc: 'Get a single user with a specific username' + optional :search, type: String, desc: 'Search for a username' + optional :active, type: Boolean, default: false, desc: 'Filters only active users' + optional :external, type: Boolean, default: false, desc: 'Filters only external users' + optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users' + end get do unless can?(current_user, :read_users_list, nil) render_api_error!("Not authorized.", 403) end if params[:username].present? - @users = User.where(username: params[:username]) + users = User.where(username: params[:username]) else - @users = User.all - @users = @users.active if to_boolean(params[:active]) - @users = @users.search(params[:search]) if params[:search].present? - @users = @users.blocked if to_boolean(params[:blocked]) - @users = @users.external if to_boolean(params[:external]) && current_user.is_admin? - @users = paginate @users + users = User.all + users = users.active if params[:active] + users = users.search(params[:search]) if params[:search].present? + users = users.blocked if params[:blocked] + users = users.external if params[:external] && current_user.is_admin? end - if current_user.is_admin? - present @users, with: Entities::UserFull - else - present @users, with: Entities::UserBasic - end + entity = current_user.is_admin? ? Entities::UserFull : Entities::UserBasic + present paginate(users), with: entity end - # Get a single user - # - # Parameters: - # id (required) - The ID of a user - # Example Request: - # GET /users/:id + desc 'Get a single user' do + success Entities::UserBasic + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end get ":id" do - @user = User.find(params[:id]) + user = User.find_by(id: params[:id]) + not_found!('User') unless user if current_user && current_user.is_admin? - present @user, with: Entities::UserFull - elsif can?(current_user, :read_user, @user) - present @user, with: Entities::User + present user, with: Entities::UserFull + elsif can?(current_user, :read_user, user) + present user, with: Entities::User else render_api_error!("User not found.", 404) end end - # Create user. Available only for admin - # - # Parameters: - # email (required) - Email - # password (required) - Password - # name (required) - Name - # username (required) - Name - # skype - Skype ID - # linkedin - Linkedin - # twitter - Twitter account - # website_url - Website url - # organization - Organization - # projects_limit - Number of projects user can create - # extern_uid - External authentication provider UID - # provider - External provider - # bio - Bio - # location - Location of the user - # admin - User is admin - true or false (default) - # can_create_group - User can create groups - true or false - # confirm - Require user confirmation - true (default) or false - # external - Flags the user as external - true or false(default) - # Example Request: - # POST /users + desc 'Create a user. Available only for admins.' do + success Entities::UserFull + end + params do + requires :email, type: String, desc: 'The email of the user' + requires :password, type: String, desc: 'The password of the new user' + requires :name, type: String, desc: 'The name of the user' + requires :username, type: String, desc: 'The username of the user' + use :optional_attributes + end post do authenticated_as_admin! - required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external, :organization] - admin = attrs.delete(:admin) - confirm = !(attrs.delete(:confirm) =~ /(false|f|no|0)$/i) - user = User.build_user(attrs) - user.admin = admin unless admin.nil? + + # Filter out params which are used later + identity_attrs = params.slice(:provider, :extern_uid) + confirm = params.delete(:confirm) + + user = User.build_user(declared_params(include_missing: false)) user.skip_confirmation! unless confirm - identity_attrs = attributes_for_keys [:provider, :extern_uid] if identity_attrs.any? user.identities.build(identity_attrs) @@ -107,46 +111,40 @@ module API end end - # Update user. Available only for admin - # - # Parameters: - # email - Email - # name - Name - # password - Password - # skype - Skype ID - # linkedin - Linkedin - # twitter - Twitter account - # website_url - Website url - # organization - Organization - # projects_limit - Limit projects each user can create - # bio - Bio - # location - Location of the user - # admin - User is admin - true or false (default) - # can_create_group - User can create groups - true or false - # external - Flags the user as external - true or false(default) - # Example Request: - # PUT /users/:id + desc 'Update a user. Available only for admins.' do + success Entities::UserFull + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + optional :email, type: String, desc: 'The email of the user' + optional :password, type: String, desc: 'The password of the new user' + optional :name, type: String, desc: 'The name of the user' + optional :username, type: String, desc: 'The username of the user' + use :optional_attributes + at_least_one_of :email, :password, :name, :username, :skype, :linkedin, + :twitter, :website_url, :organization, :projects_limit, + :extern_uid, :provider, :bio, :location, :admin, + :can_create_group, :confirm, :external + end put ":id" do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external, :organization] - user = User.find(params[:id]) + user = User.find_by(id: params.delete(:id)) not_found!('User') unless user - admin = attrs.delete(:admin) - user.admin = admin unless admin.nil? - - conflict!('Email has already been taken') if attrs[:email] && - User.where(email: attrs[:email]). + conflict!('Email has already been taken') if params[:email] && + User.where(email: params[:email]). where.not(id: user.id).count > 0 - conflict!('Username has already been taken') if attrs[:username] && - User.where(username: attrs[:username]). + conflict!('Username has already been taken') if params[:username] && + User.where(username: params[:username]). where.not(id: user.id).count > 0 - identity_attrs = attributes_for_keys [:provider, :extern_uid] + identity_attrs = params.slice(:provider, :extern_uid) + if identity_attrs.any? identity = user.identities.find_by(provider: identity_attrs[:provider]) + if identity identity.update_attributes(identity_attrs) else @@ -155,28 +153,33 @@ module API end end - if user.update_attributes(attrs) + # Delete already handled parameters + params.delete(:extern_uid) + params.delete(:provider) + + if user.update_attributes(declared_params(include_missing: false)) present user, with: Entities::UserFull else render_validation_error!(user) end end - # Add ssh key to a specified user. Only available to admin users. - # - # Parameters: - # id (required) - The ID of a user - # key (required) - New SSH Key - # title (required) - New SSH Key's title - # Example Request: - # POST /users/:id/keys + desc 'Add an SSH key to a specified user. Available only for admins.' do + success Entities::SSHKey + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :key, type: String, desc: 'The new SSH key' + requires :title, type: String, desc: 'The title of the new SSH key' + end post ":id/keys" do authenticated_as_admin! - required_attributes! [:title, :key] - user = User.find(params[:id]) - attrs = attributes_for_keys [:title, :key] - key = user.keys.new attrs + user = User.find_by(id: params.delete(:id)) + not_found!('User') unless user + + key = user.keys.new(declared_params(include_missing: false)) + if key.save present key, with: Entities::SSHKey else @@ -184,55 +187,55 @@ module API end end - # Get ssh keys of a specified user. Only available to admin users. - # - # Parameters: - # uid (required) - The ID of a user - # Example Request: - # GET /users/:uid/keys - get ':uid/keys' do + desc 'Get the SSH keys of a specified user. Available only for admins.' do + success Entities::SSHKey + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + get ':id/keys' do authenticated_as_admin! - user = User.find_by(id: params[:uid]) + + user = User.find_by(id: params[:id]) not_found!('User') unless user present user.keys, with: Entities::SSHKey end - # Delete existing ssh key of a specified user. Only available to admin - # users. - # - # Parameters: - # uid (required) - The ID of a user - # id (required) - SSH Key ID - # Example Request: - # DELETE /users/:uid/keys/:id - delete ':uid/keys/:id' do + desc 'Delete an existing SSH key from a specified user. Available only for admins.' do + success Entities::SSHKey + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :key_id, type: Integer, desc: 'The ID of the SSH key' + end + delete ':id/keys/:key_id' do authenticated_as_admin! - user = User.find_by(id: params[:uid]) + + user = User.find_by(id: params[:id]) not_found!('User') unless user - begin - key = user.keys.find params[:id] - key.destroy - rescue ActiveRecord::RecordNotFound - not_found!('Key') - end + key = user.keys.find_by(id: params[:key_id]) + not_found!('Key') unless key + + present key.destroy, with: Entities::SSHKey end - # Add email to a specified user. Only available to admin users. - # - # Parameters: - # id (required) - The ID of a user - # email (required) - Email address - # Example Request: - # POST /users/:id/emails + desc 'Add an email address to a specified user. Available only for admins.' do + success Entities::Email + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :email, type: String, desc: 'The email of the user' + end post ":id/emails" do authenticated_as_admin! - required_attributes! [:email] - user = User.find(params[:id]) - attrs = attributes_for_keys [:email] - email = user.emails.new attrs + user = User.find_by(id: params.delete(:id)) + not_found!('User') unless user + + email = user.emails.new(declared_params(include_missing: false)) + if email.save NotificationService.new.new_email(email) present email, with: Entities::Email @@ -241,101 +244,94 @@ module API end end - # Get emails of a specified user. Only available to admin users. - # - # Parameters: - # uid (required) - The ID of a user - # Example Request: - # GET /users/:uid/emails - get ':uid/emails' do + desc 'Get the emails addresses of a specified user. Available only for admins.' do + success Entities::Email + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + get ':id/emails' do authenticated_as_admin! - user = User.find_by(id: params[:uid]) + user = User.find_by(id: params[:id]) not_found!('User') unless user present user.emails, with: Entities::Email end - # Delete existing email of a specified user. Only available to admin - # users. - # - # Parameters: - # uid (required) - The ID of a user - # id (required) - Email ID - # Example Request: - # DELETE /users/:uid/emails/:id - delete ':uid/emails/:id' do + desc 'Delete an email address of a specified user. Available only for admins.' do + success Entities::Email + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :email_id, type: Integer, desc: 'The ID of the email' + end + delete ':id/emails/:email_id' do authenticated_as_admin! - user = User.find_by(id: params[:uid]) + user = User.find_by(id: params[:id]) not_found!('User') unless user - begin - email = user.emails.find params[:id] - email.destroy + email = user.emails.find_by(id: params[:email_id]) + not_found!('Email') unless email - user.update_secondary_emails! - rescue ActiveRecord::RecordNotFound - not_found!('Email') - end + email.destroy + user.update_secondary_emails! end - # Delete user. Available only for admin - # - # Example Request: - # DELETE /users/:id + desc 'Delete a user. Available only for admins.' do + success Entities::Email + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end delete ":id" do authenticated_as_admin! user = User.find_by(id: params[:id]) + not_found!('User') unless user - if user - DeleteUserService.new(current_user).execute(user) - else - not_found!('User') - end + DeleteUserService.new(current_user).execute(user) end - # Block user. Available only for admin - # - # Example Request: - # PUT /users/:id/block + desc 'Block a user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end put ':id/block' do authenticated_as_admin! user = User.find_by(id: params[:id]) + not_found!('User') unless user - if !user - not_found!('User') - elsif !user.ldap_blocked? + if !user.ldap_blocked? user.block else forbidden!('LDAP blocked users cannot be modified by the API') end end - # Unblock user. Available only for admin - # - # Example Request: - # PUT /users/:id/unblock + desc 'Unblock a user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end put ':id/unblock' do authenticated_as_admin! user = User.find_by(id: params[:id]) + not_found!('User') unless user - if !user - not_found!('User') - elsif user.ldap_blocked? + if user.ldap_blocked? forbidden!('LDAP blocked users cannot be unblocked by the API') else user.activate end end - desc 'Get contribution events of a specified user' do + desc 'Get the contribution events of a specified user' do detail 'This feature was introduced in GitLab 8.13.' success Entities::Event end params do - requires :id, type: String, desc: 'The user ID' + requires :id, type: Integer, desc: 'The ID of the user' end get ':id/events' do - user = User.find_by(id: declared(params).id) + user = User.find_by(id: params[:id]) not_found!('User') unless user events = user.events. @@ -349,43 +345,43 @@ module API end resource :user do - # Get currently authenticated user - # - # Example Request: - # GET /user + desc 'Get the currently authenticated user' do + success Entities::UserFull + end get do - present @current_user, with: Entities::UserFull + present current_user, with: Entities::UserFull end - # Get currently authenticated user's keys - # - # Example Request: - # GET /user/keys + desc "Get the currently authenticated user's SSH keys" do + success Entities::SSHKey + end get "keys" do present current_user.keys, with: Entities::SSHKey end - # Get single key owned by currently authenticated user - # - # Example Request: - # GET /user/keys/:id - get "keys/:id" do - key = current_user.keys.find params[:id] + desc 'Get a single key owned by currently authenticated user' do + success Entities::SSHKey + end + params do + requires :key_id, type: Integer, desc: 'The ID of the SSH key' + end + get "keys/:key_id" do + key = current_user.keys.find_by(id: params[:key_id]) + not_found!('Key') unless key + present key, with: Entities::SSHKey end - # Add new ssh key to currently authenticated user - # - # Parameters: - # key (required) - New SSH Key - # title (required) - New SSH Key's title - # Example Request: - # POST /user/keys + desc 'Add a new SSH key to the currently authenticated user' do + success Entities::SSHKey + end + params do + requires :key, type: String, desc: 'The new SSH key' + requires :title, type: String, desc: 'The title of the new SSH key' + end post "keys" do - required_attributes! [:title, :key] + key = current_user.keys.new(declared_params) - attrs = attributes_for_keys [:title, :key] - key = current_user.keys.new attrs if key.save present key, with: Entities::SSHKey else @@ -393,48 +389,48 @@ module API end end - # Delete existing ssh key of currently authenticated user - # - # Parameters: - # id (required) - SSH Key ID - # Example Request: - # DELETE /user/keys/:id - delete "keys/:id" do - begin - key = current_user.keys.find params[:id] - key.destroy - rescue - end + desc 'Delete an SSH key from the currently authenticated user' do + success Entities::SSHKey + end + params do + requires :key_id, type: Integer, desc: 'The ID of the SSH key' + end + delete "keys/:key_id" do + key = current_user.keys.find_by(id: params[:key_id]) + not_found!('Key') unless key + + present key.destroy, with: Entities::SSHKey end - # Get currently authenticated user's emails - # - # Example Request: - # GET /user/emails + desc "Get the currently authenticated user's email addresses" do + success Entities::Email + end get "emails" do present current_user.emails, with: Entities::Email end - # Get single email owned by currently authenticated user - # - # Example Request: - # GET /user/emails/:id - get "emails/:id" do - email = current_user.emails.find params[:id] + desc 'Get a single email address owned by the currently authenticated user' do + success Entities::Email + end + params do + requires :email_id, type: Integer, desc: 'The ID of the email' + end + get "emails/:email_id" do + email = current_user.emails.find_by(id: params[:email_id]) + not_found!('Email') unless email + present email, with: Entities::Email end - # Add new email to currently authenticated user - # - # Parameters: - # email (required) - Email address - # Example Request: - # POST /user/emails + desc 'Add new email address to the currently authenticated user' do + success Entities::Email + end + params do + requires :email, type: String, desc: 'The new email' + end post "emails" do - required_attributes! [:email] + email = current_user.emails.new(declared_params) - attrs = attributes_for_keys [:email] - email = current_user.emails.new attrs if email.save NotificationService.new.new_email(email) present email, with: Entities::Email @@ -443,20 +439,16 @@ module API end end - # Delete existing email of currently authenticated user - # - # Parameters: - # id (required) - EMail ID - # Example Request: - # DELETE /user/emails/:id - delete "emails/:id" do - begin - email = current_user.emails.find params[:id] - email.destroy + desc 'Delete an email address from the currently authenticated user' + params do + requires :email_id, type: Integer, desc: 'The ID of the email' + end + delete "emails/:email_id" do + email = current_user.emails.find_by(id: params[:email_id]) + not_found!('Email') unless email - current_user.update_secondary_emails! - rescue - end + email.destroy + current_user.update_secondary_emails! end end end -- cgit v1.2.1 From df5548e19e8c988b709e66a7e35ddc097344913e Mon Sep 17 00:00:00 2001 From: Yorick Peterse <yorickpeterse@gmail.com> Date: Tue, 8 Nov 2016 16:42:28 +0100 Subject: Unify detecting of special repository files This moves the logic of detecting special repository files (e.g. a README or a Koding configuration file) to a single class: Gitlab::FileDetector. Moving this logic into a single place allows this to be re-used more easily. This commit also changes Repository#gitlab_ci_yaml so that its cached similar to other data (e.g. the Koding configuration file). --- lib/gitlab/file_detector.rb | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 lib/gitlab/file_detector.rb (limited to 'lib') diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb new file mode 100644 index 00000000000..1d93a67dc56 --- /dev/null +++ b/lib/gitlab/file_detector.rb @@ -0,0 +1,63 @@ +require 'set' + +module Gitlab + # Module that can be used to detect if a path points to a special file such as + # a README or a CONTRIBUTING file. + module FileDetector + PATTERNS = { + readme: /\Areadme/i, + changelog: /\A(changelog|history|changes|news)/i, + license: /\A(licen[sc]e|copying)(\..+|\z)/i, + contributing: /\Acontributing/i, + version: 'version', + gitignore: '.gitignore', + koding: '.koding.yml', + gitlab_ci: '.gitlab-ci.yml', + avatar: /\Alogo\.(png|jpg|gif)\z/ + } + + # Returns an Array of file types based on the given paths. + # + # This method can be used to check if a list of file paths (e.g. of changed + # files) involve any special files such as a README or a LICENSE file. + # + # Example: + # + # types_in_paths(%w{README.md foo/bar.txt}) # => [:readme] + def self.types_in_paths(paths) + types = Set.new + + paths.each do |path| + type = type_of(path) + + types << type if type + end + + types.to_a + end + + # Returns the type of a file path, or nil if none could be detected. + # + # Returned types are Symbols such as `:readme`, `:version`, etc. + # + # Example: + # + # type_of('README.md') # => :readme + # type_of('VERSION') # => :version + def self.type_of(path) + name = File.basename(path) + + PATTERNS.each do |type, search| + did_match = if search.is_a?(Regexp) + name =~ search + else + name.casecmp(search) == 0 + end + + return type if did_match + end + + nil + end + end +end -- cgit v1.2.1 From 9b691688583ad46d5608320ec64873dd2eb9a647 Mon Sep 17 00:00:00 2001 From: James Lopez <james@jameslopez.es> Date: Mon, 21 Nov 2016 14:09:26 +0100 Subject: refactored a couple of things based on feedback --- lib/gitlab/cycle_analytics/permissions.rb | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb index 121b723f7be..bef3b95ff1b 100644 --- a/lib/gitlab/cycle_analytics/permissions.rb +++ b/lib/gitlab/cycle_analytics/permissions.rb @@ -2,9 +2,12 @@ module Gitlab module CycleAnalytics class Permissions STAGE_PERMISSIONS = { - read_build: [:test, :staging], - read_issue: [:issue, :production], - read_merge_request: [:code, :review] + issue: :read_issue, + code: :read_merge_request, + test: :read_build, + review: :read_merge_request, + staging: :read_build, + production: :read_issue, }.freeze def self.get(*args) @@ -30,15 +33,7 @@ module Gitlab def authorized_stage?(stage) return false unless authorize_project(:read_cycle_analytics) - permissions_for_stage(stage).keys.each do |permission| - return false unless authorize_project(permission) - end - - true - end - - def permissions_for_stage(stage) - STAGE_PERMISSIONS.select { |_permission, stages| stages.include?(stage) } + STAGE_PERMISSIONS[stage] ? authorize_project(STAGE_PERMISSIONS[stage]) : true end def authorize_project(permission) -- cgit v1.2.1 From e3fb0740228219433a4623dc0b9325785e23ae16 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin <tomasz@maczukin.pl> Date: Sun, 20 Nov 2016 20:43:50 +0100 Subject: Send credentials array with build data --- lib/ci/api/entities.rb | 8 +++++--- lib/gitlab/ci/build/credentials.rb | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 lib/gitlab/ci/build/credentials.rb (limited to 'lib') diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index e00d91a6b45..792ff628b09 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -32,6 +32,10 @@ module Ci expose :artifacts_file, using: ArtifactFile, if: ->(build, _) { build.artifacts? } end + class BuildCredentials < Grape::Entity + expose :type, :url, :username, :password + end + class BuildDetails < Build expose :commands expose :repo_url @@ -51,9 +55,7 @@ module Ci expose :variables expose :depends_on_builds, using: Build - expose :registry_url, if: ->(_, _) { Gitlab.config.registry.enabled } do |_| - Gitlab.config.registry.host_port - end + expose :credentials, using: BuildCredentials end class Runner < Grape::Entity diff --git a/lib/gitlab/ci/build/credentials.rb b/lib/gitlab/ci/build/credentials.rb new file mode 100644 index 00000000000..14f9e8d7244 --- /dev/null +++ b/lib/gitlab/ci/build/credentials.rb @@ -0,0 +1,16 @@ +module Gitlab + module Ci + module Build + class Credentials + attr_accessor :type, :url, :username, :password + + def initialize(type, url, username, password) + @type = type + @url = url + @username = username + @password = password + end + end + end + end +end -- cgit v1.2.1 From 1d16f137d93576385e403f5caf5f64bfe0b3a647 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski <ayufan@ayufan.eu> Date: Mon, 21 Nov 2016 13:47:18 +0100 Subject: Add deploy chat command [ci skip] --- lib/gitlab/chat_commands/deploy.rb | 34 ++++++++++++++++++++++++++++++++++ lib/gitlab/chat_commands/result.rb | 5 +++++ lib/mattermost/presenter.rb | 35 ++++++++++++++++++++++------------- 3 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 lib/gitlab/chat_commands/deploy.rb create mode 100644 lib/gitlab/chat_commands/result.rb (limited to 'lib') diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb new file mode 100644 index 00000000000..8165d52575d --- /dev/null +++ b/lib/gitlab/chat_commands/deploy.rb @@ -0,0 +1,34 @@ +module Gitlab + module ChatCommands + class Deploy < BaseCommand + def self.match(text) + /\Adeploy\s+(?<from>.*)\s+to+\s+(?<to>.*)\z/.match(text) + end + + def self.help_message + 'deploy <environment> to <target-environment>' + end + + def self.allowed?(project, user) + can?(user, :create_deployment, project) + end + + def execute(match) + from = match[:from] + to = match[:to] + + environment = project.environments.find_by(name: from) + return unless environment + + actions = environment.actions_for(to) + return unless actions.any? + + if actions.one? + actions.first.play(current_user) + else + Result.new(:error, 'Too many actions defined') + end + end + end + end +end diff --git a/lib/gitlab/chat_commands/result.rb b/lib/gitlab/chat_commands/result.rb new file mode 100644 index 00000000000..324d7ef43a3 --- /dev/null +++ b/lib/gitlab/chat_commands/result.rb @@ -0,0 +1,5 @@ +module Gitlab + module ChatCommands + Result = Struct.new(:type, :message) + end +end diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index bfbb089eb02..8ecab01a359 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -24,20 +24,22 @@ module Mattermost end end - def present(resource) - return not_found unless resource - - if resource.respond_to?(:count) - if resource.count > 1 - return multiple_resources(resource) - elsif resource.count == 0 - return not_found + def present(subject) + return not_found unless subject + + if subject.is_a?(Gitlab::ChatCommands::Result) + show_result(subject) + elsif subject.respond_to?(:count) + if subject.try(:many?) + multiple_resources(subject) + elsif subject.count == 0 + not_found else - resource = resource.first + single_resource(subject) end + else + single_resource(subject) end - - single_resource(resource) end def access_denied @@ -46,6 +48,10 @@ module Mattermost private + def show_result(result) + ephemeral_response(result.message) + end + def not_found ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:") end @@ -54,7 +60,7 @@ module Mattermost return error(resource) if resource.errors.any? || !resource.persisted? message = "### #{title(resource)}" - message << "\n\n#{resource.description}" if resource.description + message << "\n\n#{resource.description}" if resource.try(:description) in_channel_response(message) end @@ -74,7 +80,10 @@ module Mattermost end def title(resource) - "[#{resource.to_reference} #{resource.title}](#{url(resource)})" + reference = resource.try(:to_reference) || resource.try(:id) + title = resource.try(:title) || resource.try(:name) + + "[#{reference} #{title}](#{url(resource)})" end def header_with_list(header, items) -- cgit v1.2.1 From b7bf1f3a9d8c406024a212e6098f049334cda8e3 Mon Sep 17 00:00:00 2001 From: Robert Schilling <rschilling@student.tugraz.at> Date: Mon, 21 Nov 2016 15:06:32 +0100 Subject: Grapify the projects snippet API --- lib/api/project_snippets.rb | 156 ++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 78 deletions(-) (limited to 'lib') diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index ce1bf0d26d2..d0ee9c9a5b2 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -3,6 +3,9 @@ module API class ProjectSnippets < Grape::API before { authenticate! } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do helpers do def handle_project_member_errors(errors) @@ -18,111 +21,108 @@ module API end end - # Get a project snippets - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/snippets + desc 'Get all project snippets' do + success Entities::ProjectSnippet + end get ":id/snippets" do present paginate(snippets_for_current_user), with: Entities::ProjectSnippet end - # Get a project snippet - # - # Parameters: - # id (required) - The ID of a project - # snippet_id (required) - The ID of a project snippet - # Example Request: - # GET /projects/:id/snippets/:snippet_id + desc 'Get a single project snippet' do + success Entities::ProjectSnippet + end + params do + requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' + end get ":id/snippets/:snippet_id" do - @snippet = snippets_for_current_user.find(params[:snippet_id]) - present @snippet, with: Entities::ProjectSnippet - end - - # Create a new project snippet - # - # Parameters: - # id (required) - The ID of a project - # title (required) - The title of a snippet - # file_name (required) - The name of a snippet file - # code (required) - The content of a snippet - # visibility_level (required) - The snippet's visibility - # Example Request: - # POST /projects/:id/snippets + snippet = snippets_for_current_user.find(params[:snippet_id]) + present snippet, with: Entities::ProjectSnippet + end + + desc 'Create a new project snippet' do + success Entities::ProjectSnippet + end + params do + requires :title, type: String, desc: 'The title of the snippet' + requires :file_name, type: String, desc: 'The file name of the snippet' + requires :code, type: String, desc: 'The content of the snippet' + requires :visibility_level, type: Integer, + values: [Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC], + desc: 'The visibility level of the snippet' + end post ":id/snippets" do authorize! :create_project_snippet, user_project - required_attributes! [:title, :file_name, :code, :visibility_level] + snippet_params = declared_params + snippet_params[:content] = snippet_params.delete(:code) - attrs = attributes_for_keys [:title, :file_name, :visibility_level] - attrs[:content] = params[:code] if params[:code].present? - @snippet = CreateSnippetService.new(user_project, current_user, - attrs).execute + snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute - if @snippet.errors.any? - render_validation_error!(@snippet) + if snippet.persisted? + present snippet, with: Entities::ProjectSnippet else - present @snippet, with: Entities::ProjectSnippet + render_validation_error!(snippet) end end - # Update an existing project snippet - # - # Parameters: - # id (required) - The ID of a project - # snippet_id (required) - The ID of a project snippet - # title (optional) - The title of a snippet - # file_name (optional) - The name of a snippet file - # code (optional) - The content of a snippet - # visibility_level (optional) - The snippet's visibility - # Example Request: - # PUT /projects/:id/snippets/:snippet_id + desc 'Update an existing project snippet' do + success Entities::ProjectSnippet + end + params do + requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' + optional :title, type: String, desc: 'The title of the snippet' + optional :file_name, type: String, desc: 'The file name of the snippet' + optional :code, type: String, desc: 'The content of the snippet' + optional :visibility_level, type: Integer, + values: [Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC], + desc: 'The visibility level of the snippet' + at_least_one_of :title, :file_name, :code, :visibility_level + end put ":id/snippets/:snippet_id" do - @snippet = snippets_for_current_user.find(params[:snippet_id]) - authorize! :update_project_snippet, @snippet + snippet = snippets_for_current_user.find_by(id: params.delete(:snippet_id)) + not_found!('Snippet') unless snippet + + authorize! :update_project_snippet, snippet + + snippet_params = declared_params(include_missing: false) + snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present? - attrs = attributes_for_keys [:title, :file_name, :visibility_level] - attrs[:content] = params[:code] if params[:code].present? + UpdateSnippetService.new(user_project, current_user, snippet, + snippet_params).execute - UpdateSnippetService.new(user_project, current_user, @snippet, - attrs).execute - if @snippet.errors.any? - render_validation_error!(@snippet) + if snippet.persisted? + present snippet, with: Entities::ProjectSnippet else - present @snippet, with: Entities::ProjectSnippet + render_validation_error!(snippet) end end - # Delete a project snippet - # - # Parameters: - # id (required) - The ID of a project - # snippet_id (required) - The ID of a project snippet - # Example Request: - # DELETE /projects/:id/snippets/:snippet_id + desc 'Delete a project snippet' + params do + requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' + end delete ":id/snippets/:snippet_id" do - begin - @snippet = snippets_for_current_user.find(params[:snippet_id]) - authorize! :update_project_snippet, @snippet - @snippet.destroy - rescue - not_found!('Snippet') - end + snippet = snippets_for_current_user.find_by(id: params[:snippet_id]) + not_found!('Snippet') unless snippet + + authorize! :admin_project_snippet, snippet + snippet.destroy end - # Get a raw project snippet - # - # Parameters: - # id (required) - The ID of a project - # snippet_id (required) - The ID of a project snippet - # Example Request: - # GET /projects/:id/snippets/:snippet_id/raw + desc 'Get a raw project snippet' + params do + requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' + end get ":id/snippets/:snippet_id/raw" do - @snippet = snippets_for_current_user.find(params[:snippet_id]) + snippet = snippets_for_current_user.find_by(id: params[:snippet_id]) + not_found!('Snippet') unless snippet env['api.format'] = :txt content_type 'text/plain' - present @snippet.content + present snippet.content end end end -- cgit v1.2.1 From a36d556137116385e84eca592ec62e46ecb97e03 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski <ayufan@ayufan.eu> Date: Mon, 21 Nov 2016 17:26:35 +0100 Subject: Introduce deploy command that allows to start deployment from one environment to second one --- lib/gitlab/chat_commands/command.rb | 1 + lib/gitlab/chat_commands/deploy.rb | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 5f131703d40..0ec358debc7 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -4,6 +4,7 @@ module Gitlab COMMANDS = [ Gitlab::ChatCommands::IssueShow, Gitlab::ChatCommands::IssueCreate, + Gitlab::ChatCommands::Deploy, ].freeze def execute diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb index 8165d52575d..c0b93cca68c 100644 --- a/lib/gitlab/chat_commands/deploy.rb +++ b/lib/gitlab/chat_commands/deploy.rb @@ -9,6 +9,10 @@ module Gitlab 'deploy <environment> to <target-environment>' end + def self.available?(project) + project.builds_enabled? + end + def self.allowed?(project, user) can?(user, :create_deployment, project) end @@ -17,11 +21,8 @@ module Gitlab from = match[:from] to = match[:to] - environment = project.environments.find_by(name: from) - return unless environment - - actions = environment.actions_for(to) - return unless actions.any? + actions = find_actions(from, to) + return unless actions.present? if actions.one? actions.first.play(current_user) @@ -29,6 +30,15 @@ module Gitlab Result.new(:error, 'Too many actions defined') end end + + private + + def find_actions(from, to) + environment = project.environments.find_by(name: from) + return unless environment + + environment.actions_for(to).select(&:starts_environment?) + end end end end -- cgit v1.2.1 From d375e3f15b6f0340addb9a20e0c03a1f1fca413a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski <ayufan@ayufan.eu> Date: Mon, 21 Nov 2016 18:22:03 +0100 Subject: Fix specs and improve code readability --- lib/mattermost/presenter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index 8ecab01a359..6b12081575d 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -30,9 +30,9 @@ module Mattermost if subject.is_a?(Gitlab::ChatCommands::Result) show_result(subject) elsif subject.respond_to?(:count) - if subject.try(:many?) + if subject.many? multiple_resources(subject) - elsif subject.count == 0 + elsif subject.none? not_found else single_resource(subject) -- cgit v1.2.1 From 039d4a1cbff5b54dc60363ff5f244e84cb54aacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= <remy@rymai.me> Date: Mon, 21 Nov 2016 17:44:24 +0100 Subject: Fix StrongAttibutes error with Ruby 2.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable <remy@rymai.me> --- lib/api/users.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/api/users.rb b/lib/api/users.rb index c07539194ed..a73650dc361 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -140,7 +140,8 @@ module API User.where(username: params[:username]). where.not(id: user.id).count > 0 - identity_attrs = params.slice(:provider, :extern_uid) + user_params = declared_params(include_missing: false) + identity_attrs = user_params.slice(:provider, :extern_uid) if identity_attrs.any? identity = user.identities.find_by(provider: identity_attrs[:provider]) @@ -154,10 +155,10 @@ module API end # Delete already handled parameters - params.delete(:extern_uid) - params.delete(:provider) + user_params.delete(:extern_uid) + user_params.delete(:provider) - if user.update_attributes(declared_params(include_missing: false)) + if user.update_attributes(user_params) present user, with: Entities::UserFull else render_validation_error!(user) -- cgit v1.2.1 From 461195665b22291d5e57287efc0567b89a9ee262 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin <tomasz@maczukin.pl> Date: Mon, 21 Nov 2016 16:02:08 +0100 Subject: Add Gitlab::Ci::Build::Credentials module with build credentials abstraction --- lib/gitlab/ci/build/credentials.rb | 16 ---------------- lib/gitlab/ci/build/credentials/base.rb | 13 +++++++++++++ lib/gitlab/ci/build/credentials/factory.rb | 27 +++++++++++++++++++++++++++ lib/gitlab/ci/build/credentials/registry.rb | 24 ++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 16 deletions(-) delete mode 100644 lib/gitlab/ci/build/credentials.rb create mode 100644 lib/gitlab/ci/build/credentials/base.rb create mode 100644 lib/gitlab/ci/build/credentials/factory.rb create mode 100644 lib/gitlab/ci/build/credentials/registry.rb (limited to 'lib') diff --git a/lib/gitlab/ci/build/credentials.rb b/lib/gitlab/ci/build/credentials.rb deleted file mode 100644 index 14f9e8d7244..00000000000 --- a/lib/gitlab/ci/build/credentials.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Gitlab - module Ci - module Build - class Credentials - attr_accessor :type, :url, :username, :password - - def initialize(type, url, username, password) - @type = type - @url = url - @username = username - @password = password - end - end - end - end -end diff --git a/lib/gitlab/ci/build/credentials/base.rb b/lib/gitlab/ci/build/credentials/base.rb new file mode 100644 index 00000000000..29a7a27c963 --- /dev/null +++ b/lib/gitlab/ci/build/credentials/base.rb @@ -0,0 +1,13 @@ +module Gitlab + module Ci + module Build + module Credentials + class Base + def type + self.class.name.demodulize.underscore + end + end + end + end + end +end diff --git a/lib/gitlab/ci/build/credentials/factory.rb b/lib/gitlab/ci/build/credentials/factory.rb new file mode 100644 index 00000000000..2423aa8857d --- /dev/null +++ b/lib/gitlab/ci/build/credentials/factory.rb @@ -0,0 +1,27 @@ +module Gitlab + module Ci + module Build + module Credentials + class Factory + def initialize(build) + @build = build + end + + def create! + credentials.select(&:valid?) + end + + private + + def credentials + providers.map { |provider| provider.new(@build) } + end + + def providers + [Registry] + end + end + end + end + end +end diff --git a/lib/gitlab/ci/build/credentials/registry.rb b/lib/gitlab/ci/build/credentials/registry.rb new file mode 100644 index 00000000000..55eafcaed10 --- /dev/null +++ b/lib/gitlab/ci/build/credentials/registry.rb @@ -0,0 +1,24 @@ +module Gitlab + module Ci + module Build + module Credentials + class Registry < Base + attr_reader :username, :password + + def initialize(build) + @username = 'gitlab-ci-token' + @password = build.token + end + + def url + Gitlab.config.registry.host_port + end + + def valid? + Gitlab.config.registry.enabled + end + end + end + end + end +end -- cgit v1.2.1 From 5949f398f71d9cb540e42988c19fac2768c92eb8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> Date: Mon, 21 Nov 2016 21:03:18 +0200 Subject: Fix 500 error when group name ends with git Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> --- lib/gitlab/regex.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 47ea8b7e82e..c12358ceef4 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -9,7 +9,7 @@ module Gitlab # `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation # will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation. NAMESPACE_REGEX_STR_SIMPLE = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze - NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR_SIMPLE})(?<!\.git|\.atom)".freeze + NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?<!\.git|\.atom)'.freeze def namespace_regex @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze -- cgit v1.2.1 From 41aa605d4ed3d0d302c70ecd01052115f818f949 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski <ayufan@ayufan.eu> Date: Mon, 21 Nov 2016 19:40:04 +0100 Subject: Improve deploy command message --- lib/gitlab/chat_commands/deploy.rb | 15 ++++++++++++++- lib/mattermost/presenter.rb | 7 ++++++- 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb index c0b93cca68c..0eed1fce0dc 100644 --- a/lib/gitlab/chat_commands/deploy.rb +++ b/lib/gitlab/chat_commands/deploy.rb @@ -1,6 +1,8 @@ module Gitlab module ChatCommands class Deploy < BaseCommand + include Gitlab::Routing.url_helpers + def self.match(text) /\Adeploy\s+(?<from>.*)\s+to+\s+(?<to>.*)\z/.match(text) end @@ -25,7 +27,7 @@ module Gitlab return unless actions.present? if actions.one? - actions.first.play(current_user) + play!(from, to, actions.first) else Result.new(:error, 'Too many actions defined') end @@ -33,12 +35,23 @@ module Gitlab private + def play!(from, to, action) + new_action = action.play(current_user) + + Result.new(:success, "Deployment from #{from} to #{to} started. Follow the progress: #{url(new_action)}.") + end + def find_actions(from, to) environment = project.environments.find_by(name: from) return unless environment environment.actions_for(to).select(&:starts_environment?) end + + def url(subject) + polymorphic_url( + [ subject.project.namespace.becomes(Namespace), subject.project, subject ]) + end end end end diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb index 6b12081575d..67eda983a74 100644 --- a/lib/mattermost/presenter.rb +++ b/lib/mattermost/presenter.rb @@ -49,7 +49,12 @@ module Mattermost private def show_result(result) - ephemeral_response(result.message) + case result.type + when :success + in_channel_response(result.message) + else + ephemeral_response(result.message) + end end def not_found -- cgit v1.2.1 From 77cf855bb995eb8875b41d5683c30bae2d17f2f4 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov <mail@semyonpupkov.com> Date: Tue, 22 Nov 2016 00:15:46 +0500 Subject: Define common helper for describe pagination params in api --- lib/api/broadcast_messages.rb | 5 +++-- lib/api/commits.rb | 5 +++-- lib/api/deployments.rb | 5 +++-- lib/api/environments.rb | 5 +++-- lib/api/pagination_params.rb | 24 ++++++++++++++++++++++++ lib/api/pipelines.rb | 5 +++-- lib/api/variables.rb | 5 +++-- 7 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 lib/api/pagination_params.rb (limited to 'lib') diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb index b6281a7f0ac..1217002bf8e 100644 --- a/lib/api/broadcast_messages.rb +++ b/lib/api/broadcast_messages.rb @@ -1,5 +1,7 @@ module API class BroadcastMessages < Grape::API + include PaginationParams + before { authenticate! } before { authenticated_as_admin! } @@ -15,8 +17,7 @@ module API success Entities::BroadcastMessage end params do - optional :page, type: Integer, desc: 'Current page number' - optional :per_page, type: Integer, desc: 'Number of messages per page' + use :pagination end get do messages = BroadcastMessage.all diff --git a/lib/api/commits.rb b/lib/api/commits.rb index f412e1da1bf..0319d076ecb 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -3,6 +3,8 @@ require 'mime/types' module API # Projects commits API class Commits < Grape::API + include PaginationParams + before { authenticate! } before { authorize! :download_code, user_project } @@ -107,9 +109,8 @@ module API failure [[404, 'Not Found']] end params do + use :pagination requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' - optional :per_page, type: Integer, desc: 'The amount of items per page for paginaion' - optional :page, type: Integer, desc: 'The page number for pagination' end get ':id/repository/commits/:sha/comments' do commit = user_project.commit(params[:sha]) diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index f782bcaf7e9..c5feb49b22f 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -1,6 +1,8 @@ module API # Deployments RESTfull API endpoints class Deployments < Grape::API + include PaginationParams + before { authenticate! } params do @@ -12,8 +14,7 @@ module API success Entities::Deployment end params do - optional :page, type: Integer, desc: 'Page number of the current request' - optional :per_page, type: Integer, desc: 'Number of items per page' + use :pagination end get ':id/deployments' do authorize! :read_deployment, user_project diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 00c901937b1..80bbd9bb6e4 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -1,6 +1,8 @@ module API # Environments RESTfull API endpoints class Environments < Grape::API + include PaginationParams + before { authenticate! } params do @@ -12,8 +14,7 @@ module API success Entities::Environment end params do - optional :page, type: Integer, desc: 'Page number of the current request' - optional :per_page, type: Integer, desc: 'Number of items per page' + use :pagination end get ':id/environments' do authorize! :read_environment, user_project diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb new file mode 100644 index 00000000000..8c1e4381a74 --- /dev/null +++ b/lib/api/pagination_params.rb @@ -0,0 +1,24 @@ +module API + # Concern for declare pagination params. + # + # @example + # class CustomApiResource < Grape::API + # include PaginationParams + # + # params do + # use :pagination + # end + # end + module PaginationParams + extend ActiveSupport::Concern + + included do + helpers do + params :pagination do + optional :page, type: Integer, desc: 'Current page number' + optional :per_page, type: Integer, desc: 'Number of items per page' + end + end + end + end +end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 2a0c8e1f2c0..24cc0932181 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -1,5 +1,7 @@ module API class Pipelines < Grape::API + include PaginationParams + before { authenticate! } params do @@ -11,8 +13,7 @@ module API success Entities::Pipeline end params do - optional :page, type: Integer, desc: 'Page number of the current request' - optional :per_page, type: Integer, desc: 'Number of items per page' + use :pagination optional :scope, type: String, values: ['running', 'branches', 'tags'], desc: 'Either running, branches, or tags' end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index b9fb3c21dbb..90f904b8a12 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -1,6 +1,8 @@ module API # Projects variables API class Variables < Grape::API + include PaginationParams + before { authenticate! } before { authorize! :admin_build, user_project } @@ -13,8 +15,7 @@ module API success Entities::Variable end params do - optional :page, type: Integer, desc: 'The page number for pagination' - optional :per_page, type: Integer, desc: 'The value of items per page to show' + use :pagination end get ':id/variables' do variables = user_project.variables -- cgit v1.2.1 From cb11d3521ce99a5ce796e2fdf9e6d96a3e36791d Mon Sep 17 00:00:00 2001 From: Robert Schilling <rschilling@student.tugraz.at> Date: Tue, 22 Nov 2016 08:23:43 +0100 Subject: Fix IID filter for merge requests and milestones --- lib/api/merge_requests.rb | 8 +++----- lib/api/milestones.rb | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4176c7eec06..009913c6242 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -41,15 +41,13 @@ module API desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return merge requests sorted in `asc` or `desc` order.' - optional :iid, type: Integer, desc: 'The IID of the merge requests' + optional :iid, type: Array[Integer], desc: 'The IID of the merge requests' end get ":id/merge_requests" do authorize! :read_merge_request, user_project - merge_requests = user_project.merge_requests.inc_notes_with_associations - unless params[:iid].nil? - merge_requests = filter_by_iid(merge_requests, params[:iid]) - end + merge_requests = user_project.merge_requests.inc_notes_with_associations + merge_requests = filter_by_iid(merge_requests, params[:iid]) if params[:iid].present? merge_requests = case params[:state] diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 937c118779d..29bf73934d2 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -28,7 +28,7 @@ module API params do optional :state, type: String, values: %w[active closed all], default: 'all', desc: 'Return "active", "closed", or "all" milestones' - optional :iid, type: Integer, desc: 'The IID of the milestone' + optional :iid, type: Array[Integer], desc: 'The IID of the milestone' end get ":id/milestones" do authorize! :read_milestone, user_project -- cgit v1.2.1 From 3ff0575669ecda15c5e72bd2987715a998f97d82 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" <git@zjvandeweg.nl> Date: Mon, 21 Nov 2016 20:48:18 +0100 Subject: Issue creation now accepts trailing whitespace --- lib/gitlab/chat_commands/issue_create.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index 98338ebfa27..99c1382af44 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -2,7 +2,9 @@ module Gitlab module ChatCommands class IssueCreate < IssueCommand def self.match(text) - /\Aissue\s+create\s+(?<title>[^\n]*)\n*(?<description>.*)\z/.match(text) + # we can not match \n with the dot by passing the m modifier as than + # the title and description are not seperated + /\Aissue\s+create\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text) end def self.help_message @@ -15,7 +17,7 @@ module Gitlab def execute(match) title = match[:title] - description = match[:description] + description = match[:description].to_s.rstrip Issues::CreateService.new(project, current_user, title: title, description: description).execute end -- cgit v1.2.1 From c048d5d2d964292bddb567bbad6caffd8b275bfa Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon <grzesiek.bizon@gmail.com> Date: Tue, 22 Nov 2016 09:09:38 +0100 Subject: Fix entry lookup in CI config inheritance rules --- lib/gitlab/ci/config/entry/job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 20dcc024b4e..a55362f0b6b 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -108,7 +108,7 @@ module Gitlab self.class.nodes.each_key do |key| global_entry = deps[key] - job_entry = @entries[key] + job_entry = self[key] if global_entry.specified? && !job_entry.specified? @entries[key] = global_entry -- cgit v1.2.1 From 9e608b41a13a148f74fc2ab153bf5b97047fe1bc Mon Sep 17 00:00:00 2001 From: Robert Schilling <rschilling@student.tugraz.at> Date: Tue, 22 Nov 2016 08:27:21 +0100 Subject: Avoid helper call with default parameters --- lib/api/merge_requests.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4176c7eec06..15488b33f31 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -59,7 +59,7 @@ module API else merge_requests end - merge_requests = merge_requests.reorder(issuable_order_by => issuable_sort) + merge_requests = merge_requests.reorder(params[:order_by] => params[:sort]) present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project end -- cgit v1.2.1 From b938aa5cc83ffb51b516c7abfaaf6fe5e37031a6 Mon Sep 17 00:00:00 2001 From: James Lopez <james@jameslopez.es> Date: Wed, 23 Nov 2016 09:10:04 +0100 Subject: Fix and relevant spec for plan stage breaking with nil commits --- lib/gitlab/cycle_analytics/base_event.rb | 2 +- lib/gitlab/cycle_analytics/plan_event.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb index 486139b1687..53a148ad703 100644 --- a/lib/gitlab/cycle_analytics/base_event.rb +++ b/lib/gitlab/cycle_analytics/base_event.rb @@ -16,7 +16,7 @@ module Gitlab event_result.map do |event| serialize(event) if has_permission?(event['id']) - end + end.compact end def custom_query(_base_query); end diff --git a/lib/gitlab/cycle_analytics/plan_event.rb b/lib/gitlab/cycle_analytics/plan_event.rb index b1ae215f348..7c3f0e9989f 100644 --- a/lib/gitlab/cycle_analytics/plan_event.rb +++ b/lib/gitlab/cycle_analytics/plan_event.rb @@ -27,6 +27,8 @@ module Gitlab end def first_time_reference_commit(commits, event) + return nil if commits.blank? + YAML.load(commits).find do |commit| next unless commit[:committed_date] && event['first_mentioned_in_commit_at'] -- cgit v1.2.1 From cd78e02096ffe210ede99f90d794ce4ba3f727c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= <remy@rymai.me> Date: Tue, 22 Nov 2016 19:13:41 +0100 Subject: Ensure we sanitize branch names with path-unfriendly characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable <remy@rymai.me> --- lib/gitlab/ee_compat_check.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index f4d1505ea91..c8e36d8ff4a 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -149,7 +149,7 @@ module Gitlab end def ce_patch_name - @ce_patch_name ||= "#{ce_branch}.patch" + @ce_patch_name ||= patch_name_from_branch(ce_branch) end def ce_patch_full_path @@ -161,13 +161,17 @@ module Gitlab end def ee_patch_name - @ee_patch_name ||= "#{ee_branch}.patch" + @ee_patch_name ||= patch_name_from_branch(ee_branch) end def ee_patch_full_path @ee_patch_full_path ||= patches_dir.join(ee_patch_name) end + def patch_name_from_branch(branch_name) + branch_name.parameterize << '.patch' + end + def step(desc, cmd = nil) puts "\n=> #{desc}\n" -- cgit v1.2.1 From 3789cfe056c1d8a5fb91267cc2b1dd0f9f5902a9 Mon Sep 17 00:00:00 2001 From: Valery Sizov <valery@gitlab.com> Date: Tue, 15 Nov 2016 19:48:30 +0200 Subject: Add a starting date to milestones --- lib/api/entities.rb | 1 + lib/api/milestones.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 33cb6fd3704..7a724487e02 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -210,6 +210,7 @@ module API class Milestone < ProjectEntity expose :due_date + expose :start_date end class Issue < ProjectEntity diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 29bf73934d2..50d6109be3d 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -14,7 +14,8 @@ module API params :optional_params do optional :description, type: String, desc: 'The description of the milestone' - optional :due_date, type: String, desc: 'The due date of the milestone' + optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)' + optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)' end end -- cgit v1.2.1 From eff1b05ab1d50895be668be12de8239def648d97 Mon Sep 17 00:00:00 2001 From: Robert Schilling <rschilling@student.tugraz.at> Date: Tue, 22 Nov 2016 11:23:41 +0100 Subject: API: Add endpoint to delete a group share --- lib/api/projects.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lib') diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 6b856128c2e..ddfde178d30 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -438,6 +438,19 @@ module API end end + params do + requires :group_id, type: Integer, desc: 'The ID of the group' + end + delete ":id/share/:group_id" do + authorize! :admin_project, user_project + + link = user_project.project_group_links.find_by(group_id: params[:group_id]) + not_found!('Group Link') unless link + + link.destroy + no_content! + end + # Upload a file # # Parameters: -- cgit v1.2.1 From 6683fdcfb0ae4ceb368b6f5f63dde0a10a4a3e1b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> Date: Mon, 14 Nov 2016 16:55:31 +0200 Subject: Add nested groups support to the routing Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> --- lib/constraints/constrainer_helper.rb | 15 --------------- lib/constraints/group_url_constrainer.rb | 20 +++++++++++--------- lib/constraints/project_url_constrainer.rb | 13 +++++++++++++ lib/constraints/user_url_constrainer.rb | 12 +----------- lib/gitlab/regex.rb | 14 ++++++++++++-- 5 files changed, 37 insertions(+), 37 deletions(-) delete mode 100644 lib/constraints/constrainer_helper.rb create mode 100644 lib/constraints/project_url_constrainer.rb (limited to 'lib') diff --git a/lib/constraints/constrainer_helper.rb b/lib/constraints/constrainer_helper.rb deleted file mode 100644 index ab07a6793d9..00000000000 --- a/lib/constraints/constrainer_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ConstrainerHelper - def extract_resource_path(path) - id = path.dup - id.sub!(/\A#{relative_url_root}/, '') if relative_url_root - id.sub(/\A\/+/, '').sub(/\/+\z/, '').sub(/.atom\z/, '') - end - - private - - def relative_url_root - if defined?(Gitlab::Application.config.relative_url_root) - Gitlab::Application.config.relative_url_root - end - end -end diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb index 2af6e1a11c8..5711d96a586 100644 --- a/lib/constraints/group_url_constrainer.rb +++ b/lib/constraints/group_url_constrainer.rb @@ -1,15 +1,17 @@ -require_relative 'constrainer_helper' - class GroupUrlConstrainer - include ConstrainerHelper - def matches?(request) - id = extract_resource_path(request.path) + id = request.params[:id] + + return false unless valid?(id) + + Group.find_by(path: id).present? + end + + private - if id =~ Gitlab::Regex.namespace_regex - Group.find_by(path: id).present? - else - false + def valid?(id) + id.split('/').all? do |namespace| + NamespaceValidator.valid?(namespace) end end end diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb new file mode 100644 index 00000000000..730b05bed97 --- /dev/null +++ b/lib/constraints/project_url_constrainer.rb @@ -0,0 +1,13 @@ +class ProjectUrlConstrainer + def matches?(request) + namespace_path = request.params[:namespace_id] + project_path = request.params[:project_id] || request.params[:id] + full_path = namespace_path + '/' + project_path + + unless ProjectPathValidator.valid?(project_path) + return false + end + + Project.find_with_namespace(full_path).present? + end +end diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb index 4d722ad5af2..9ab5bcb12ff 100644 --- a/lib/constraints/user_url_constrainer.rb +++ b/lib/constraints/user_url_constrainer.rb @@ -1,15 +1,5 @@ -require_relative 'constrainer_helper' - class UserUrlConstrainer - include ConstrainerHelper - def matches?(request) - id = extract_resource_path(request.path) - - if id =~ Gitlab::Regex.namespace_regex - User.find_by('lower(username) = ?', id.downcase).present? - else - false - end + User.find_by_username(request.params[:username]).present? end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index c12358ceef4..a06cf6a989c 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -8,8 +8,10 @@ module Gitlab # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_SIMPLE` serves as a Javascript-compatible version of # `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation # will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation. - NAMESPACE_REGEX_STR_SIMPLE = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze + PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze + NAMESPACE_REGEX_STR_SIMPLE = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?<!\.git|\.atom)'.freeze + PROJECT_REGEX_STR = PATH_REGEX_STR + '(?<!\.git|\.atom)'.freeze def namespace_regex @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze @@ -42,7 +44,15 @@ module Gitlab end def project_path_regex - @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git|\.atom)\z/.freeze + @project_path_regex ||= /\A#{PROJECT_REGEX_STR}\z/.freeze + end + + def project_route_regex + @project_route_regex ||= /#{PROJECT_REGEX_STR}/.freeze + end + + def project_git_route_regex + @project_route_git_regex ||= /#{PATH_REGEX_STR}\.git/.freeze end def project_path_regex_message -- cgit v1.2.1 From d2985eb57201a98b3bb9c1abebdabd2061eabd10 Mon Sep 17 00:00:00 2001 From: Robert Schilling <rschilling@student.tugraz.at> Date: Wed, 23 Nov 2016 13:34:08 +0100 Subject: Grapify the sidekiq metrics API --- lib/api/sidekiq_metrics.rb | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) (limited to 'lib') diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb index d3d6827dc54..11f2b40269a 100644 --- a/lib/api/sidekiq_metrics.rb +++ b/lib/api/sidekiq_metrics.rb @@ -39,50 +39,22 @@ module API end end - # Get Sidekiq Queue metrics - # - # Parameters: - # None - # - # Example: - # GET /sidekiq/queue_metrics - # + desc 'Get the Sidekiq queue metrics' get 'sidekiq/queue_metrics' do { queues: queue_metrics } end - # Get Sidekiq Process metrics - # - # Parameters: - # None - # - # Example: - # GET /sidekiq/process_metrics - # + desc 'Get the Sidekiq process metrics' get 'sidekiq/process_metrics' do { processes: process_metrics } end - # Get Sidekiq Job statistics - # - # Parameters: - # None - # - # Example: - # GET /sidekiq/job_stats - # + desc 'Get the Sidekiq job statistics' get 'sidekiq/job_stats' do { jobs: job_stats } end - # Get Sidekiq Compound metrics. Includes all previous metrics - # - # Parameters: - # None - # - # Example: - # GET /sidekiq/compound_metrics - # + desc 'Get the Sidekiq Compound metrics. Includes queue, process, and job statistics' get 'sidekiq/compound_metrics' do { queues: queue_metrics, processes: process_metrics, jobs: job_stats } end -- cgit v1.2.1 From 9ad2dba250e3facc18ff2f21217ebe006451dd8c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin <godfat@godfat.org> Date: Thu, 24 Nov 2016 02:33:55 +0800 Subject: Use Commit#author so we share logic and cache Closes #24900 --- lib/gitlab/identifier.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb index c5acf18beb5..94678b6ec40 100644 --- a/lib/gitlab/identifier.rb +++ b/lib/gitlab/identifier.rb @@ -21,10 +21,8 @@ module Gitlab return if !commit || !commit.author_email - email = commit.author_email - - identify_with_cache(:email, email) do - User.find_by_any_email(email) + identify_with_cache(:email, commit.author_email) do + commit.author end end -- cgit v1.2.1