diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
index ba4ee6fcf9d8d3d8767abf31583e45b0c4e0029d..5e33273c9ba5475041dc51c0b86900b7d97c3f57 100644
--- a/app/models/cycle_analytics.rb
+++ b/app/models/cycle_analytics.rb
@@ -1,17 +1,17 @@
 class CycleAnalytics
   STAGES = %i[issue plan code test review staging production].freeze
 
-  def initialize(project, current_user, from:)
+  def initialize(project, from:)
     @project = project
-    @current_user = current_user
-    @from = from
-    @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil)
+    @options = options
   end
 
   def summary
-    @summary ||= Summary.new(@project, @current_user, from: @from)
+    @summary ||= Summary.new(@project, from: @options[:from])
   end
 
+  def method_missing(method_sym, *arguments, &block)
+    classify_stage(method_sym).new(project: @project, options: @options, stage: method_sym)
   def permissions(user:)
     Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project)
   end
@@ -23,40 +23,7 @@ class CycleAnalytics
                       Issue::Metrics.arel_table[:first_added_to_board_at]])
   end
 
-  def plan
-    @fetcher.calculate_metric(:plan,
-                     [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
-                      Issue::Metrics.arel_table[:first_added_to_board_at]],
-                     Issue::Metrics.arel_table[:first_mentioned_in_commit_at])
-  end
-
-  def code
-    @fetcher.calculate_metric(:code,
-                     Issue::Metrics.arel_table[:first_mentioned_in_commit_at],
-                     MergeRequest.arel_table[:created_at])
-  end
-
-  def test
-    @fetcher.calculate_metric(:test,
-                     MergeRequest::Metrics.arel_table[:latest_build_started_at],
-                     MergeRequest::Metrics.arel_table[:latest_build_finished_at])
-  end
-
-  def review
-    @fetcher.calculate_metric(:review,
-                     MergeRequest.arel_table[:created_at],
-                     MergeRequest::Metrics.arel_table[:merged_at])
-  end
-
-  def staging
-    @fetcher.calculate_metric(:staging,
-                     MergeRequest::Metrics.arel_table[:merged_at],
-                     MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
-  end
-
-  def production
-    @fetcher.calculate_metric(:production,
-                     Issue.arel_table[:created_at],
-                     MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
+  def classify_stage(method_sym)
+    "Gitlab::CycleAnalytics::#{method_sym.to_s.capitalize}Stage".constantize
   end
 end
diff --git a/lib/gitlab/cycle_analytics/base_event.rb b/lib/gitlab/cycle_analytics/base_event.rb
index 53a148ad703504cb70c19bd2894dc6974ca09761..c87841c119ac19296f0335e594f8655414c8d924 100644
--- a/lib/gitlab/cycle_analytics/base_event.rb
+++ b/lib/gitlab/cycle_analytics/base_event.rb
@@ -5,10 +5,10 @@ module Gitlab
 
       attr_reader :stage, :start_time_attrs, :end_time_attrs, :projections, :query
 
-      def initialize(project:, options:)
-        @query = EventsQuery.new(project: project, options: options)
-        @project = project
-        @options = options
+      def initialize(fetcher:, stage:)
+        @query = EventsQuery.new(fetcher: fetcher)
+        @project = fetcher.project
+        @stage = stage
       end
 
       def fetch
diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..70f1e1018c9a6867ab0758a2f58a45923ca60fbf
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/base_stage.rb
@@ -0,0 +1,24 @@
+module Gitlab
+  module CycleAnalytics
+    class BaseStage
+      def initialize(project:, options:, stage: stage)
+        @project = project
+        @options = options
+        @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project,
+                                                              from: options[:from],
+                                                              branch: options[:branch])
+        @stage = stage
+      end
+
+      def events
+        event_class.new(fetcher: @fetcher, stage: @stage).fetch
+      end
+
+      private
+
+      def event_class
+        "Gitlab::CycleAnalytics::#{@stage.to_s.capitalize}Event".constantize
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/cycle_analytics/code_event.rb b/lib/gitlab/cycle_analytics/code_event.rb
index 2afdf0b8518b487d643c77fb77694373db601953..68251630e080512d710a33ddf61d121517ad2674 100644
--- a/lib/gitlab/cycle_analytics/code_event.rb
+++ b/lib/gitlab/cycle_analytics/code_event.rb
@@ -4,7 +4,6 @@ module Gitlab
       include MergeRequestAllowed
 
       def initialize(*args)
-        @stage = :code
         @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at]
         @end_time_attrs = mr_table[:created_at]
         @projections = [mr_table[:title],
diff --git a/lib/gitlab/cycle_analytics/code_stage.rb b/lib/gitlab/cycle_analytics/code_stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9d28393ce53db5ed945d7da048571fbb39910c4b
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/code_stage.rb
@@ -0,0 +1,11 @@
+module Gitlab
+  module CycleAnalytics
+    class CodeStage < BaseStage
+      def median
+        @fetcher.calculate_metric(:code,
+                                  Issue::Metrics.arel_table[:first_mentioned_in_commit_at],
+                                  MergeRequest.arel_table[:created_at])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/cycle_analytics/events_query.rb b/lib/gitlab/cycle_analytics/events_query.rb
index 2418832ccc2fe02dc93a6acd71bdc13f6e100b46..e2b79384c9bedd35cd00f39cb4cdc9c9d539584a 100644
--- a/lib/gitlab/cycle_analytics/events_query.rb
+++ b/lib/gitlab/cycle_analytics/events_query.rb
@@ -1,13 +1,8 @@
 module Gitlab
   module CycleAnalytics
     class EventsQuery
-      attr_reader :project
-
-      def initialize(project:, options: {})
-        @project = project
-        @from = options[:from]
-        @branch = options[:branch]
-        @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: @from, branch: @branch)
+      def initialize(fetcher:)
+        @fetcher = fetcher
       end
 
       def execute(stage_class)
