Commit a8ef8326 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '221077-define-and-load-default-analytics-report-file' into 'master'

Load initial analytics report configuration

See merge request gitlab-org/gitlab!35632
parents 39b7836b bdfc98dc
recent_merge_requests_by_group:
title: Recent Issues (90 days)
chart:
type: bar
series:
open_merge_requests:
title: Merge Requests
data_retrieval: Metrics::InsightsQuery
data_retrieval_options:
issuable_type: merge_request
issuable_state: opened
group_by: week
period_limit: 12
......@@ -12,6 +12,19 @@ module API
# This will be scoped to a project or a group
Feature.enabled?(:report_pages) && ::License.feature_available?(:group_activity_analytics)
end
def load_report
loader_class = Gitlab::Analytics::Reports::ConfigLoader
report_id = params[:report_id]
loader_class.new.find_report_by_id!(report_id)
rescue loader_class::MissingReportError
not_found!("Report(#{report_id})")
end
def report
@report ||= load_report
end
end
params do
......@@ -25,20 +38,7 @@ module API
get do
not_found! unless api_endpoints_available?
# Dummy response
{
id: params[:report_id],
title: 'Recent Issues (90 days)',
chart: {
type: 'bar',
series: [
{
id: 'open_merge_requests',
title: 'Merge requests'
}
]
}
}
present report, with: EE::API::Entities::Analytics::Reports::Chart
end
end
end
......
# frozen_string_literal: true
module EE
module API
module Entities
module Analytics
module Reports
class Chart < Grape::Entity
class ChartSeriesConfig < Grape::Entity
expose :id
expose :title
end
class ChartConfig < Grape::Entity
expose :type
expose :series, using: ChartSeriesConfig
end
expose :id
expose :title
expose :chart, using: ChartConfig
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class Chart
attr_reader :type, :series
def initialize(type:, series:)
@type = type
@series = series
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class ConfigLoader
include ::Gitlab::Utils::StrongMemoize
MissingReportError = Class.new(StandardError)
MissingSeriesError = Class.new(StandardError)
DEFAULT_CONFIG = File.join('ee', 'fixtures', 'report_pages', 'default.yml').freeze
def find_report_by_id!(report_id)
raw_report = default_config[report_id.to_s.to_sym]
raise(MissingReportError.new) if raw_report.nil?
ReportBuilder.build(raw_report.merge(id: report_id))
end
def find_series_by_id!(report_id, series_id)
report = find_report_by_id!(report_id)
series = report.find_series_by_id(series_id)
raise(MissingSeriesError.new) if series.nil?
series
end
private
def default_config
strong_memoize(:default_config) do
yaml = File.read(Rails.root.join(DEFAULT_CONFIG).to_s)
::Gitlab::Config::Loader::Yaml.new(yaml).load!
rescue Gitlab::Config::Loader::FormatError
{}
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class Report
attr_reader :id, :title, :chart
def initialize(id:, title:, chart:)
@id = id
@title = title
@chart = chart
end
def find_series_by_id(series_id)
chart.series.find { |series| series.id.to_s.eql?(series_id.to_s) }
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class ReportBuilder
def self.build(raw_report)
series = raw_report[:chart][:series].map { |id, series| build_series(series.merge(id: id)) }
chart = Chart.new(type: raw_report[:chart][:type], series: series)
Report.new(
id: raw_report[:id],
title: raw_report[:title],
chart: chart
)
end
def self.build_series(raw_series)
Series.new(
id: raw_series[:id],
title: raw_series[:title],
data_retrieval_options: {
data_retrieval: raw_series[:data_retrieval]
}.merge(raw_series[:data_retrieval_options])
)
end
private_class_method :build_series
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module Reports
class Series
attr_reader :id, :title, :data_retrieval_options
def initialize(id:, title:, data_retrieval_options:)
@id = id
@title = title
@data_retrieval_options = data_retrieval_options
end
end
end
end
end
{
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" },
"chart": {
"type": "object",
"properties": {
"type": { "type": "string" },
"series": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" }
},
"additionalProperties": false
}
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Analytics::Reports::ConfigLoader do
let(:report_id) { 'recent_merge_requests_by_group' }
shared_examples 'missing report_id' do
it 'raises ReportNotFoundError' do
expect { subject }.to raise_error(described_class::MissingReportError)
end
end
shared_examples 'missing series_id' do
it 'raises ReportNotFoundError' do
expect { subject }.to raise_error(described_class::MissingSeriesError)
end
end
describe '#find_report_by_id' do
subject { described_class.new.find_report_by_id!(report_id) }
context 'when unknown report_id is given' do
let(:report_id) { 'unknown_report_id' }
include_examples 'missing report_id'
end
context 'when nil report_id is given' do
let(:report_id) { nil }
include_examples 'missing report_id'
end
it 'loads the report configuration' do
expect(subject.title).to eq('Recent Issues (90 days)')
end
end
describe '#find_series_by_id' do
let(:series_id) { 'open_merge_requests' }
subject { described_class.new.find_series_by_id!(report_id, series_id) }
context 'when unknown report_id is given' do
let(:report_id) { 'unknown_report_id' }
include_examples 'missing report_id'
end
context 'when unknown series_id is given' do
let(:series_id) { 'unknown_series_id' }
include_examples 'missing series_id'
end
context 'when nil series_id is given' do
let(:series_id) { nil }
include_examples 'missing series_id'
end
it 'loads the report configuration' do
expect(subject.title).to eq('Merge Requests')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Analytics::Reports::ReportBuilder do
describe '#build' do
let(:config_path) { Rails.root.join(Gitlab::Analytics::Reports::ConfigLoader::DEFAULT_CONFIG).to_s }
let(:report_file) { Gitlab::Config::Loader::Yaml.new(File.read(config_path)).load! }
subject { described_class.build(report_file[:recent_merge_requests_by_group]) }
it 'builds the report object' do
expect(subject).to be_a_kind_of(Gitlab::Analytics::Reports::Report)
expect(subject.chart).to be_a_kind_of(Gitlab::Analytics::Reports::Chart)
expect(subject.chart.series.first).to be_a_kind_of(Gitlab::Analytics::Reports::Series)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Analytics::Reports::Report do
describe '#find_series_by_id' do
let(:series) { Gitlab::Analytics::Reports::Series.new(id: 'series_1', title: 'series title', data_retrieval_options: nil) }
let(:chart) { Gitlab::Analytics::Reports::Chart.new(type: 'bar', series: [series]) }
subject { described_class.new(id: 'id', title: 'title', chart: chart) }
it 'returns the series object' do
expect(subject.find_series_by_id('series_1')).to eq(series)
end
it 'returns nil when series cannot be found' do
expect(subject.find_series_by_id('unknown')).to be_nil
end
end
end
......@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe API::Analytics::Reports do
let_it_be(:user) { create(:user) }
let_it_be(:report_id) { 'some_report_id' }
let_it_be(:report_id) { 'recent_merge_requests_by_group' }
shared_examples 'error response examples' do
context 'when `report_pages` feature flag is off' do
......@@ -46,7 +46,19 @@ RSpec.describe API::Analytics::Reports do
api_call
expect(response).to have_gitlab_http_status(:ok)
expect(response.parsed_body["id"]).to eq(report_id)
expect(response.parsed_body['id']).to eq(report_id)
expect(response.parsed_body).to match_schema('analytics/reports/chart', dir: 'ee')
end
context 'when unknown report_id is given' do
let(:report_id) { 'unknown_report_id' }
it 'renders 404, not found' do
api_call
expect(response).to have_gitlab_http_status(:not_found)
expect(response.parsed_body['message']).to eq('404 Report(unknown_report_id) Not Found')
end
end
include_examples 'error response examples'
......@@ -63,7 +75,7 @@ RSpec.describe API::Analytics::Reports do
api_call
expect(response).to have_gitlab_http_status(:ok)
expect(response.parsed_body["datasets"].size).to eq(1)
expect(response.parsed_body['datasets'].size).to eq(1)
end
include_examples 'error response examples'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment