Verified Commit 452a3ce1 authored by Hercules Merscher's avatar Hercules Merscher
Browse files

feat: Pushing and loading experiences from Thread.current

parent cffe0d61
Loading
Loading
Loading
Loading
+8 −15
Original line number Diff line number Diff line
@@ -24,8 +24,8 @@ module Labkit
      private :initialize

      class << self
        # Factory method to create a new Covered Experience
        # It will also rehydrate the state from the current context
        # Factory method to create a new Covered Experience.
        # It will rehydrate the state from the current context
        # if it exists.
        #
        # @param definition [Labkit::CoveredExperience::Definition]
@@ -35,6 +35,10 @@ module Labkit
          instance.instance_eval { load_from_context }
          instance
        end

        def aggregation_context
          Thread.current[:covered_experiences] ||= {}
        end
      end

      # Start the Covered Experience.
@@ -234,11 +238,6 @@ module Labkit
        log_data
      end

      def aggregation_context
        aggregation_key = Labkit::Context::COVERED_EXPERIENCE_AGG_KEY
        Labkit::Context.current&.get_attribute(aggregation_key) || {}
      end

      def to_proc(**extra)
        lambda do
          checkpoint(checkpoint_category: "push_to_context", **extra)
@@ -247,24 +246,18 @@ module Labkit
      end

      def push_to_context(**extra)
        covered_experiences = aggregation_context

        # Pushing the experience to the context as a Proc to be lazy evaluated.
        # This allows the checkpoint event to be triggered only when context
        # is being serialized sidekiq jobs, and eventually HTTP headers.
        covered_experiences[@definition.covered_experience] = to_proc(**extra)

        Labkit::Context.push(Labkit::Context::COVERED_EXPERIENCE_AGG_KEY => covered_experiences)
        self.class.aggregation_context[@definition.covered_experience] = to_proc(**extra)
      end

      def load_from_context
        covered_experiences = aggregation_context
        experience_data = covered_experiences[@definition.covered_experience]
        experience_data = self.class.aggregation_context[@definition.covered_experience]

        processed_data =
          case experience_data
          when Proc then experience_data.call
          when String then experience_data
          else experience_data || {}
          end

+14 −21
Original line number Diff line number Diff line
@@ -85,19 +85,18 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do
      end

      it 'pushes covered experience data to context' do
        expect(Labkit::Context.current.get_attribute("covered_experiences")).to be_nil

        expect { experience.start }.not_to checkpoint_covered_experience(:testing_sample)

        covered_experiences = Labkit::Context.current.get_attribute("covered_experiences")
        covered_experiences = Thread.current[:covered_experiences]
        expect(covered_experiences).to include("testing_sample")
        expect(covered_experiences["testing_sample"]).to be_a(Proc)

        # proc should trigger a checkpoint event when called
        # Proc should trigger a checkpoint event when called
        expect(Labkit::CoveredExperience.configuration.logger).to receive(:info)
          .with(hash_including(checkpoint: 'intermediate', checkpoint_category: 'push_to_context'))
          .ordered
          .and_call_original

        expect do
          expect(covered_experiences["testing_sample"].call).to match("start_time" => a_kind_of(String))
        end.to checkpoint_covered_experience(:testing_sample)
@@ -160,16 +159,17 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do
      it 'pushes covered experience data to context' do
        start

        covered_experiences = Labkit::Context.current.get_attribute("covered_experiences")
        covered_experiences = Thread.current[:covered_experiences]

        expect(covered_experiences).to include("testing_sample")
        expect(covered_experiences["testing_sample"]).to be_a(Proc)

        # proc should trigger a checkpoint event when called
        # Proc should trigger a checkpoint event when called
        expect(Labkit::CoveredExperience.configuration.logger).to receive(:info)
          .with(hash_including(checkpoint: 'intermediate', checkpoint_category: 'push_to_context'))
          .ordered
          .and_call_original

        expect do
          expect(covered_experiences["testing_sample"].call).to eq("start_time" => experience.start_time.iso8601(3))
        end.to checkpoint_covered_experience(:testing_sample)
@@ -180,11 +180,11 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do
        existing_experiences = {
          "other_experience" => { "start_time" => (Time.now.utc - 10).iso8601(3) }
        }
        Labkit::Context.push("covered_experiences" => existing_experiences)
        Thread.current[:covered_experiences] = existing_experiences

        start

        covered_experiences = Labkit::Context.current.get_attribute("covered_experiences")
        covered_experiences = Thread.current[:covered_experiences]

        expect(covered_experiences).to include("other_experience", "testing_sample")
        expect(covered_experiences["testing_sample"]).to be_a(Proc)
@@ -193,12 +193,11 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do
      end

      it 'handles empty context gracefully' do
        # Clear any existing context
        Labkit::Context.push("covered_experiences" => nil)
        Thread.current[:covered_experiences] = nil

        start

        covered_experiences = Labkit::Context.current.get_attribute("covered_experiences")
        covered_experiences = Thread.current[:covered_experiences]

        expect(covered_experiences).to include("testing_sample")
        expect(covered_experiences["testing_sample"]).to be_a(Proc)
@@ -643,10 +642,9 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do

      before do
        # Set up existing covered experience in context
        covered_experiences = {
        Thread.current[:covered_experiences] = {
          "testing_sample" => { "start_time" => start_time.iso8601(3) }
        }
        Labkit::Context.push("covered_experiences" => covered_experiences)
      end

      subject(:loaded_experience) { described_class.load(definition) }
@@ -682,10 +680,7 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do

      before do
        # Set up existing covered experience as a Proc in context
        covered_experiences = {
          "testing_sample" => proc_data
        }
        Labkit::Context.push("covered_experiences" => covered_experiences)
        Thread.current[:covered_experiences] = { "testing_sample" => proc_data }
      end

      subject(:loaded_experience) { described_class.load(definition) }
@@ -715,14 +710,12 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do
    context 'when no experience data exists in context' do
      before do
        # Ensure no covered experiences in context
        Labkit::Context.push("covered_experiences" => nil)
        Thread.current[:covered_experiences] = nil
      end

      subject(:loaded_experience) { described_class.load(definition) }

      it 'returns an Experience instance' do
        expect(loaded_experience).to be_a(described_class)
      end
      it { is_expected.to be_a(described_class) }

      it 'does not set start_time' do
        expect(loaded_experience.start_time).to be_nil
+2 −2
Original line number Diff line number Diff line
@@ -225,7 +225,7 @@ RSpec.describe Labkit::CoveredExperience, :with_metrics_config do
      let(:experience_data) { { "start_time" => Time.now.utc.iso8601(3) } }

      before do
        Labkit::Context.push(Labkit::Context::COVERED_EXPERIENCE_AGG_KEY => { 'testing_sample' => experience_data })
        Thread.current[:covered_experiences] = { 'testing_sample' => experience_data }
      end

      context 'when block is given' do
@@ -331,7 +331,7 @@ RSpec.describe Labkit::CoveredExperience, :with_metrics_config do
      let(:experience_proc) { -> { { "start_time" => Time.now.utc.iso8601(3) } } }

      before do
        Labkit::Context.push(Labkit::Context::COVERED_EXPERIENCE_AGG_KEY => { 'testing_sample' => experience_proc })
        Thread.current[:covered_experiences] = { 'testing_sample' => experience_proc }
      end

      it 'calls the proc to get experience data' do
+1 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ RSpec.configure do |config|
      # Ignore logs by default in tests
      config.logger = Labkit::Logging::JsonLogger.new("/dev/null")
    end
    Thread.current[:covered_experiences] = nil

    example.run