Move all EE services differences to EE specific modules

EE specific code that resides in app/services should be moved to the corresponding EE specific modules that reside in ee/app/services, leaving behind only the necessary prepend and include calls, which should be placed at the end of the file).

Differences

app/services/ci/pipeline_trigger_service.rb: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24866 / https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9394
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/ci/pipeline_trigger_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/ci/pipeline_trigger_service.rb
index f54574b026b..b11f3655cb2 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/ci/pipeline_trigger_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/ci/pipeline_trigger_service.rb
@@ -7,6 +7,8 @@ module Ci
     def execute
       if trigger_from_token
         create_pipeline_from_trigger(trigger_from_token)
+      elsif job_from_token
+        create_pipeline_from_job(job_from_token)
       end
     end
 
@@ -29,12 +31,43 @@ module Ci
       end
     end
 
+    def create_pipeline_from_job(job)
+      # this check is to not leak the presence of the project if user cannot read it
+      return unless can?(job.user, :read_project, project)
+
+      return error("400 Job has to be running", 400) unless job.running?
+
+      pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref])
+        .execute(:pipeline, ignore_skip_ci: true) do |pipeline|
+          source = job.sourced_pipelines.build(
+            source_pipeline: job.pipeline,
+            source_project: job.project,
+            pipeline: pipeline,
+            project: project)
+
+          pipeline.source_pipeline = source
+          pipeline.variables.build(variables)
+        end
+
+      if pipeline.persisted?
+        success(pipeline: pipeline)
+      else
+        error(pipeline.errors.messages, 400)
+      end
+    end
+
     def trigger_from_token
       strong_memoize(:trigger) do
         Ci::Trigger.find_by_token(params[:token].to_s)
       end
     end
 
+    def job_from_token
+      strong_memoize(:job) do
+        Ci::Build.find_by_token(params[:token].to_s)
+      end
+    end
+
     def variables
       params[:variables].to_h.map do |key, value|
         { key: key, value: value }
app/services/ci/create_pipeline_service.rb: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24831 / https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9378
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/ci/create_pipeline_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/ci/create_pipeline_service.rb
index f8d8ef04001..91551b78893 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/ci/create_pipeline_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/ci/create_pipeline_service.rb
@@ -7,14 +7,17 @@ module Ci
     CreateError = Class.new(StandardError)
 
     SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build,
+                EE::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
                 Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
                 Gitlab::Ci::Pipeline::Chain::Validate::Repository,
                 Gitlab::Ci::Pipeline::Chain::Validate::Config,
                 Gitlab::Ci::Pipeline::Chain::Skip,
+                EE::Gitlab::Ci::Pipeline::Chain::Limit::Size,
                 Gitlab::Ci::Pipeline::Chain::Populate,
-                Gitlab::Ci::Pipeline::Chain::Create].freeze
+                Gitlab::Ci::Pipeline::Chain::Create,
+                EE::Gitlab::Ci::Pipeline::Chain::Limit::Activity].freeze
 
-    def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, &block)
+    def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, mirror_update: false, &block)
       @pipeline = Ci::Pipeline.new
 
       command = Gitlab::Ci::Pipeline::Chain::Command.new(
@@ -32,7 +35,12 @@ module Ci
         variables_attributes: params[:variables_attributes],
         project: project,
         current_user: current_user,
-        push_options: params[:push_options])
+        push_options: params[:push_options],
+
+        # EE specific
+        allow_mirror_update: mirror_update,
+        chat_data: params[:chat_data]
+      )
 
       sequence = Gitlab::Ci::Pipeline::Chain::Sequence
         .new(pipeline, command, SEQUENCE)
app/services/groups/create_service.rb: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24871 / https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9397
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/groups/create_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/groups/create_service.rb
index 24d8400c625..27a5eb79e98 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/groups/create_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/groups/create_service.rb
@@ -2,6 +2,8 @@
 
 module Groups
   class CreateService < Groups::BaseService
+    prepend ::EE::Groups::CreateService # rubocop: disable Cop/InjectEnterpriseEditionModule
+
     def initialize(user, params = {})
       @current_user, @params = user, params.dup
       @chat_team = @params.delete(:create_chat_team)
@@ -10,6 +12,10 @@ module Groups
     def execute
       @group = Group.new(params)
 
