diff --git a/doc/development/internal_analytics/metrics/metrics_lifecycle.md b/doc/development/internal_analytics/metrics/metrics_lifecycle.md index 6899f31a2b7a49511e6b7b7ffe375fef60927a18..0c1bb5a1b9e5ad7639a03694f8b7ed630831e06a 100644 --- a/doc/development/internal_analytics/metrics/metrics_lifecycle.md +++ b/doc/development/internal_analytics/metrics/metrics_lifecycle.md @@ -57,3 +57,25 @@ Currently, the [Metrics Dictionary](https://metrics.gitlab.com/) is built automa Do not remove the metric's YAML definition altogether. Some self-managed instances might not immediately update to the latest version of GitLab, and therefore continue to report the removed metric. The Analytics Instrumentation team requires a record of all removed metrics to identify and filter them. + +## Group name changes + +When the name of a group that owns events or metrics is changed, the `product_group` property should be updated in all metric and event definitions belonging to that group. + +The `product_group_renamer` script can update all the definitions so you do not have to do it manually. + +For example, if the group 5-min-app was renamed to 2-min-app, you can update the relevant files like this: + +```shell +$ ruby scripts/internal_events/product_group_renamer.rb 5-min-app 2-min-app +Updated '5-min-app' to '2-min-app' in 3 files + +Updated files: + config/metrics/schema/product_groups.json + config/metrics/counts_28d/20210216184517_p_ci_templates_5_min_production_app_monthly.yml + config/metrics/counts_7d/20210216184515_p_ci_templates_5_min_production_app_weekly.yml +``` + +After running the script, you must commit all the modified files to Git and create a merge request. + +If a group is split into multiple groups, you need to manually update the product_group. diff --git a/scripts/internal_events/product_group_renamer.rb b/scripts/internal_events/product_group_renamer.rb new file mode 100644 index 0000000000000000000000000000000000000000..2f2680c4172a7b7566559d2506270c2bd2bb7421 --- /dev/null +++ b/scripts/internal_events/product_group_renamer.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +# !/usr/bin/env ruby +# +# Update group name in all relevant metric and event definition after a group name change. + +require 'json' +PRODUCT_GROUPS_SCHEMA_PATH = 'config/metrics/schema/product_groups.json' +ALL_METRIC_AND_EVENT_DEFINITIONS_GLOB = "{ee/,}config/{metrics/*,events}/*.yml" + +class ProductGroupRenamer + def initialize(schema_path, definitions_glob) + @schema_path = schema_path + @definitions_glob = definitions_glob + end + + def rename_product_group(old_name, new_name) + changed_files = [] + # Rename the product group in the schema + + current_schema = File.read(@schema_path) + product_group_schema = JSON.parse(current_schema) + + product_group_schema["enum"].delete(old_name) + product_group_schema["enum"].push(new_name) unless product_group_schema["enum"].include?(new_name) + product_group_schema["enum"].sort! + + new_schema = "#{JSON.pretty_generate(product_group_schema)}\n" + if new_schema != current_schema + File.write(@schema_path, new_schema) + changed_files << @schema_path + end + + # Rename product group in all metric and event definitions + Dir.glob(@definitions_glob).each do |file_path| + file_content = File.read(File.expand_path(file_path)) + + new_content = file_content.gsub(/product_group:\s*['"]?#{old_name}['"]?$/, "product_group: #{new_name}") + + if new_content != file_content + File.write(file_path, new_content) + changed_files << file_path + end + end + + changed_files + end +end + +if $PROGRAM_NAME == __FILE__ + if ARGV.length != 2 + puts <<~TEXT + Usage: + When a group is renamed, this script replaces the value for "product_group" in all matching event & metric definitions. + + Format: + ruby #{$PROGRAM_NAME} OLD_NAME NEW_NAME + + Example: + ruby #{$PROGRAM_NAME} pipeline_authoring renamed_pipeline_authoring + TEXT + exit + end + + old_name = ARGV[0] + new_name = ARGV[1] + + changed_files = ProductGroupRenamer + .new(PRODUCT_GROUPS_SCHEMA_PATH, ALL_METRIC_AND_EVENT_DEFINITIONS_GLOB) + .rename_product_group(old_name, new_name) + + puts "Updated '#{old_name}' to '#{new_name}' in #{changed_files.length} files" + puts + + if changed_files.any? + puts "Updated files:" + changed_files.each do |file_path| + puts " #{file_path}" + end + end +end diff --git a/spec/fixtures/scripts/product_group_renamer/event_definition.yml b/spec/fixtures/scripts/product_group_renamer/event_definition.yml new file mode 100644 index 0000000000000000000000000000000000000000..dd4b48e762a6a8e8e91e5da57f230fe9748b9a5c --- /dev/null +++ b/spec/fixtures/scripts/product_group_renamer/event_definition.yml @@ -0,0 +1,18 @@ +--- +description: Engineer uses Internal Event CLI to define a new event +internal_events: true +action: internal_events_cli_used +identifiers: +- project +- namespace +- user +product_group: a_group_name +milestone: '16.6' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149010 +distributions: +- ce +- ee +tiers: +- free +- premium +- ultimate diff --git a/spec/fixtures/scripts/product_group_renamer/event_definition_from_another_group.yml b/spec/fixtures/scripts/product_group_renamer/event_definition_from_another_group.yml new file mode 100644 index 0000000000000000000000000000000000000000..57bf3a1b779c6d31ab2c67fb82e790680c7139f6 --- /dev/null +++ b/spec/fixtures/scripts/product_group_renamer/event_definition_from_another_group.yml @@ -0,0 +1,18 @@ +--- +description: Engineer uses Internal Event CLI to define a new event +internal_events: true +action: internal_events_cli_used +identifiers: +- project +- namespace +- user +product_group: another_group +milestone: '17.0' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149010 +distributions: +- ce +- ee +tiers: +- free +- premium +- ultimate diff --git a/spec/fixtures/scripts/product_group_renamer/metric_definition.yml b/spec/fixtures/scripts/product_group_renamer/metric_definition.yml new file mode 100644 index 0000000000000000000000000000000000000000..12a581501da789096f259c6c3cdcce633bdd075a --- /dev/null +++ b/spec/fixtures/scripts/product_group_renamer/metric_definition.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.count_total_internal_events_cli_used +description: Total count of when an event was defined using the CLI +product_group: a_group_name +performance_indicator_type: [] +value_type: number +status: active +milestone: '17.0' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149010 +time_frame: all +data_source: internal_events +data_category: optional +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +events: +- name: internal_events_cli_used diff --git a/spec/fixtures/scripts/product_group_renamer/product_groups.json b/spec/fixtures/scripts/product_group_renamer/product_groups.json new file mode 100644 index 0000000000000000000000000000000000000000..99fd56a0465f908d7f996006336da5890c4404e7 --- /dev/null +++ b/spec/fixtures/scripts/product_group_renamer/product_groups.json @@ -0,0 +1,7 @@ +{ + "type": "string", + "enum": [ + "a_better_group_name", + "another_group_name" + ] +} diff --git a/spec/scripts/internal_events/product_group_renamer_spec.rb b/spec/scripts/internal_events/product_group_renamer_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..98d847d608837f24785bcf17105e3ada7311f851 --- /dev/null +++ b/spec/scripts/internal_events/product_group_renamer_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative '../../../scripts/internal_events/product_group_renamer' + +RSpec.describe ProductGroupRenamer, feature_category: :service_ping do + let(:renamer) { described_class.new(schema_path, definitions_glob) } + + context 'with real definitions', :aggregate_failures do + let(:schema_path) { PRODUCT_GROUPS_SCHEMA_PATH } + let(:definitions_glob) { ALL_METRIC_AND_EVENT_DEFINITIONS_GLOB } + + it 'reads all definitions files' do + allow(File).to receive(:read).and_call_original + + Gitlab::Tracking::EventDefinition.definitions.each do |event_definition| + expect(File).to receive(:read).with(event_definition.path) + expect(File).not_to receive(:write).with(event_definition.path) + end + + Gitlab::Usage::MetricDefinition.definitions.each_value do |metric_definition| + expect(File).to receive(:read).with(metric_definition.path) + expect(File).not_to receive(:write).with(metric_definition.path) + end + + renamer.rename_product_group('old_name', 'new_name') + end + end + + describe '#rename_product_group', :aggregate_failures do + let(:temp_dir) { Dir.mktmpdir } + let(:schema_path) { File.join(temp_dir, 'product_groups.json') } + let(:event_definition_path) { File.join(temp_dir, 'event_definition.yml') } + let(:metric_definition_path) { File.join(temp_dir, 'metric_definition.yml') } + let(:event_definition_from_another_group_path) do + File.join(temp_dir, 'event_definition_from_another_group.yml') + end + + let(:definitions_glob) { [event_definition_path, metric_definition_path, event_definition_from_another_group_path] } + + before do + FileUtils.cp_r(File.join('spec/fixtures/scripts/product_group_renamer', '.'), temp_dir) + end + + after do + FileUtils.rm_rf(temp_dir) + end + + it 'renames product group in the schema and the definitions' do + renamer.rename_product_group('a_group_name', 'a_better_group_name') + + schema_content = File.read(schema_path) + + expect(schema_content).to include('a_better_group_name') + expect(schema_content).not_to include('a_group_name') + expect(File.read(event_definition_path)).to include('product_group: a_better_group_name') + expect(File.read(metric_definition_path)).to include('product_group: a_better_group_name') + expect(File.read(event_definition_from_another_group_path)).not_to include('product_group: a_better_group_name') + end + end +end