Commit cd2b19bf authored by Michael Kozono's avatar Michael Kozono

Merge branch '32454-tasks-by-type-chart-query' into 'master'

Query backend for tasks by type chart

Closes #32454

See merge request gitlab-org/gitlab!17944
parents bc8c2026 4d5caa89
......@@ -9,24 +9,6 @@ class Analytics::TasksByTypeController < Analytics::ApplicationController
before_action :validate_label_ids
before_action :prepare_date_range
# Mocked data, this will be replaced with real implementation
class TasksByType
LabelCountResult = Struct.new(:label, :series)
def counts_by_labels
[
LabelCountResult.new(GroupLabel.new(id: 1, title: 'label 1'), [
["2018-01-01", 23],
["2018-01-02", 5]
]),
LabelCountResult.new(GroupLabel.new(id: 2, title: 'label 3'), [
["2018-01-01", 3],
["2018-01-03", 10]
])
]
end
end
def show
render json: Analytics::TasksByTypeLabelEntity.represent(counts_by_labels)
end
......@@ -34,7 +16,13 @@ class Analytics::TasksByTypeController < Analytics::ApplicationController
private
def counts_by_labels
TasksByType.new.counts_by_labels
Gitlab::Analytics::TypeOfWork::TasksByType.new(group: @group, current_user: current_user, params: {
subject: params[:subject],
label_ids: Array(params[:label_ids]),
project_ids: Array(params[:project_ids]),
created_after: @created_after.to_time.utc.beginning_of_day,
created_before: @created_before.to_time.utc.end_of_day
}).counts_by_labels
end
def validate_label_ids
......
---
title: Data API endpoint for tasks by type chart within the analytics workspace
merge_request: 17944
author:
type: added
# frozen_string_literal: true
module Gitlab
module Analytics
module TypeOfWork
class TasksByType
LabelCountResult = Struct.new(:label, :series)
FINDER_CLASSES = {
MergeRequest.to_s => MergeRequestsFinder,
Issue.to_s => IssuesFinder
}.freeze
def initialize(group:, params:, current_user:)
@group = group
@params = params
@finder = finder_class.new(current_user, finder_params)
end
def counts_by_labels
format_result(query_result)
end
private
attr_reader :group, :params, :finder
def finder_class
FINDER_CLASSES.fetch(params[:subject], FINDER_CLASSES.keys.first)
end
def format_result(result)
result.each_with_object({}) do |((label_id, date), count), hash|
label = labels_by_id.fetch(label_id)
hash[label_id] ||= LabelCountResult.new(label, [])
hash[label_id].series << [date, count]
end.values
end
def finder_params
{ include_subgroups: true, group_id: group.id }.merge(params.slice(:created_after, :created_before))
end
# rubocop: disable CodeReuse/ActiveRecord
def query_result
finder
.execute
.joins(:label_links)
.where(filters)
.group(label_id_column, date_column)
.reorder(nil)
.count(subject_table[:id])
end
def filters
{}.tap do |hash|
hash[:label_links] = { label_id: labels_by_id.keys }
hash[:project_id] = params[:project_ids] unless params[:project_ids].blank?
end
end
def labels
@labels ||= GroupLabel.where(id: params[:label_ids])
end
# rubocop: enable CodeReuse/ActiveRecord
def label_id_column
LabelLink.arel_table[:label_id]
end
# Generating `DATE(created_at)` string
def date_column
Arel::Nodes::NamedFunction.new('DATE', [subject_table[:created_at]]).to_sql
end
def subject_table
finder.klass.arel_table
end
def labels_by_id
@labels_by_id = labels.each_with_object({}) { |label, hash| hash[label.id] = label }
end
end
end
end
end
......@@ -5,18 +5,20 @@ require 'spec_helper'
describe Analytics::TasksByTypeController do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:params) { { group_id: group.full_path, label_ids: [1, 2], created_after: '2018-01-01' } }
let(:subject) { get :show, params: params }
let(:label) { create(:group_label, group: group) }
let(:params) { { group_id: group.full_path, label_ids: [label.id], created_after: 10.days.ago, subject: 'Issue' } }
let!(:issue) { create(:labeled_issue, created_at: 5.days.ago, project: create(:project, group: group), labels: [label]) }
subject { get :show, params: params }
before do
stub_licensed_features(type_of_work_analytics: true)
stub_feature_flags(Gitlab::Analytics::TASKS_BY_TYPE_CHART_FEATURE_FLAG => true)
group.add_reporter(user)
sign_in(user)
end
context 'when valid parameters are given' do
it 'succeeds' do
subject
......@@ -24,6 +26,15 @@ describe Analytics::TasksByTypeController do
expect(response).to match_response_schema('analytics/tasks_by_type', dir: 'ee')
end
it 'returns valid count' do
subject
date, count = json_response.first["series"].first
expect(Date.parse(date)).to eq(issue.created_at.to_date)
expect(count).to eq(1)
end
end
context 'when user access level is lower than reporter' do
before do
group.add_guest(user)
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::TypeOfWork::TasksByType do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:other_group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:label) { create(:group_label, group: group) }
let_it_be(:label_for_subgroup) { create(:group_label, group: group) }
let_it_be(:other_label) { create(:group_label, group: other_group) }
let_it_be(:project) { create(:project, group: group) }
let(:params) do
{
group: group,
params: { label_ids: [label.id, label_for_subgroup.id], created_after: 10.days.ago, created_before: Date.today },
current_user: user
}
end
subject do
described_class.new(params).counts_by_labels
end
around do |example|
Timecop.freeze { example.run }
end
before do
group.add_reporter(user)
end
shared_examples '#counts_by_labels' do
let!(:with_label) do
create(factory_name, {
:created_at => 3.days.ago,
:labels => [label],
project_attribute_name => project
})
end
let!(:with_label_on_other_date) do
create(factory_name, {
:created_at => 2.days.ago,
:labels => [label],
project_attribute_name => create(:project, group: group)
})
end
let!(:with_subgroup) do
create(factory_name, {
:created_at => 3.days.ago,
:labels => [label, label_for_subgroup],
project_attribute_name => create(:project, group: subgroup)
})
end
let!(:outside_group) do
create(factory_name, {
:created_at => 3.days.ago,
:labels => [other_label],
project_attribute_name => create(:project, group: other_group)
})
end
def label_count_for(label, result)
label_count_result = result.find { |r| r.label.id == label.id }
label_count_result.series.sum(&:last) # format: [DATE, COUNT]
end
it 'counts the records by label and date' do
expect(label_count_for(label, subject)).to eq(3)
end
it 'counts should include subgroups' do
expect(label_count_for(label_for_subgroup, subject)).to eq(1)
end
it 'does not include count from outside of the group' do
label_ids = subject.map { |r| r.label.id }
expect(label_ids).to contain_exactly(label.id, label_for_subgroup.id)
end
context 'when group without any record is given' do
before do
params[:group] = create(:group)
end
it { expect(subject).to be_empty }
end
context 'when no labels are given' do
before do
params[:params][:label_ids] = []
end
it { expect(subject).to be_empty }
end
context 'when records are outside of the given time range' do
before do
params[:params][:created_after] = 2.years.ago
params[:params][:created_before] = 1.year.ago
end
it { expect(subject).to be_empty }
end
context 'when filtering by `project_ids`' do
before do
params[:params][:project_ids] = [project.id]
end
it { expect(label_count_for(label, subject)).to eq(1) }
end
end
context 'when subject is `Issue`' do
let(:factory_name) { :labeled_issue }
let(:project_attribute_name) { :project }
before do
params[:params][:subject] = Issue.to_s
end
include_examples '#counts_by_labels'
end
context 'when subject is `MergeRequest`' do
let(:factory_name) { :labeled_merge_request }
let(:project_attribute_name) { :source_project }
before do
params[:params][:subject] = MergeRequest.to_s
end
include_examples '#counts_by_labels'
end
end
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