+      # Repository size limit comes as MB from the view
+      limit = params.delete(:repository_size_limit)
+      @group.repository_size_limit = Gitlab::Utils.try_megabytes_to_bytes(limit) if limit
+
       unless can_use_visibility_level? && can_create_group?
         return @group
       end
app/services/groups/update_service.rb: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24912 / https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9426
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/groups/update_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/groups/update_service.rb
index de78a3f7b27..4826eac75d3 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/groups/update_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/groups/update_service.rb
@@ -3,6 +3,7 @@
 module Groups
   class UpdateService < Groups::BaseService
     include UpdateVisibilityLevel
+    prepend ::EE::Groups::UpdateService # rubocop: disable Cop/InjectEnterpriseEditionModule
 
     def execute
       reject_parent_id!
@@ -11,6 +12,10 @@ module Groups
 
       return false unless valid_share_with_group_lock_change?
 
+      # Repository size limit comes as MB from the view
+      limit = @params.delete(:repository_size_limit)
+      group.repository_size_limit = Gitlab::Utils.try_megabytes_to_bytes(limit) if limit
+
       group.assign_attributes(params)
 
       begin
app/services/search/global_service.rb: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24914 / https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9427
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/search/global_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/search/global_service.rb
index cb1bf0a03a5..781ca5a56d6 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/search/global_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/search/global_service.rb
@@ -11,17 +11,39 @@ module Search
     end
 
     def execute
-      Gitlab::SearchResults.new(current_user, projects, params[:search],
-                                default_project_filter: default_project_filter)
+      if Gitlab::CurrentSettings.elasticsearch_search?
+        Gitlab::Elastic::SearchResults.new(current_user, params[:search], elastic_projects, elastic_global)
+      else
+        Gitlab::SearchResults.new(current_user, projects, params[:search],
+                                  default_project_filter: default_project_filter)
+      end
     end
 
     def projects
       @projects ||= ProjectsFinder.new(current_user: current_user).execute
     end
 
+    # rubocop: disable CodeReuse/ActiveRecord
+    def elastic_projects
+      @elastic_projects ||=
+        if current_user&.full_private_access?
+          :any
+        elsif current_user
+          current_user.authorized_projects.pluck(:id)
+        else
+          []
+        end
+    end
+    # rubocop: enable CodeReuse/ActiveRecord
+
+    def elastic_global
+      true
+    end
+
     def scope
       @scope ||= begin
         allowed_scopes = %w[issues merge_requests milestones]
+        allowed_scopes += %w[wiki_blobs blobs commits] if Gitlab::CurrentSettings.elasticsearch_search?
 
         allowed_scopes.delete(params[:scope]) { 'projects' }
       end
app/services/search/project_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/search/project_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/search/project_service.rb
index f223c8be103..15508af3be3 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/search/project_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/search/project_service.rb
@@ -9,10 +9,17 @@ module Search
     end
 
     def execute
-      Gitlab::ProjectSearchResults.new(current_user,
-                                       project,
-                                       params[:search],
-                                       params[:repository_ref])
+      if Gitlab::CurrentSettings.elasticsearch_search?
+        Gitlab::Elastic::ProjectSearchResults.new(current_user,
+                                                  params[:search],
+                                                  project.id,
+                                                  params[:repository_ref])
+      else
+        Gitlab::ProjectSearchResults.new(current_user,
+                                         project,
+                                         params[:search],
+                                         params[:repository_ref])
+      end
     end
 
     def scope
app/services/search/group_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/search/group_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/search/group_service.rb
index 34803d005e3..19ba4956db0 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/search/group_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/search/group_service.rb
@@ -17,5 +17,15 @@ module Search
 
       @projects = super.inside_path(group.full_path)
     end
+
+    # rubocop: disable CodeReuse/ActiveRecord
+    def elastic_projects
+      @elastic_projects ||= projects.pluck(:id)
+    end
+    # rubocop: enable CodeReuse/ActiveRecord
+
+    def elastic_global
+      false
+    end
   end
 end
app/services/issuable/clone/base_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/issuable/clone/base_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/issuable/clone/base_service.rb
index 42dd9c666f5..0b7c455ed17 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/issuable/clone/base_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/issuable/clone/base_service.rb
@@ -51,6 +51,8 @@ module Issuable
       end
 
       def group
