added new summary serializers and refactor all of the summary stuff into separate logical classes
This commit is contained in:
parent
dc6ea14b0d
commit
fc6f8f2056
|
|
@ -8,10 +8,6 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
|
|||
def show
|
||||
@cycle_analytics = ::CycleAnalytics.new(@project, current_user, from: start_date(cycle_analytics_params))
|
||||
|
||||
stats_values, cycle_analytics_json = generate_cycle_analytics_data
|
||||
|
||||
@cycle_analytics_no_data = stats_values.blank?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render json: cycle_analytics_json }
|
||||
|
|
@ -26,47 +22,11 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
|
|||
{ start_date: params[:cycle_analytics][:start_date] }
|
||||
end
|
||||
|
||||
def generate_cycle_analytics_data
|
||||
stats_values = []
|
||||
|
||||
cycle_analytics_view_data = [[:issue, "Issue", "Related Issues", "Time before an issue gets scheduled"],
|
||||
[:plan, "Plan", "Related Commits", "Time before an issue starts implementation"],
|
||||
[:code, "Code", "Related Merge Requests", "Time spent coding"],
|
||||
[:test, "Test", "Relative Builds Trigger by Commits", "The time taken to build and test the application"],
|
||||
[:review, "Review", "Relative Merged Requests", "The time taken to review the code"],
|
||||
[:staging, "Staging", "Relative Deployed Builds", "The time taken in staging"],
|
||||
[:production, "Production", "Related Issues", "The total time taken from idea to production"]]
|
||||
|
||||
stats = cycle_analytics_view_data.reduce([]) do |stats, (stage_method, stage_text, stage_legend, stage_description)|
|
||||
value = @cycle_analytics.send(stage_method).presence
|
||||
|
||||
stats_values << value.abs if value
|
||||
|
||||
stats << {
|
||||
title: stage_text,
|
||||
description: stage_description,
|
||||
legend: stage_legend,
|
||||
value: value && !value.zero? ? distance_of_time_in_words(value) : nil
|
||||
}
|
||||
|
||||
stats
|
||||
end
|
||||
|
||||
issues = @cycle_analytics.summary.new_issues
|
||||
commits = @cycle_analytics.summary.commits
|
||||
deploys = @cycle_analytics.summary.deploys
|
||||
|
||||
summary = [
|
||||
{ title: "New Issue".pluralize(issues), value: issues },
|
||||
{ title: "Commit".pluralize(commits), value: commits },
|
||||
{ title: "Deploy".pluralize(deploys), value: deploys }
|
||||
]
|
||||
|
||||
cycle_analytics_hash = { summary: summary,
|
||||
stats: stats,
|
||||
permissions: @cycle_analytics.permissions(user: current_user)
|
||||
def cycle_analytics_json
|
||||
{
|
||||
summary: @cycle_analytics.summary,
|
||||
stats: nil, # TODO
|
||||
permissions: @cycle_analytics.permissions(user: current_user)# TODO
|
||||
}
|
||||
|
||||
[stats_values, cycle_analytics_hash]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class CycleAnalytics
|
|||
end
|
||||
|
||||
def summary
|
||||
@summary ||= Summary.new(@project, from: @options[:from])
|
||||
@summary ||= Gitlab::CycleAnalytics::Summary.new(@project, from: @options[:from]).data
|
||||
end
|
||||
|
||||
def method_missing(method_sym, *arguments, &block)
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
class CycleAnalytics
|
||||
class Summary
|
||||
def initialize(project, current_user, from:)
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@from = from
|
||||
end
|
||||
|
||||
def new_issues
|
||||
IssuesFinder.new(@current_user, project_id: @project.id).execute.created_after(@from).count
|
||||
end
|
||||
|
||||
def commits
|
||||
ref = @project.default_branch.presence
|
||||
count_commits_for(ref)
|
||||
end
|
||||
|
||||
def deploys
|
||||
@project.deployments.where("created_at > ?", @from).count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Don't use the `Gitlab::Git::Repository#log` method, because it enforces
|
||||
# a limit. Since we need a commit count, we _can't_ enforce a limit, so
|
||||
# the easiest way forward is to replicate the relevant portions of the
|
||||
# `log` function here.
|
||||
def count_commits_for(ref)
|
||||
return unless ref
|
||||
|
||||
repository = @project.repository.raw_repository
|
||||
sha = @project.repository.commit(ref).sha
|
||||
|
||||
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{repository.path} log)
|
||||
cmd << '--format=%H'
|
||||
cmd << "--after=#{@from.iso8601}"
|
||||
cmd << sha
|
||||
|
||||
raw_output = IO.popen(cmd) { |io| io.read }
|
||||
raw_output.lines.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
class AnalyticsSummaryEntity < Grape::Entity
|
||||
expose :value, safe: true
|
||||
|
||||
expose :title do |object|
|
||||
object.title.pluralize(object.value)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class AnalyticsSummarySerializer < BaseSerializer
|
||||
entity AnalyticsSummaryEntity
|
||||
end
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
module Gitlab
|
||||
module CycleAnalytics
|
||||
module Summary
|
||||
extend self
|
||||
|
||||
def initialize(project, from:)
|
||||
@project = project
|
||||
@from = from
|
||||
end
|
||||
|
||||
def data
|
||||
[serialize(issue),
|
||||
serialize(commit),
|
||||
serialize(deploy)]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def serialize(summary_object)
|
||||
AnalyticsSummarySerializer.new.represent(summary_object).as_json
|
||||
end
|
||||
|
||||
def issue
|
||||
Summary::Issue.new(project: @project, from: @from)
|
||||
end
|
||||
|
||||
def deploy
|
||||
Summary::Deploy.new(project: @project, from: @from)
|
||||
end
|
||||
|
||||
def commit
|
||||
Summary::Commit.new(project: @project, from: @from)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
module Gitlab
|
||||
module CycleAnalytics
|
||||
module Summary
|
||||
class Base
|
||||
def initialize(project:, from:)
|
||||
@project = project
|
||||
@from = from
|
||||
end
|
||||
|
||||
def title
|
||||
self.name
|
||||
end
|
||||
|
||||
def value
|
||||
raise NotImplementedError.new("Expected #{self.name} to implement value")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
module Gitlab
|
||||
module CycleAnalytics
|
||||
module Summary
|
||||
class Commit < Base
|
||||
def value
|
||||
@value ||= count_commits
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Don't use the `Gitlab::Git::Repository#log` method, because it enforces
|
||||
# a limit. Since we need a commit count, we _can't_ enforce a limit, so
|
||||
# the easiest way forward is to replicate the relevant portions of the
|
||||
# `log` function here.
|
||||
def count_commits
|
||||
return unless ref
|
||||
|
||||
repository = @project.repository.raw_repository
|
||||
sha = @project.repository.commit(ref).sha
|
||||
|
||||
cmd = %W(git --git-dir=#{repository.path} log)
|
||||
cmd << '--format=%H'
|
||||
cmd << "--after=#{@from.iso8601}"
|
||||
cmd << sha
|
||||
|
||||
raw_output = IO.popen(cmd) { |io| io.read }
|
||||
raw_output.lines.count
|
||||
end
|
||||
|
||||
def ref
|
||||
@ref ||= @project.default_branch.presence
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
module Gitlab
|
||||
module CycleAnalytics
|
||||
module Summary
|
||||
class Deploy < Base
|
||||
def value
|
||||
@value ||= @project.deployments.where("created_at > ?", @from).count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
module Gitlab
|
||||
module CycleAnalytics
|
||||
module Summary
|
||||
class Issue < Base
|
||||
def title
|
||||
'New Issue'
|
||||
end
|
||||
|
||||
def value
|
||||
@value ||= @project.issues.created_after(@from).count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe AnalyticsStageSerializer do
|
||||
let(:serializer) do
|
||||
described_class
|
||||
.new.represent(resource)
|
||||
end
|
||||
|
||||
let(:json) { serializer.as_json }
|
||||
let(:resource) { Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {}, stage: :code) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Gitlab::CycleAnalytics::MetricsFetcher).to receive(:calculate_metric).and_return(1.12)
|
||||
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEvent).to receive(:event_result).and_return({})
|
||||
end
|
||||
|
||||
it 'it generates payload for single object' do
|
||||
expect(json).to be_an_instance_of Hash
|
||||
end
|
||||
|
||||
it 'contains important elements of AnalyticsStage' do
|
||||
expect(json).to include(:title, :description, :value)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe AnalyticsSummarySerializer do
|
||||
let(:serializer) do
|
||||
described_class
|
||||
.new.represent(resource)
|
||||
end
|
||||
|
||||
let(:json) { serializer.as_json }
|
||||
let(:project) { create(:empty_project) }
|
||||
let(:resource) { Gitlab::CycleAnalytics::Summary::Issue.new(project: double, from: 1.day.ago) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Gitlab::CycleAnalytics::Summary::Issue).to receive(:value).and_return(1.12)
|
||||
end
|
||||
|
||||
it 'it generates payload for single object' do
|
||||
expect(json).to be_an_instance_of Hash
|
||||
end
|
||||
|
||||
it 'contains important elements of AnalyticsStage' do
|
||||
expect(json).to include(:title, :value)
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue