Loading lib/labkit/user_experience_sli/experience.rb +1 −1 Original line number Diff line number Diff line Loading @@ -209,7 +209,7 @@ module Labkit end def urgency_threshold URGENCY_THRESHOLDS_IN_SECONDS[@definition.urgency.to_sym] @definition.apdex_threshold_s || URGENCY_THRESHOLDS_IN_SECONDS[@definition.urgency.to_sym] end def elapsed_time Loading lib/labkit/user_experience_sli/registry.rb +8 −3 Original line number Diff line number Diff line Loading @@ -8,7 +8,11 @@ require 'labkit/json_schema/ref_resolver' module Labkit module UserExperienceSli Definition = Data.define(:user_experience_id, :description, :feature_category, :urgency) Definition = Data.define(:user_experience_id, :description, :feature_category, :urgency, :apdex_threshold_s) do def initialize(user_experience_id:, description:, feature_category:, urgency:, apdex_threshold_s: nil) super end end class Registry extend Forwardable Loading Loading @@ -78,15 +82,16 @@ module Labkit content = YAML.safe_load(file_path.read) return nil unless content.is_a?(Hash) return Definition.new(user_experience_id: experience_id, **content) if schema.valid?(content) return Definition.new(user_experience_id: experience_id, **content.transform_keys(&:to_sym)) if schema.valid?(content) warn("Invalid schema for #{file_path}") nil rescue Psych::SyntaxError => e warn("Invalid definition file #{file_path}: #{e.message}") nil rescue StandardError => e warn("Unexpected error processing #{file_path}: #{e.message}") nil end def schema Loading spec/labkit/user_experience_sli/experience_spec.rb +44 −0 Original line number Diff line number Diff line Loading @@ -446,6 +446,50 @@ RSpec.describe Labkit::UserExperienceSli::Experience, :with_metrics_config do end end context 'with custom urgency' do let(:definition) do Labkit::UserExperienceSli::Definition.new( user_experience_id: 'custom_threshold', description: 'Custom Apdex threshold', feature_category: 'source_code_management', urgency: 'custom', apdex_threshold_s: 90 ) end let(:duration_s) { 60 } it 'uses apdex_threshold_s for Apdex success' do labels = definition.to_h.slice(:user_experience_id, :feature_category, :urgency) observed expect(Labkit::Metrics::Client.get(:gitlab_user_experience_apdex_total).get(labels.merge(success: true))).to eq(1) end it 'uses apdex_threshold_s for Apdex violation' do labels = definition.to_h.slice(:user_experience_id, :feature_category, :urgency) start_time = Time.now.utc - 91 experience.observed(start_time: start_time) expect(Labkit::Metrics::Client.get(:gitlab_user_experience_apdex_total).get(labels.merge(success: false))).to eq(1) end it 'logs the custom urgency threshold' do expect(Labkit::UserExperienceSli.configuration.logger).to receive(:info) .with(hash_including(checkpoint: 'start', urgency: 'custom', urgency_threshold_s: 90)) .ordered .and_call_original expect(Labkit::UserExperienceSli.configuration.logger).to receive(:info) .with(hash_including(checkpoint: 'end', urgency: 'custom', urgency_threshold_s: 90)) .ordered .and_call_original observed end end context 'with reserved keyword validation' do %w[test development].each do |env| context "when RAILS_ENV is #{env}" do Loading spec/labkit/user_experience_sli/registry_spec.rb +55 −2 Original line number Diff line number Diff line Loading @@ -42,13 +42,32 @@ RSpec.describe Labkit::UserExperienceSli::Registry do user_experience_id: "merge_request_creation", description: 'Creating a new merge request in a project', feature_category: 'source_code_management', urgency: 'sync_fast' urgency: 'sync_fast', apdex_threshold_s: nil ) expect(registry[:issue_creation]).to eq Labkit::UserExperienceSli::Definition.new( user_experience_id: 'issue_creation', description: 'Creating a new issue in a project', feature_category: 'team_planning', urgency: 'sync_fast' urgency: 'sync_fast', apdex_threshold_s: nil ) end it 'loads definitions with a custom Apdex threshold' do create_yaml_file('custom_threshold.yml', base_content.merge( 'urgency' => 'custom', 'apdex_threshold_s' => 90 )) registry = described_class.new(dir: user_experiences_dir) expect(registry[:custom_threshold]).to eq Labkit::UserExperienceSli::Definition.new( user_experience_id: 'custom_threshold', description: 'Creating a new merge request in a project', feature_category: 'source_code_management', urgency: 'custom', apdex_threshold_s: 90 ) end Loading Loading @@ -127,6 +146,40 @@ RSpec.describe Labkit::UserExperienceSli::Registry do expect(registry['invalid_schema']).to be_nil end it 'requires apdex_threshold_s for custom urgency' do create_yaml_file('invalid_custom.yml', base_content.merge('urgency' => 'custom')) registry = described_class.new(dir: user_experiences_dir) expect(registry['invalid_custom']).to be_nil end it 'rejects apdex_threshold_s for predefined urgency' do create_yaml_file('invalid_predefined.yml', base_content.merge('apdex_threshold_s' => 90)) registry = described_class.new(dir: user_experiences_dir) expect(registry['invalid_predefined']).to be_nil end it 'rejects apdex_threshold_s outside the accepted range' do create_yaml_file('invalid_zero.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => 0)) create_yaml_file('invalid_over_cap.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => 601)) registry = described_class.new(dir: user_experiences_dir) expect(registry['invalid_zero']).to be_nil expect(registry['invalid_over_cap']).to be_nil end it 'rejects non-numeric apdex_threshold_s' do create_yaml_file('invalid_type.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => '90')) registry = described_class.new(dir: user_experiences_dir) expect(registry['invalid_type']).to be_nil end end context 'with custom ref_resolver_timeout' do Loading Loading
lib/labkit/user_experience_sli/experience.rb +1 −1 Original line number Diff line number Diff line Loading @@ -209,7 +209,7 @@ module Labkit end def urgency_threshold URGENCY_THRESHOLDS_IN_SECONDS[@definition.urgency.to_sym] @definition.apdex_threshold_s || URGENCY_THRESHOLDS_IN_SECONDS[@definition.urgency.to_sym] end def elapsed_time Loading
lib/labkit/user_experience_sli/registry.rb +8 −3 Original line number Diff line number Diff line Loading @@ -8,7 +8,11 @@ require 'labkit/json_schema/ref_resolver' module Labkit module UserExperienceSli Definition = Data.define(:user_experience_id, :description, :feature_category, :urgency) Definition = Data.define(:user_experience_id, :description, :feature_category, :urgency, :apdex_threshold_s) do def initialize(user_experience_id:, description:, feature_category:, urgency:, apdex_threshold_s: nil) super end end class Registry extend Forwardable Loading Loading @@ -78,15 +82,16 @@ module Labkit content = YAML.safe_load(file_path.read) return nil unless content.is_a?(Hash) return Definition.new(user_experience_id: experience_id, **content) if schema.valid?(content) return Definition.new(user_experience_id: experience_id, **content.transform_keys(&:to_sym)) if schema.valid?(content) warn("Invalid schema for #{file_path}") nil rescue Psych::SyntaxError => e warn("Invalid definition file #{file_path}: #{e.message}") nil rescue StandardError => e warn("Unexpected error processing #{file_path}: #{e.message}") nil end def schema Loading
spec/labkit/user_experience_sli/experience_spec.rb +44 −0 Original line number Diff line number Diff line Loading @@ -446,6 +446,50 @@ RSpec.describe Labkit::UserExperienceSli::Experience, :with_metrics_config do end end context 'with custom urgency' do let(:definition) do Labkit::UserExperienceSli::Definition.new( user_experience_id: 'custom_threshold', description: 'Custom Apdex threshold', feature_category: 'source_code_management', urgency: 'custom', apdex_threshold_s: 90 ) end let(:duration_s) { 60 } it 'uses apdex_threshold_s for Apdex success' do labels = definition.to_h.slice(:user_experience_id, :feature_category, :urgency) observed expect(Labkit::Metrics::Client.get(:gitlab_user_experience_apdex_total).get(labels.merge(success: true))).to eq(1) end it 'uses apdex_threshold_s for Apdex violation' do labels = definition.to_h.slice(:user_experience_id, :feature_category, :urgency) start_time = Time.now.utc - 91 experience.observed(start_time: start_time) expect(Labkit::Metrics::Client.get(:gitlab_user_experience_apdex_total).get(labels.merge(success: false))).to eq(1) end it 'logs the custom urgency threshold' do expect(Labkit::UserExperienceSli.configuration.logger).to receive(:info) .with(hash_including(checkpoint: 'start', urgency: 'custom', urgency_threshold_s: 90)) .ordered .and_call_original expect(Labkit::UserExperienceSli.configuration.logger).to receive(:info) .with(hash_including(checkpoint: 'end', urgency: 'custom', urgency_threshold_s: 90)) .ordered .and_call_original observed end end context 'with reserved keyword validation' do %w[test development].each do |env| context "when RAILS_ENV is #{env}" do Loading
spec/labkit/user_experience_sli/registry_spec.rb +55 −2 Original line number Diff line number Diff line Loading @@ -42,13 +42,32 @@ RSpec.describe Labkit::UserExperienceSli::Registry do user_experience_id: "merge_request_creation", description: 'Creating a new merge request in a project', feature_category: 'source_code_management', urgency: 'sync_fast' urgency: 'sync_fast', apdex_threshold_s: nil ) expect(registry[:issue_creation]).to eq Labkit::UserExperienceSli::Definition.new( user_experience_id: 'issue_creation', description: 'Creating a new issue in a project', feature_category: 'team_planning', urgency: 'sync_fast' urgency: 'sync_fast', apdex_threshold_s: nil ) end it 'loads definitions with a custom Apdex threshold' do create_yaml_file('custom_threshold.yml', base_content.merge( 'urgency' => 'custom', 'apdex_threshold_s' => 90 )) registry = described_class.new(dir: user_experiences_dir) expect(registry[:custom_threshold]).to eq Labkit::UserExperienceSli::Definition.new( user_experience_id: 'custom_threshold', description: 'Creating a new merge request in a project', feature_category: 'source_code_management', urgency: 'custom', apdex_threshold_s: 90 ) end Loading Loading @@ -127,6 +146,40 @@ RSpec.describe Labkit::UserExperienceSli::Registry do expect(registry['invalid_schema']).to be_nil end it 'requires apdex_threshold_s for custom urgency' do create_yaml_file('invalid_custom.yml', base_content.merge('urgency' => 'custom')) registry = described_class.new(dir: user_experiences_dir) expect(registry['invalid_custom']).to be_nil end it 'rejects apdex_threshold_s for predefined urgency' do create_yaml_file('invalid_predefined.yml', base_content.merge('apdex_threshold_s' => 90)) registry = described_class.new(dir: user_experiences_dir) expect(registry['invalid_predefined']).to be_nil end it 'rejects apdex_threshold_s outside the accepted range' do create_yaml_file('invalid_zero.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => 0)) create_yaml_file('invalid_over_cap.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => 601)) registry = described_class.new(dir: user_experiences_dir) expect(registry['invalid_zero']).to be_nil expect(registry['invalid_over_cap']).to be_nil end it 'rejects non-numeric apdex_threshold_s' do create_yaml_file('invalid_type.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => '90')) registry = described_class.new(dir: user_experiences_dir) expect(registry['invalid_type']).to be_nil end end context 'with custom ref_resolver_timeout' do Loading