+        return new_entity.group if new_entity.respond_to?(:group) && new_entity.group
+
         if new_entity.project&.group && current_user.can?(:read_group, new_entity.project.group)
           new_entity.project.group
         end
app/services/system_hooks_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/system_hooks_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/system_hooks_service.rb
index bd3907cdf8e..40dbe55a5e6 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/system_hooks_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/system_hooks_service.rb
@@ -130,6 +130,7 @@ class SystemHooksService
     {
       group_name: model.group.name,
       group_path: model.group.path,
+      group_plan: model.group.plan&.name,
       group_id: model.group.id,
       user_username: model.user.username,
       user_name: model.user.name,
@@ -148,3 +149,5 @@ class SystemHooksService
     }
   end
 end
+
+SystemHooksService.prepend(EE::SystemHooksService)
app/services/notes/quick_actions_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/notes/quick_actions_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/notes/quick_actions_service.rb
index 7ee9732040d..2653236d530 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/notes/quick_actions_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/notes/quick_actions_service.rb
@@ -5,7 +5,8 @@ module Notes
     UPDATE_SERVICES = {
       'Issue' => Issues::UpdateService,
       'MergeRequest' => MergeRequests::UpdateService,
-      'Commit' => Commits::TagService
+      'Commit' => Commits::TagService,
+      'Epic' => Epics::UpdateService
     }.freeze
 
     def self.noteable_update_service(note)
app/services/git_tag_push_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/git_tag_push_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/git_tag_push_service.rb
index 03fcf614c64..ee1e2700ba1 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/git_tag_push_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/git_tag_push_service.rb
@@ -10,7 +10,7 @@ class GitTagPushService < BaseService
     @push_data = build_push_data
 
     EventCreateService.new.push(project, current_user, push_data)
-    Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push)
+    Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push, mirror_update: params[:mirror_update])
 
     SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks)
     project.execute_hooks(push_data.dup, :tag_push_hooks)
app/services/concerns/exclusive_lease_guard.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/concerns/exclusive_lease_guard.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/concerns/exclusive_lease_guard.rb
index 28879d2d67f..2cb73555d85 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/concerns/exclusive_lease_guard.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/concerns/exclusive_lease_guard.rb
@@ -42,7 +42,7 @@ module ExclusiveLeaseGuard
 
   def lease_timeout
     raise NotImplementedError,
-          "#{self.class.name} does not implement #{__method__}"
+      "#{self.class.name} does not implement #{__method__}"
   end
 
   def lease_release?
app/services/create_branch_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/create_branch_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/create_branch_service.rb
index 65208b07e27..110e589e30d 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/create_branch_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/create_branch_service.rb
@@ -1,8 +1,8 @@
 # frozen_string_literal: true
 
 class CreateBranchService < BaseService
-  def execute(branch_name, ref)
-    create_master_branch if project.empty_repo?
+  def execute(branch_name, ref, create_master_if_empty: true)
+    create_master_branch if create_master_if_empty && project.empty_repo?
 
     result = ValidateNewBranchService.new(project, current_user)
       .execute(branch_name)
app/services/protected_branches/legacy_api_update_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/protected_branches/legacy_api_update_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/protected_branches/legacy_api_update_service.rb
index da8bf2ce02a..4cb78c4aeb1 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/protected_branches/legacy_api_update_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/protected_branches/legacy_api_update_service.rb
@@ -13,7 +13,7 @@ module ProtectedBranches
       @protected_branch = protected_branch
 
       protected_branch.transaction do
-        delete_redundant_access_levels
+        delete_redundant_ee_access_levels
 
         case @developers_can_push
         when true
@@ -38,11 +38,32 @@ module ProtectedBranches
 
     def delete_redundant_access_levels
       unless @developers_can_merge.nil?
-        @protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll
+        @protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll # rubocop: disable DestroyAll
       end
 
       unless @developers_can_push.nil?
