Verified Commit 7549a92d authored by Hercules Merscher's avatar Hercules Merscher 🌴
Browse files

refactor: Registry object

parent 349703ce
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -12,12 +12,14 @@ module Labkit
    class << self
      extend Forwardable

      def_delegators :registry, :init_registry

      private
      def_delegators :registry, :[], :invalid_definitions

      def registry
        Registry
        @registry ||= Registry.new
      end

      def reset
        @registry = nil
      end
    end
  end
+2 −21
Original line number Diff line number Diff line
@@ -4,6 +4,8 @@ This module covers the definition for Covered Experiences, as described in the [

## Usage

Covered experience definitions will be lazy loaded from the default directory (`config/covered_experiences`).

Create a new covered experience file in the registry directory, e.g. config/covered_experiences/merge_request_creation.yaml

The basename of the file will be taken as the covered_experience_id.
@@ -30,24 +32,3 @@ https://docs.gitlab.com/development/feature_categorization/#feature-categorizati
| `sync_slow`  | A user is awaiting a synchronous response which needs to be returned before they can continue with their action, but which the user may accept a slower response | Displaying a full-text search response while displaying an amusement animation | 5s    |
| `async_fast` | An async process which may block a user from continuing with their user journey                                                                                  | MR diff update after git push                                                  | 15s   |
| `async_slow` | An async process which will not block a user and will not be immediately noticed as being slow                                                                   | Notification following an assignment                                           | 5m    |


Initializing the registry:

```ruby
# initialize the registry (automatically loads from config/covered_experiences relative to the project's root)
registry = Labkit::CoveredExperience.init_registry

# initialize the registry providing a custom directory
registry = Labkit::CoveredExperience.init_registry(dir: 'config/awesome_covered_experiences')

# the invalid file definitions can be retrieved for debugging
# they are silently ignored during load
registry.invalid_definitions
# {"testing_sample" => "The property '#/' did not contain a required property of 'feature_category' in schema 2cc4aef6-0b0d-551f-b89b-62d868b55944"}
```

If this step is skipped, the covered experiences will be lazily loaded from the default directory.

It's recommended to call `init_registry` upfront, so we can catch issues with directory not accessible
at development time.
+8 −28
Original line number Diff line number Diff line
@@ -9,31 +9,16 @@ require 'labkit/logging/json_logger'
module Labkit
  module CoveredExperience
    class Registry
      DirectoryNotReadable = Class.new(StandardError)
      extend Forwardable

      SCHEMA_PATH = File.expand_path('../../../config/covered_experiences/schema.json', __dir__)

      class << self
        extend Forwardable

        def_delegator :registry, :has_key?, :has_covered_experience?
      def_delegator :registry, :empty?

        # Initialize the registry by loading YAML files from the specified directory
        #
      # @param dir [String, Pathname] Directory path containing YAML file definitions
      #   Defaults to 'config/covered_experiences' relative to the calling application's root
        # @return [Hash] Registry of covered experiences keyed by covered_experience_id
        def init_registry(dir: nil)
          reset

          # assigning dir to an instance variable to lazy load files on first access
          @init_dir = dir

          # eagerly checking directory existence to fail fast during development
          raise(DirectoryNotReadable) unless directory_exists?(directory_path)

          self
      def initialize(dir: nil)
        @dir = dir
      end

      # Retrieve a definition from the registry
@@ -65,28 +50,24 @@ module Labkit
        @registry ||= load_definitions
      end

        # Resetting will force the registry to be re-evaluated by clearing the memoized vars
        # if .init_registry is called again.
        def reset
          @registry = nil
          @invalid_definitions = nil
        end

      # Initialize the registry by loading YAML files from the specified directory
      def load_definitions
        @registry = {}
        @invalid_definitions = {}

        warn("Directory not readable: #{directory_path}") unless directory_exists?(directory_path)

        load_yaml_files(directory_path)

        @registry.freeze
      end

        # Resolve the directory path, using default if @init_dir not provided.
      # Resolve the directory path, using default if @dir not provided.
      # Expands relative paths from the current working directory.
      #
      # @return [Pathname] Resolved directory path
      def directory_path
          dir = @init_dir || File.join("config", "covered_experiences")
        dir = @dir || File.join("config", "covered_experiences")
        Pathname.new(Dir.pwd).join(dir)
      end

@@ -134,4 +115,3 @@ module Labkit
    end
  end
end
end
+19 −21
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ RSpec.describe Labkit::CoveredExperience::Registry do
    FileUtils.rm_rf(covered_experiences_dir)
  end

  describe '.init_registry' do
  describe '.new' do
    context 'with valid YAML files' do
      before do
        create_yaml_file("#{covered_experience_id}.yml", base_content)
@@ -41,14 +41,14 @@ RSpec.describe Labkit::CoveredExperience::Registry do
      end

      it 'loads all YAML files and returns registry' do
        registry = described_class.init_registry(dir: covered_experiences_dir)
        registry = described_class.new(dir: covered_experiences_dir)

        expect(registry).to have_covered_experience('merge_request_creation')
        expect(registry).to have_covered_experience('issue_creation')
        expect(registry[:merge_request_creation]).to be_a(Hash)
        expect(registry[:issue_creation]).to be_a(Hash)
      end

      it 'stores complete covered experience data' do
        registry = described_class.init_registry(dir: covered_experiences_dir)
        registry = described_class.new(dir: covered_experiences_dir)

        mr_experience = registry['merge_request_creation']
        expect(mr_experience['description']).to eq('Creating a new merge request in a project')
@@ -57,7 +57,7 @@ RSpec.describe Labkit::CoveredExperience::Registry do
      end

      it 'freezes the stored data' do
        registry = described_class.init_registry(dir: covered_experiences_dir)
        registry = described_class.new(dir: covered_experiences_dir)

        experience = registry['merge_request_creation']
        expect(experience).to be_frozen
@@ -67,18 +67,18 @@ RSpec.describe Labkit::CoveredExperience::Registry do
        create_yaml_file('yaml_test.yaml', base_content.merge('description' => 'yaml_test'))
        create_yaml_file('yml_test.yml', base_content.merge('description' => 'yml_test'))

        registry = described_class.init_registry(dir: covered_experiences_dir)
        registry = described_class.new(dir: covered_experiences_dir)

        expect(registry).not_to have_covered_experience('yaml_test')
        expect(registry).to have_covered_experience('yml_test')
        expect(registry[:yaml_test]).to be_nil
        expect(registry[:yml_test]).to be_a(Hash)
      end
    end

    context 'with default directory' do
      it 'uses default config/covered_experiences directory when no dir specified' do
        registry = described_class.init_registry
        registry = described_class.new

        expect(registry).to have_covered_experience('testing_sample')
        expect(registry[:testing_sample]).not_to be_nil
      end
    end

@@ -86,9 +86,9 @@ RSpec.describe Labkit::CoveredExperience::Registry do
      let(:relative_dir) { 'spec/fixtures/covered_experiences' }

      it 'expands relative paths from current working directory' do
        registry = described_class.init_registry(dir: relative_dir)
        registry = described_class.new(dir: relative_dir)

        expect(registry).to have_covered_experience('merge_request_creation')
        expect(registry[:merge_request_creation]).to be_a(Hash)
      end
    end

@@ -96,23 +96,21 @@ RSpec.describe Labkit::CoveredExperience::Registry do
      it 'uses absolute path as-is' do
        create_yaml_file('absolute_test.yml', base_content)

        registry = described_class.init_registry(dir: covered_experiences_dir)
        registry = described_class.new(dir: covered_experiences_dir)

        expect(registry).to have_covered_experience('absolute_test')
        expect(registry[:absolute_test]).to be_a(Hash)
      end
    end

    context 'with non-existent directory' do
      it 'returns empty registry' do
        expect do
          described_class.init_registry(dir: '/non/existent/path')
        end.to raise_error(Labkit::CoveredExperience::Registry::DirectoryNotReadable)
        expect(described_class.new(dir: '/non/existent/path')).to be_empty
      end
    end

    context 'with empty directory' do
      it 'returns empty registry' do
        registry = described_class.init_registry(dir: covered_experiences_dir)
        registry = described_class.new(dir: covered_experiences_dir)

        expect(registry).to be_empty
      end
@@ -122,7 +120,7 @@ RSpec.describe Labkit::CoveredExperience::Registry do
      it 'ignores invalid YAML' do
        File.write(File.join(covered_experiences_dir, 'invalid.yml'), "invalid: yaml: content:")

        expect(described_class.init_registry(dir: covered_experiences_dir)).to be_empty
        expect(described_class.new(dir: covered_experiences_dir)).to be_empty
      end
    end

@@ -130,7 +128,7 @@ RSpec.describe Labkit::CoveredExperience::Registry do
      it 'ignores invalid schemas' do
        create_yaml_file('invalid_schema.yml', { 'invalid_key' => 'invalid_value' })

        registry = described_class.init_registry(dir: covered_experiences_dir)
        registry = described_class.new(dir: covered_experiences_dir)

        expect(registry).to be_empty
        expect(registry.invalid_definitions).to match("invalid_schema" => String)
+3 −5
Original line number Diff line number Diff line
@@ -6,11 +6,9 @@ require 'labkit/covered_experience'
RSpec.describe Labkit::CoveredExperience do
  let(:covered_experiences_dir) { File.join('spec/fixtures/covered_experiences') }

  describe '.init_registry' do
    it 'delegates to Registry.init_registry' do
      registry = described_class.init_registry(dir: covered_experiences_dir)

      expect(registry).to eq described_class::Registry.init_registry(dir: covered_experiences_dir)
  describe '.registry' do
    specify do
      expect(described_class.registry).to be_a(described_class::Registry)
    end
  end
end