diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-07 12:10:27 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-07 12:10:27 +0000 |
commit | 53f456b167f19877d663ee6ed510673cebee0f91 (patch) | |
tree | fcc0bb52b79c195bf0eda100cc5d7e7a16dc0c0b /lib | |
parent | e8a31d8dc2afd673ca50d74d26edab0a0fec83ca (diff) | |
download | gitlab-ce-53f456b167f19877d663ee6ed510673cebee0f91.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'lib')
21 files changed, 484 insertions, 115 deletions
diff --git a/lib/gitlab/ci/build/cache.rb b/lib/gitlab/ci/build/cache.rb index 4fcb5168847..375e6b4a96f 100644 --- a/lib/gitlab/ci/build/cache.rb +++ b/lib/gitlab/ci/build/cache.rb @@ -7,39 +7,22 @@ module Gitlab include ::Gitlab::Utils::StrongMemoize def initialize(cache, pipeline) - if multiple_cache_per_job? - cache = Array.wrap(cache) - @cache = cache.map do |cache| - Gitlab::Ci::Pipeline::Seed::Build::Cache - .new(pipeline, cache) - end - else - @cache = Gitlab::Ci::Pipeline::Seed::Build::Cache - .new(pipeline, cache) + cache = Array.wrap(cache) + @cache = cache.map do |cache| + Gitlab::Ci::Pipeline::Seed::Build::Cache + .new(pipeline, cache) end end def cache_attributes strong_memoize(:cache_attributes) do - if multiple_cache_per_job? - if @cache.empty? - {} - else - { options: { cache: @cache.map(&:attributes) } } - end + if @cache.empty? + {} else - @cache.build_attributes + { options: { cache: @cache.map(&:attributes) } } end end end - - private - - def multiple_cache_per_job? - strong_memoize(:multiple_cache_per_job) do - ::Gitlab::Ci::Features.multiple_cache_per_job? - end - end end end end diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb index f9688c500d2..ab79add688b 100644 --- a/lib/gitlab/ci/config/entry/cache.rb +++ b/lib/gitlab/ci/config/entry/cache.rb @@ -4,88 +4,52 @@ module Gitlab module Ci class Config module Entry - ## - # Entry that represents a cache configuration - # - class Cache < ::Gitlab::Config::Entry::Simplifiable - strategy :Caches, if: -> (config) { Feature.enabled?(:multiple_cache_per_job, default_enabled: :yaml) } - strategy :Cache, if: -> (config) { Feature.disabled?(:multiple_cache_per_job, default_enabled: :yaml) } - - class Caches < ::Gitlab::Config::Entry::ComposableArray - include ::Gitlab::Config::Entry::Validatable - - MULTIPLE_CACHE_LIMIT = 4 - - validations do - validate do - unless config.is_a?(Hash) || config.is_a?(Array) - errors.add(:config, 'can only be a Hash or an Array') - end - - if config.is_a?(Array) && config.count > MULTIPLE_CACHE_LIMIT - errors.add(:config, "no more than #{MULTIPLE_CACHE_LIMIT} caches can be created") - end - end - end - - def initialize(*args) - super - - @key = nil - end - - def composable_class - Entry::Cache::Cache + class Cache < ::Gitlab::Config::Entry::Node + include ::Gitlab::Config::Entry::Configurable + include ::Gitlab::Config::Entry::Validatable + include ::Gitlab::Config::Entry::Attributable + + ALLOWED_KEYS = %i[key untracked paths when policy].freeze + ALLOWED_POLICY = %w[pull-push push pull].freeze + DEFAULT_POLICY = 'pull-push' + ALLOWED_WHEN = %w[on_success on_failure always].freeze + DEFAULT_WHEN = 'on_success' + + validations do + validates :config, type: Hash, allowed_keys: ALLOWED_KEYS + validates :policy, + inclusion: { in: ALLOWED_POLICY, message: 'should be pull-push, push, or pull' }, + allow_blank: true + + with_options allow_nil: true do + validates :when, + inclusion: { + in: ALLOWED_WHEN, + message: 'should be on_success, on_failure or always' + } end end - class Cache < ::Gitlab::Config::Entry::Node - include ::Gitlab::Config::Entry::Configurable - include ::Gitlab::Config::Entry::Validatable - include ::Gitlab::Config::Entry::Attributable - - ALLOWED_KEYS = %i[key untracked paths when policy].freeze - ALLOWED_POLICY = %w[pull-push push pull].freeze - DEFAULT_POLICY = 'pull-push' - ALLOWED_WHEN = %w[on_success on_failure always].freeze - DEFAULT_WHEN = 'on_success' + entry :key, Entry::Key, + description: 'Cache key used to define a cache affinity.' - validations do - validates :config, type: Hash, allowed_keys: ALLOWED_KEYS - validates :policy, - inclusion: { in: ALLOWED_POLICY, message: 'should be pull-push, push, or pull' }, - allow_blank: true - - with_options allow_nil: true do - validates :when, - inclusion: { - in: ALLOWED_WHEN, - message: 'should be on_success, on_failure or always' - } - end - end + entry :untracked, ::Gitlab::Config::Entry::Boolean, + description: 'Cache all untracked files.' - entry :key, Entry::Key, - description: 'Cache key used to define a cache affinity.' + entry :paths, Entry::Paths, + description: 'Specify which paths should be cached across builds.' - entry :untracked, ::Gitlab::Config::Entry::Boolean, - description: 'Cache all untracked files.' + attributes :policy, :when - entry :paths, Entry::Paths, - description: 'Specify which paths should be cached across builds.' + def value + result = super - attributes :policy, :when + result[:key] = key_value + result[:policy] = policy || DEFAULT_POLICY + # Use self.when to avoid conflict with reserved word + result[:when] = self.when || DEFAULT_WHEN - def value - result = super - - result[:key] = key_value - result[:policy] = policy || DEFAULT_POLICY - # Use self.when to avoid conflict with reserved word - result[:when] = self.when || DEFAULT_WHEN - - result - end + result end class UnknownStrategy < ::Gitlab::Config::Entry::Node diff --git a/lib/gitlab/ci/config/entry/caches.rb b/lib/gitlab/ci/config/entry/caches.rb new file mode 100644 index 00000000000..75240599c9c --- /dev/null +++ b/lib/gitlab/ci/config/entry/caches.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module Entry + ## + # Entry that represents caches configuration + # + class Caches < ::Gitlab::Config::Entry::ComposableArray + include ::Gitlab::Config::Entry::Validatable + + MULTIPLE_CACHE_LIMIT = 4 + + validations do + validate do + unless config.is_a?(Hash) || config.is_a?(Array) + errors.add(:config, 'can only be a Hash or an Array') + end + + if config.is_a?(Array) && config.count > MULTIPLE_CACHE_LIMIT + errors.add(:config, "no more than #{MULTIPLE_CACHE_LIMIT} caches can be created") + end + end + end + + def initialize(*args) + super + + @key = nil + end + + def composable_class + Entry::Cache + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/default.rb b/lib/gitlab/ci/config/entry/default.rb index ab493ff7d78..eaaf9f69102 100644 --- a/lib/gitlab/ci/config/entry/default.rb +++ b/lib/gitlab/ci/config/entry/default.rb @@ -37,7 +37,7 @@ module Gitlab description: 'Script that will be executed after each job.', inherit: true - entry :cache, Entry::Cache, + entry :cache, Entry::Caches, description: 'Configure caching between build jobs.', inherit: true diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index a20b802be58..d76cab5d7c0 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -64,7 +64,7 @@ module Gitlab description: 'Commands that will be executed when finishing job.', inherit: true - entry :cache, Entry::Cache, + entry :cache, Entry::Caches, description: 'Cache definition for this job.', inherit: true diff --git a/lib/gitlab/ci/config/entry/root.rb b/lib/gitlab/ci/config/entry/root.rb index 54ef84b965a..e6290ef2479 100644 --- a/lib/gitlab/ci/config/entry/root.rb +++ b/lib/gitlab/ci/config/entry/root.rb @@ -61,7 +61,7 @@ module Gitlab description: 'Deprecated: stages for this pipeline.', reserved: true - entry :cache, Entry::Cache, + entry :cache, Entry::Caches, description: 'Configure caching between build jobs.', reserved: true diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index 64eaffa1e82..162dbc4bcc7 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -56,8 +56,8 @@ module Gitlab ::Feature.enabled?(:codequality_mr_diff, project, default_enabled: false) end - def self.multiple_cache_per_job? - ::Feature.enabled?(:multiple_cache_per_job, default_enabled: :yaml) + def self.gldropdown_tags_enabled? + ::Feature.enabled?(:gldropdown_tags, default_enabled: :yaml) end end end diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml index b6f98d5034e..f768bcae6d3 100644 --- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml @@ -157,11 +157,6 @@ gosec-sast: mobsf-android-sast: extends: .sast-analyzer - services: - # this version must match with analyzer version mentioned in: https://gitlab.com/gitlab-org/security-products/analyzers/mobsf/-/blob/master/Dockerfile - # Unfortunately, we need to keep track of mobsf version in 2 different places for now. - - name: opensecurity/mobile-security-framework-mobsf:v3.4.0 - alias: mobsf image: name: "$SAST_ANALYZER_IMAGE" variables: @@ -169,7 +164,6 @@ mobsf-android-sast: # override the analyzer image with a custom value. This may be subject to change or # breakage across GitLab releases. SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/mobsf:$SAST_ANALYZER_IMAGE_TAG" - MOBSF_API_KEY: key rules: - if: $SAST_DISABLED when: never @@ -183,11 +177,6 @@ mobsf-android-sast: mobsf-ios-sast: extends: .sast-analyzer - services: - # this version must match with analyzer version mentioned in: https://gitlab.com/gitlab-org/security-products/analyzers/mobsf/-/blob/master/Dockerfile - # Unfortunately, we need to keep track of mobsf version in 2 different places for now. - - name: opensecurity/mobile-security-framework-mobsf:v3.4.0 - alias: mobsf image: name: "$SAST_ANALYZER_IMAGE" variables: @@ -195,7 +184,6 @@ mobsf-ios-sast: # override the analyzer image with a custom value. This may be subject to change or # breakage across GitLab releases. SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/mobsf:$SAST_ANALYZER_IMAGE_TAG" - MOBSF_API_KEY: key rules: - if: $SAST_DISABLED when: never diff --git a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb index 4a62e83e8e9..03b2a1086bb 100644 --- a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb +++ b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb @@ -19,7 +19,7 @@ module Gitlab def execute return if @spend_arg.blank? - return [get_time, DateTime.now.to_date] unless date_present? + return [get_time, DateTime.current] unless date_present? return unless valid_date? [get_time, get_date] diff --git a/lib/gitlab/subscription_portal.rb b/lib/gitlab/subscription_portal.rb index 1ba1d70a13c..ab2e1404cd2 100644 --- a/lib/gitlab/subscription_portal.rb +++ b/lib/gitlab/subscription_portal.rb @@ -9,8 +9,13 @@ module Gitlab def self.subscriptions_url ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url) end + + def self.payment_form_url + "#{self.subscriptions_url}/payment_forms/cc_validation" + end end end Gitlab::SubscriptionPortal.prepend_mod Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL = Gitlab::SubscriptionPortal.subscriptions_url.freeze +Gitlab::SubscriptionPortal::PAYMENT_FORM_URL = Gitlab::SubscriptionPortal.payment_form_url.freeze diff --git a/lib/gitlab/usage/metric_definition.rb b/lib/gitlab/usage/metric_definition.rb index 9d047b8a255..c0cf4a4db4b 100644 --- a/lib/gitlab/usage/metric_definition.rb +++ b/lib/gitlab/usage/metric_definition.rb @@ -65,6 +65,10 @@ module Gitlab @definitions ||= load_all! end + def all + @all ||= definitions.map { |_key_path, definition| definition } + end + def schemer @schemer ||= ::JSONSchemer.schema(Pathname.new(METRIC_SCHEMA_PATH)) end diff --git a/lib/gitlab/usage/metrics/instrumentations/base_metric.rb b/lib/gitlab/usage/metrics/instrumentations/base_metric.rb new file mode 100644 index 00000000000..29b44f2bd0a --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/base_metric.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class BaseMetric + include Gitlab::Utils::UsageData + + attr_reader :time_frame + + def initialize(time_frame:) + @time_frame = time_frame + end + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb new file mode 100644 index 00000000000..f83f90dea03 --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class DatabaseMetric < BaseMetric + # Usage Example + # + # class CountUsersCreatingIssuesMetric < DatabaseMetric + # operation :distinct_count, column: :author_id + # + # relation do |database_time_constraints| + # ::Issue.where(database_time_constraints) + # end + # end + class << self + def start(&block) + @metric_start = block + end + + def finish(&block) + @metric_finish = block + end + + def relation(&block) + @metric_relation = block + end + + def operation(symbol, column: nil) + @metric_operation = symbol + @column = column + end + + attr_reader :metric_operation, :metric_relation, :metric_start, :metric_finish, :column + end + + def value + method(self.class.metric_operation) + .call(relation, + self.class.column, + start: self.class.metric_start&.call, + finish: self.class.metric_finish&.call) + end + + def relation + self.class.metric_relation.call.where(time_constraints) + end + + private + + def time_constraints + case time_frame + when '28d' + { created_at: 30.days.ago..2.days.ago } + when 'all' + {} + when 'none' + nil + else + raise "Unknown time frame: #{time_frame} for DatabaseMetric" + end + end + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb b/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb new file mode 100644 index 00000000000..7c97cc37d17 --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class GenericMetric < BaseMetric + # Usage example + # + # class UuidMetric < GenericMetric + # value do + # Gitlab::CurrentSettings.uuid + # end + # end + class << self + def value(&block) + @metric_value = block + end + + attr_reader :metric_value + end + + def value + alt_usage_data do + self.class.metric_value.call + end + end + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb b/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb new file mode 100644 index 00000000000..ed0ddb1cbbe --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class RedisHLLMetric < BaseMetric + # Usage example + # + # class CountUsersVisitingAnalyticsValuestreamMetric < RedisHLLMetric + # event_names :g_analytics_valuestream + # end + class << self + def event_names(events = nil) + @mentric_events = events + end + + attr_reader :metric_events + end + + def value + redis_usage_data do + event_params = time_constraints.merge(event_names: self.class.metric_events) + + Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**event_params) + end + end + + private + + def time_constraints + case time_frame + when '28d' + { start_date: 4.weeks.ago.to_date, end_date: Date.current } + when '7d' + { start_date: 7.days.ago.to_date, end_date: Date.current } + else + raise "Unknown time frame: #{time_frame} for TimeConstraint" + end + end + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/instrumentations/uuid_metric.rb b/lib/gitlab/usage/metrics/instrumentations/uuid_metric.rb new file mode 100644 index 00000000000..58547b5383a --- /dev/null +++ b/lib/gitlab/usage/metrics/instrumentations/uuid_metric.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + module Instrumentations + class UuidMetric < GenericMetric + value do + Gitlab::CurrentSettings.uuid + end + end + end + end + end +end diff --git a/lib/gitlab/usage/metrics/key_path_processor.rb b/lib/gitlab/usage/metrics/key_path_processor.rb new file mode 100644 index 00000000000..dbe574d5838 --- /dev/null +++ b/lib/gitlab/usage/metrics/key_path_processor.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module Metrics + class KeyPathProcessor + class << self + def process(key_path, value) + unflatten(key_path.split('.'), value) + end + + private + + def unflatten(keys, value) + loop do + value = { keys.pop.to_sym => value } + + break if keys.blank? + end + + value + end + end + end + end + end +end diff --git a/lib/gitlab/usage_data_metrics.rb b/lib/gitlab/usage_data_metrics.rb new file mode 100644 index 00000000000..4f162c28f1c --- /dev/null +++ b/lib/gitlab/usage_data_metrics.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Gitlab + class UsageDataMetrics + class << self + # Build the Usage Ping JSON payload from metrics YAML definitions which have instrumentation class set + def uncached_data + ::Gitlab::Usage::MetricDefinition.all.map do |definition| + instrumentation_class = definition.attributes[:instrumentation_class] + + if instrumentation_class.present? + metric_value = instrumentation_class.constantize.new(time_frame: definition.attributes[:time_frame]).value + + metric_payload(definition.key_path, metric_value) + else + {} + end + end.reduce({}, :deep_merge) + end + + private + + def metric_payload(key_path, value) + ::Gitlab::Usage::Metrics::KeyPathProcessor.process(key_path, value) + end + end + end +end diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb new file mode 100644 index 00000000000..8aec769d8d5 --- /dev/null +++ b/lib/sidebars/projects/menus/settings_menu.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class SettingsMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + return false unless can?(context.current_user, :admin_project, context.project) + + add_item(general_menu_item) + add_item(integrations_menu_item) + add_item(webhooks_menu_item) + add_item(access_tokens_menu_item) + add_item(repository_menu_item) + add_item(ci_cd_menu_item) + add_item(operations_menu_item) + add_item(pages_menu_item) + add_item(packages_and_registries_menu_item) + + true + end + + override :link + def link + edit_project_path(context.project) + end + + override :title + def title + _('Settings') + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-settings-link' + } + end + + override :sprite_icon + def sprite_icon + 'settings' + end + + private + + def general_menu_item + ::Sidebars::MenuItem.new( + title: _('General'), + link: edit_project_path(context.project), + active_routes: { path: 'projects#edit' }, + item_id: :general + ) + end + + def integrations_menu_item + ::Sidebars::MenuItem.new( + title: _('Integrations'), + link: project_settings_integrations_path(context.project), + active_routes: { path: %w[integrations#show services#edit] }, + item_id: :integrations + ) + end + + def webhooks_menu_item + ::Sidebars::MenuItem.new( + title: _('Webhooks'), + link: project_hooks_path(context.project), + active_routes: { path: %w[hooks#index hooks#edit hook_logs#show] }, + item_id: :webhooks + ) + end + + def access_tokens_menu_item + return unless can?(context.current_user, :read_resource_access_tokens, context.project) + + ::Sidebars::MenuItem.new( + title: _('Access Tokens'), + link: project_settings_access_tokens_path(context.project), + active_routes: { path: 'access_tokens#index' }, + item_id: :access_tokens + ) + end + + def repository_menu_item + ::Sidebars::MenuItem.new( + title: _('Repository'), + link: project_settings_repository_path(context.project), + active_routes: { path: 'repository#show' }, + item_id: :repository + ) + end + + def ci_cd_menu_item + return if context.project.archived? + return unless context.project.feature_available?(:builds, context.current_user) + + ::Sidebars::MenuItem.new( + title: _('CI/CD'), + link: project_settings_ci_cd_path(context.project), + active_routes: { path: 'ci_cd#show' }, + item_id: :ci_cd + ) + end + + def operations_menu_item + return if context.project.archived? + return unless can?(context.current_user, :admin_operations, context.project) + + ::Sidebars::MenuItem.new( + title: _('Operations'), + link: project_settings_operations_path(context.project), + active_routes: { path: 'operations#show' }, + item_id: :operations + ) + end + + def pages_menu_item + return unless context.project.pages_available? + + ::Sidebars::MenuItem.new( + title: _('Pages'), + link: project_pages_path(context.project), + active_routes: { path: 'pages#show' }, + item_id: :pages + ) + end + + def packages_and_registries_menu_item + return unless Gitlab.config.registry.enabled + return if Feature.disabled?(:sidebar_refactor, context.current_user) + return unless can?(context.current_user, :destroy_container_image, context.project) + + ::Sidebars::MenuItem.new( + title: _('Packages & Registries'), + link: project_settings_packages_and_registries_path(context.project), + active_routes: { path: 'packages_and_registries#index' }, + item_id: :packages_and_registries + ) + end + end + end + end +end diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb index bc37eb262b8..3be2a2b2b71 100644 --- a/lib/sidebars/projects/panel.rb +++ b/lib/sidebars/projects/panel.rb @@ -24,6 +24,7 @@ module Sidebars add_menu(Sidebars::Projects::Menus::ExternalWikiMenu.new(context)) add_menu(Sidebars::Projects::Menus::SnippetsMenu.new(context)) add_menu(Sidebars::Projects::Menus::MembersMenu.new(context)) + add_menu(Sidebars::Projects::Menus::SettingsMenu.new(context)) end override :render_raw_menus_partial diff --git a/lib/tasks/gitlab/usage_data.rake b/lib/tasks/gitlab/usage_data.rake index 95072444fcf..0ad50c0fa53 100644 --- a/lib/tasks/gitlab/usage_data.rake +++ b/lib/tasks/gitlab/usage_data.rake @@ -29,5 +29,10 @@ namespace :gitlab do items = Gitlab::Usage::MetricDefinition.definitions Gitlab::Usage::Docs::Renderer.new(items).write end + + desc 'GitLab | UsageDataMetrics | Generate usage ping from metrics definition YAML files in JSON' + task generate_from_yaml: :environment do + puts Gitlab::Json.pretty_generate(Gitlab::UsageDataMetrics.uncached_data) + end end end |