-        @protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll
+        @protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll # rubocop: disable DestroyAll
+      end
+    end
+
+    # If a protected branch can have more than one access level (EE), only
+    # remove the relevant access levels. If we don't do this, we'll have a
+    # failed validation.
+    def delete_redundant_ee_access_levels
+      case @developers_can_merge
+      when true
+        @protected_branch.merge_access_levels.developer.destroy_all # rubocop: disable DestroyAll
+      when false
+        @protected_branch.merge_access_levels.developer.destroy_all # rubocop: disable DestroyAll
+        @protected_branch.merge_access_levels.maintainer.destroy_all # rubocop: disable DestroyAll
+      end
+
+      case @developers_can_push
+      when true
+        @protected_branch.push_access_levels.developer.destroy_all # rubocop: disable DestroyAll
+      when false
+        @protected_branch.push_access_levels.developer.destroy_all # rubocop: disable DestroyAll
+        @protected_branch.push_access_levels.maintainer.destroy_all # rubocop: disable DestroyAll
       end
     end
   end
app/services/protected_branches/api_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/protected_branches/api_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/protected_branches/api_service.rb
index 4340d3e8260..eba4d216454 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/protected_branches/api_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/protected_branches/api_service.rb
@@ -5,13 +5,15 @@ module ProtectedBranches
     def create
       @push_params = AccessLevelParams.new(:push, params)
       @merge_params = AccessLevelParams.new(:merge, params)
+      @unprotect_params = AccessLevelParams.new(:unprotect, params)
 
       verify_params!
 
       protected_branch_params = {
         name: params[:name],
         push_access_levels_attributes: @push_params.access_levels,
-        merge_access_levels_attributes: @merge_params.access_levels
+        merge_access_levels_attributes: @merge_params.access_levels,
+        unprotect_access_levels_attributes: @unprotect_params.access_levels
       }
 
       ::ProtectedBranches::CreateService.new(@project, @current_user, protected_branch_params).execute
@@ -24,3 +26,5 @@ module ProtectedBranches
     end
   end
 end
+
+ProtectedBranches::ApiService.prepend(EE::ProtectedBranches::ApiService)
app/services/git_push_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/git_push_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/git_push_service.rb
index 9ecee7c6156..ca8e302d6c7 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/git_push_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/git_push_service.rb
@@ -55,6 +55,10 @@ class GitPushService < BaseService
       update_gitattributes if default_branch?
     end
 
+    if Gitlab::CurrentSettings.elasticsearch_indexing? && default_branch?
+      ElasticCommitIndexerWorker.perform_async(@project.id, params[:oldrev], params[:newrev])
+    end
+
     execute_related_hooks
     perform_housekeeping
 
@@ -139,8 +143,10 @@ class GitPushService < BaseService
     UpdateMergeRequestsWorker
       .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
 
+    mirror_update = project.mirror? && project.repository.up_to_date_with_upstream?(branch_name)
+
     EventCreateService.new.push(project, current_user, build_push_data)
-    Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push)
+    Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, mirror_update: mirror_update)
 
     project.execute_hooks(build_push_data.dup, :push_hooks)
     project.execute_services(build_push_data.dup, :push_hooks)
app/services/notification_recipient_service.rb
diff --git a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/notification_recipient_service.rb b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/notification_recipient_service.rb
index 68cdc69023a..3831b277880 100644
--- a/home/yorickpeterse/Projects/gitlab/gdk-ce/gitlab/app/services/notification_recipient_service.rb
+++ b/home/yorickpeterse/Projects/gitlab/gdk-ee/gitlab/app/services/notification_recipient_service.rb
@@ -242,6 +242,8 @@ module NotificationRecipientService
     end
 
     class Default < Base
+      prepend ::EE::NotificationRecipientBuilders::Default # rubocop: disable Cop/InjectEnterpriseEditionModule
+
       MENTION_TYPE_ACTIONS = [:new_issue, :new_merge_request].freeze
 
       attr_reader :target
@@ -260,7 +262,13 @@ module NotificationRecipientService
 
       def build!
         add_participants(current_user)
-        add_project_watchers
+
+        if project
+          add_project_watchers
+        else # for group level targets
+          add_group_watchers
+        end
+
         add_custom_notifications
 
         # Re-assign is considered as a mention of the new assignee
@@ -405,3 +413,5 @@ module NotificationRecipientService
     end
   end
 end
+
+NotificationRecipientService.prepend(EE::NotificationRecipientService)
Edited Aug 27, 2025 by Dave Smith
Assignee Loading
Time tracking Loading