diff --git a/lib/gitlab/cycle_analytics/issue_event.rb b/lib/gitlab/cycle_analytics/issue_event.rb
index 705b7e5ce24871addc58d903a45f25b52b878a4f..76e8decf36e6f4399b273c435e696fcd1ae835d9 100644
--- a/lib/gitlab/cycle_analytics/issue_event.rb
+++ b/lib/gitlab/cycle_analytics/issue_event.rb
@@ -4,7 +4,6 @@ module Gitlab
       include IssueAllowed
 
       def initialize(*args)
-        @stage = :issue
         @start_time_attrs = issue_table[:created_at]
         @end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at],
                            issue_metrics_table[:first_added_to_board_at]]
diff --git a/lib/gitlab/cycle_analytics/issue_stage.rb b/lib/gitlab/cycle_analytics/issue_stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6793cc77976211ca458e8cc7cc17e80f8b169fe9
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/issue_stage.rb
@@ -0,0 +1,12 @@
+module Gitlab
+  module CycleAnalytics
+    class IssueStage < BaseStage
+      def median
+        @fetcher.calculate_metric(:issue,
+                                  Issue.arel_table[:created_at],
+                                  [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
+                                   Issue::Metrics.arel_table[:first_added_to_board_at]])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb
index b71e8735e27e91cc7c660f1f1b76752d35a9b1ba..51835bbde248588fe6fbc0e4d28f1175c49c7e2f 100644
--- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb
@@ -5,10 +5,11 @@ module Gitlab
       include Gitlab::Database::DateTime
       include MetricsTables
 
+      attr_reader :project
+
       DEPLOYMENT_METRIC_STAGES = %i[production staging]
 
       def initialize(project:, from:, branch:)
-        @project = project
         @project = project
         @from = from
         @branch = branch
diff --git a/lib/gitlab/cycle_analytics/plan_event.rb b/lib/gitlab/cycle_analytics/plan_event.rb
index 7c3f0e9989fbf3832d7f6217e1218179317ecf29..4b06143495b57c5afbb1fbd7ed77446157464388 100644
--- a/lib/gitlab/cycle_analytics/plan_event.rb
+++ b/lib/gitlab/cycle_analytics/plan_event.rb
@@ -2,7 +2,6 @@ module Gitlab
   module CycleAnalytics
     class PlanEvent < BaseEvent
       def initialize(*args)
-        @stage = :plan
         @start_time_attrs = issue_metrics_table[:first_associated_with_milestone_at]
         @end_time_attrs = [issue_metrics_table[:first_added_to_board_at],
                            issue_metrics_table[:first_mentioned_in_commit_at]]
diff --git a/lib/gitlab/cycle_analytics/plan_stage.rb b/lib/gitlab/cycle_analytics/plan_stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..772237087c09926679085806d011aea752042575
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/plan_stage.rb
@@ -0,0 +1,12 @@
+module Gitlab
+  module CycleAnalytics
+    class PlanStage < BaseStage
+      def median
+        @fetcher.calculate_metric(:plan,
+                                  [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
+                                   Issue::Metrics.arel_table[:first_added_to_board_at]],
+                                  Issue::Metrics.arel_table[:first_mentioned_in_commit_at])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/cycle_analytics/production_event.rb b/lib/gitlab/cycle_analytics/production_event.rb
index 4868c3c62373d942bea2d10bade2160cfbf1969e..c03cd4f49092969d55b10e8576c0add41198905a 100644
--- a/lib/gitlab/cycle_analytics/production_event.rb
+++ b/lib/gitlab/cycle_analytics/production_event.rb
@@ -4,7 +4,6 @@ module Gitlab
       include IssueAllowed
 
       def initialize(*args)
-        @stage = :production
         @start_time_attrs = issue_table[:created_at]
         @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
         @projections = [issue_table[:title],
diff --git a/lib/gitlab/cycle_analytics/production_stage.rb b/lib/gitlab/cycle_analytics/production_stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2fb087a8cac60a6faa5a1a66c7b334fdfca77d53
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/production_stage.rb
@@ -0,0 +1,11 @@
+module Gitlab
+  module CycleAnalytics
+    class ProductionStage < BaseStage
+      def median
+        @fetcher.calculate_metric(:production,
+                                  Issue.arel_table[:created_at],
+                                  MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/cycle_analytics/review_event.rb b/lib/gitlab/cycle_analytics/review_event.rb
index b394a02cc529f173f578e2d66490b91f649b6c0e..3f9ffa9657bae7b57cc06aa46dabe369a5daaada 100644
--- a/lib/gitlab/cycle_analytics/review_event.rb
+++ b/lib/gitlab/cycle_analytics/review_event.rb
@@ -4,7 +4,6 @@ module Gitlab
       include MergeRequestAllowed
 
       def initialize(*args)
-        @stage = :review
         @start_time_attrs = mr_table[:created_at]
         @end_time_attrs = mr_metrics_table[:merged_at]
         @projections = [mr_table[:title],
diff --git a/lib/gitlab/cycle_analytics/review_stage.rb b/lib/gitlab/cycle_analytics/review_stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec9f07319e84ec84a2bb8ffb0777cb9ffbe3b489
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/review_stage.rb
@@ -0,0 +1,11 @@
+module Gitlab
+  module CycleAnalytics
+    class ReviewStage < BaseStage
+      def median
+        @fetcher.calculate_metric(:review,
+                                  MergeRequest.arel_table[:created_at],
+                                  MergeRequest::Metrics.arel_table[:merged_at])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/cycle_analytics/staging_event.rb b/lib/gitlab/cycle_analytics/staging_event.rb
index a1f30b716f6b1d170c6db873485010e678f4d185..eae18b447f00ca2fa0b14bc2a5a3aa37ec073499 100644
--- a/lib/gitlab/cycle_analytics/staging_event.rb
+++ b/lib/gitlab/cycle_analytics/staging_event.rb
@@ -2,7 +2,6 @@ module Gitlab
   module CycleAnalytics
     class StagingEvent < BaseEvent
       def initialize(*args)
-        @stage = :staging
         @start_time_attrs = mr_metrics_table[:merged_at]
         @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
         @projections = [build_table[:id]]
diff --git a/lib/gitlab/cycle_analytics/staging_stage.rb b/lib/gitlab/cycle_analytics/staging_stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9c67a2aa6fe1c91697e5eb5ba349f445f7531873
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/staging_stage.rb
@@ -0,0 +1,11 @@
+module Gitlab
+  module CycleAnalytics
+    class StagingStage < BaseStage
+      def median
+        @fetcher.calculate_metric(:staging,
+                                  MergeRequest::Metrics.arel_table[:merged_at],
+                                  MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/cycle_analytics/test_event.rb b/lib/gitlab/cycle_analytics/test_event.rb
index d553d0b5aecc8ac1dff64e3b7d86af45bf16e1cd..d0736672adf4db53f107611fa5a6c04b14ce1c35 100644
--- a/lib/gitlab/cycle_analytics/test_event.rb
+++ b/lib/gitlab/cycle_analytics/test_event.rb
@@ -4,7 +4,6 @@ module Gitlab
       def initialize(*args)
         super(*args)
 
-        @stage = :test
         @start_time_attrs =  mr_metrics_table[:latest_build_started_at]
         @end_time_attrs = mr_metrics_table[:latest_build_finished_at]
       end
diff --git a/lib/gitlab/cycle_analytics/test_stage.rb b/lib/gitlab/cycle_analytics/test_stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6bedfdbba6127dd9b9b255e0b1919f80e6fdc72a
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/test_stage.rb
@@ -0,0 +1,11 @@
+module Gitlab
+  module CycleAnalytics
+    class TestStage < BaseStage
+      def median
+        @fetcher.calculate_metric(:test,
+                                  MergeRequest::Metrics.arel_table[:latest_build_started_at],
+                                  MergeRequest::Metrics.arel_table[:latest_build_finished_at])
+      end
+    end
+  end
+end