Commit b7910eb4 authored by Aakriti Gupta's avatar Aakriti Gupta Committed by Peter Leitzen

Remove dormant Code Analytics' code

This feature is frozen and there are no plans
of continuation on the inital plan.
parent 9fd594b7
......@@ -21,7 +21,6 @@
- cloud_native_installation
- cluster_cost_optimization
- cluster_monitoring
- code_analytics
- code_quality
- code_review
- collection
......
......@@ -185,9 +185,9 @@ their color is `#428BCA`.
`<Category Name>` is the category name as it is in the single source of truth for categories at
<https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml>.
For instance, the "Code Analytics" category is represented by the
~"Category:Code Analytics" label in the `gitlab-org` group since its
`code_analytics.name` value is "Code Analytics".
For instance, the "DevOps Score" category is represented by the
~"Category:DevOps Score" label in the `gitlab-org` group since its
`devops_score.name` value is "DevOps Score".
If a category's label doesn't respect this naming convention, it should be specified
with [the `label` attribute](https://about.gitlab.com/handbook/marketing/website/#category-attributes)
......
......@@ -6,8 +6,6 @@ class Analytics::AnalyticsController < Analytics::ApplicationController
redirect_to analytics_productivity_analytics_path
elsif Gitlab::Analytics.cycle_analytics_enabled?
redirect_to analytics_cycle_analytics_path
elsif Gitlab::Analytics.code_analytics_enabled?
redirect_to analytics_code_analytics_path
else
render_404
end
......
# frozen_string_literal: true
class Analytics::CodeAnalyticsController < Analytics::ApplicationController
check_feature_flag Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG
before_action :load_group
before_action :load_project
before_action -> {
check_feature_availability!(:code_analytics)
}, if: -> { request.format.json? }
before_action -> {
authorize_view_by_action!(:view_code_analytics)
}
before_action :validate_params, if: -> { request.format.json? }
def show
respond_to do |format|
format.html
format.json { render json: Analytics::CodeAnalytics::RepositoryFileCommitCountEntity.represent(top_files) }
end
end
private
def validate_params
render(json: { message: 'Invalid parameters', errors: request_params.errors }, status: :unprocessable_entity) if request_params.invalid?
end
def request_params
@request_params ||= Gitlab::Analytics::CodeAnalytics::RequestParams.new(allowed_params)
end
def top_files
Analytics::CodeAnalyticsFinder.new(
project: @project,
file_count: request_params.file_count,
from: request_params.from,
to: request_params.to
).execute
end
def allowed_params
params.permit(:file_count)
end
end
# frozen_string_literal: true
module Analytics
class CodeAnalyticsFinder
RepositoryFileCommitCount = Struct.new(:repository_file, :count)
def initialize(project:, from:, to:, file_count: nil)
@project = project
@from = from
@to = to
@file_count = file_count
end
def execute
result.map do |(id, file_path), count|
RepositoryFileCommitCount.new(
Analytics::CodeAnalytics::RepositoryFile.new(id: id, file_path: file_path),
count
)
end
end
private
def result
@result ||= Analytics::CodeAnalytics::RepositoryFileCommit.top_files(
project: @project,
from: @from,
to: @to,
file_count: @file_count
)
end
end
end
# frozen_string_literal: true
module Analytics
module CodeAnalytics
class RepositoryFile < ApplicationRecord
self.table_name = 'analytics_repository_files'
belongs_to :project
end
end
end
# frozen_string_literal: true
module Analytics
module CodeAnalytics
class RepositoryFileCommit < ApplicationRecord
DEFAULT_FILE_COUNT = 100
MAX_FILE_COUNT = 500
TopFilesLimitError = Class.new(StandardError)
belongs_to :project
belongs_to :analytics_repository_file, class_name: 'Analytics::CodeAnalytics::RepositoryFile'
self.table_name = 'analytics_repository_file_commits'
def self.files_table
Analytics::CodeAnalytics::RepositoryFile.arel_table
end
def self.top_files(project:, from:, to:, file_count: DEFAULT_FILE_COUNT)
file_count ||= DEFAULT_FILE_COUNT
raise TopFilesLimitError if file_count > MAX_FILE_COUNT
joins(:analytics_repository_file)
.select(files_table[:id], files_table[:file_path])
.where(project_id: project.id)
.where(arel_table[:committed_date].gteq(from))
.where(arel_table[:committed_date].lteq(to))
.group(files_table[:id], files_table[:file_path])
.order(arel_table[:commit_count].sum)
.limit(file_count)
.sum(arel_table[:commit_count])
end
private_class_method :files_table
end
end
end
......@@ -51,7 +51,6 @@ class License < ApplicationRecord
board_milestone_lists
ci_cd_projects
cluster_deployments
code_analytics
code_owner_approval_required
commit_committer_check
cross_project_pipelines
......
......@@ -29,7 +29,6 @@ module EE
rule { ~anonymous }.policy do
enable :view_productivity_analytics
enable :view_code_analytics
end
end
end
......
......@@ -57,7 +57,6 @@ module EE
enable :admin_list
enable :admin_board
enable :read_prometheus
enable :view_code_analytics
enable :view_productivity_analytics
enable :view_type_of_work_charts
enable :read_group_timelogs
......
# frozen_string_literal: true
module Analytics
module CodeAnalytics
class RepositoryFileCommitCountEntity < Grape::Entity
expose(:id) { |model| model.repository_file.id }
expose(:name) { |model| model.repository_file.file_path }
expose :count
end
end
end
......@@ -24,8 +24,4 @@ namespace :analytics do
resource :tasks_by_type, controller: :tasks_by_type, only: :show
end
end
constraints(::Constraints::FeatureConstrainer.new(Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG)) do
resource :code_analytics, only: :show
end
end
......@@ -3,13 +3,11 @@
module Gitlab
module Analytics
# Normally each analytics feature should be guarded with a feature flag.
CODE_ANALYTICS_FEATURE_FLAG = :code_analytics
CYCLE_ANALYTICS_FEATURE_FLAG = :cycle_analytics
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG = :productivity_analytics
TASKS_BY_TYPE_CHART_FEATURE_FLAG = :tasks_by_type_chart
FEATURE_FLAGS = [
CODE_ANALYTICS_FEATURE_FLAG,
CYCLE_ANALYTICS_FEATURE_FLAG,
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG,
TASKS_BY_TYPE_CHART_FEATURE_FLAG
......@@ -24,10 +22,6 @@ module Gitlab
FEATURE_FLAGS.any? { |flag| Feature.enabled?(flag, default_enabled: feature_enabled_by_default?(flag)) }
end
def self.code_analytics_enabled?
Feature.enabled?(CODE_ANALYTICS_FEATURE_FLAG)
end
def self.cycle_analytics_enabled?
feature_enabled?(CYCLE_ANALYTICS_FEATURE_FLAG)
end
......
# frozen_string_literal: true
module Gitlab
module Analytics
module CodeAnalytics
class RequestParams
include ActiveModel::Model
include ActiveModel::Validations
attr_writer :file_count
validates :file_count, presence: true, numericality: {
only_integer: true,
greater_than: 0,
less_than_or_equal_to: ::Analytics::CodeAnalytics::RepositoryFileCommit::MAX_FILE_COUNT
}
# The date range will be customizable later, for now we load data for the last 30 days
def from
30.days.ago
end
def to
Date.today
end
def file_count
Integer(@file_count) if @file_count
end
end
end
end
end
......@@ -29,14 +29,6 @@ describe Analytics::AnalyticsController do
expect(response).to redirect_to(analytics_productivity_analytics_path)
end
it 'redirects to code analytics' do
stub_feature_flags(Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG => true)
get :index
expect(response).to redirect_to(analytics_code_analytics_path)
end
end
it 'renders 404 all the analytics feature flags are disabled' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Analytics::CodeAnalyticsController do
let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
before do
group.add_reporter(current_user)
stub_licensed_features(code_analytics: true)
sign_in(current_user)
end
describe 'GET show' do
subject { get :show, format: :html, params: {} }
it 'renders successfully without license' do
stub_feature_flags(Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG => true)
stub_licensed_features(code_analytics: false)
subject
expect(response).to have_gitlab_http_status(:ok)
end
it 'renders successfully with license' do
stub_feature_flags(Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG => true)
stub_licensed_features(code_analytics: true)
subject
expect(response).to have_gitlab_http_status(:ok)
end
it 'renders `not_found` when feature flag is disabled' do
stub_licensed_features(code_analytics: true)
stub_feature_flags(Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG => false)
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe 'GET `show` as json' do
let(:params) { { group_id: group.full_path, project_id: project.full_path, file_count: 15 } }
subject { get :show, format: :json, params: params }
it 'renders `forbidden` without proper license' do
stub_feature_flags(Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG => true)
stub_licensed_features(code_analytics: false)
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'renders `not_found` when feature flag is disabled' do
stub_licensed_features(code_analytics: true)
stub_feature_flags(Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG => false)
subject
expect(response).to have_gitlab_http_status(:not_found)
end
context 'when user has lower access than reporter' do
before do
stub_feature_flags(Gitlab::Analytics::CODE_ANALYTICS_FEATURE_FLAG => true)
GroupMember.where(user: current_user).delete_all
group.add_guest(current_user)
end
it 'renders `forbidden`' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when valid parameters are given' do
let_it_be(:file_commit) { create(:analytics_repository_file_commit, committed_date: 2.days.ago, project: project) }
it { expect(response).to be_successful }
it 'renders files with commit count' do
subject
first_repository_file = json_response.first
expect(first_repository_file['name']).to eq(file_commit.analytics_repository_file.file_path)
expect(first_repository_file['count']).to eq(file_commit.commit_count)
end
end
context 'when invalid parameters are given' do
context 'when `file_count` is missing' do
before do
params.delete(:file_count)
end
it 'renders error response' do
subject
expect(json_response['errors']['file_count']).not_to be_empty
end
end
context 'when `file_count` is over the limit' do
before do
params[:file_count] = Analytics::CodeAnalytics::RepositoryFileCommit::MAX_FILE_COUNT + 1
end
it 'renders error response' do
subject
expect(json_response['errors']['file_count']).not_to be_empty
end
end
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :analytics_repository_file, class: 'Analytics::CodeAnalytics::RepositoryFile' do
project
file_path { 'app/db/migrate/file.rb' }
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :analytics_repository_file_commit, class: 'Analytics::CodeAnalytics::RepositoryFileCommit' do
commit_count { 5 }
committed_date { Date.today }
project
analytics_repository_file
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Analytics::CodeAnalyticsFinder do
describe '#execute' do
let_it_be(:project) { create(:project) }
let_it_be(:gemfile) { create(:analytics_repository_file, project: project, file_path: 'Gemfile') }
let_it_be(:user_model) { create(:analytics_repository_file, project: project, file_path: 'app/models/user.rb') }
let_it_be(:app_controller) { create(:analytics_repository_file, project: project, file_path: 'app/controllers/application_controller.rb') }
let_it_be(:date1) { Date.new(2018, 3, 5) }
let_it_be(:date2) { Date.new(2018, 10, 20) }
let_it_be(:date_outside_of_range) { Date.new(2019, 12, 1) }
let_it_be(:gemfile_commit) { create(:analytics_repository_file_commit, project: project, analytics_repository_file: gemfile, committed_date: date1, commit_count: 2) }
let_it_be(:gemfile_commit_other_day) { create(:analytics_repository_file_commit, project: project, analytics_repository_file: gemfile, committed_date: date2, commit_count: 1) }
let_it_be(:user_model_commit) { create(:analytics_repository_file_commit, project: project, analytics_repository_file: user_model, committed_date: date1, commit_count: 5) }
let_it_be(:controller_outside_of_range) { create(:analytics_repository_file_commit, project: project, analytics_repository_file: app_controller, committed_date: date_outside_of_range) }
let(:params) { { project: project } }
subject { described_class.new(params).execute }
def find_file_count(result, file_path)
result.find { |r| r.repository_file.file_path.eql?(file_path) }
end
context 'with no commits in the given date range' do
before do
params[:from] = 5.years.ago
params[:to] = 4.years.ago
end
it 'returns empty array' do
expect(subject).to eq([])
end
end
context 'with commits in the given date range' do
before do
params[:from] = date1
params[:to] = date2
end
it 'sums up the gemfile commits' do
expect(find_file_count(subject, gemfile.file_path).count).to eq(3)
end
it 'includes the user model commit' do
expect(find_file_count(subject, user_model.file_path).count).to eq(5)
end
it 'verifies that the out of range record is persisted' do
expect(controller_outside_of_range).to be_persisted
expect(controller_outside_of_range.committed_date).to eq(date_outside_of_range)
end
it 'does not include items outside of the date range' do
expect(find_file_count(subject, app_controller.file_path)).to be_nil
end
it 'orders the results by commit count' do
result_file_paths = subject.map { |item| item.repository_file.file_path }
expect(result_file_paths).to eq([gemfile.file_path, user_model.file_path])
end
context 'when `file_count` is given' do
before do
params[:file_count] = 1
end
it 'limits the number of files' do
expect(subject.size).to eq(1)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CodeAnalytics::RequestParams do
let(:params) { { file_count: 5 } }
subject { described_class.new(params) }
it 'is valid' do
expect(subject).to be_valid
end
context 'when `file_count` is invalid' do
before do
params[:file_count] = -1
end
it 'is invalid' do
expect(subject).not_to be_valid
expect(subject.errors[:file_count]).not_to be_empty
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Analytics::CodeAnalytics::RepositoryFileCommit do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:analytics_repository_file) }
describe '.top_files' do
let_it_be(:project) { create(:project) }
subject { described_class.top_files(project: project, from: 10.days.ago, to: Date.today) }
context 'when no records matching the query' do
it 'returns empty hash' do
expect(subject).to eq({})
end
end
context 'returns file with the commit count' do
let(:file) { create(:analytics_repository_file, project: project) }
let!(:file_commit1) { create(:analytics_repository_file_commit, { project: project, analytics_repository_file: file, committed_date: 1.day.ago, commit_count: 2 }) }
let!(:file_commit2) { create(:analytics_repository_file_commit, { project: project, analytics_repository_file: file, committed_date: 2.days.ago, commit_count: 2 }) }
it { expect(subject[[file.id, file.file_path]]).to eq(4) }
end
context 'when the `file_count` is higher than allowed' do
it 'raises error' do
max_files = Analytics::CodeAnalytics::RepositoryFileCommit::MAX_FILE_COUNT
expect do
described_class.top_files(project: project, from: 10.days.ago, to: Date.today, file_count: max_files + 1)
end.to raise_error(Analytics::CodeAnalytics::RepositoryFileCommit::TopFilesLimitError)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Analytics::CodeAnalytics::RepositoryFile do
it { is_expected.to belong_to(:project) }
end
......@@ -56,10 +56,6 @@ describe GlobalPolicy do
end
end
describe 'view_code_analytics' do
include_examples 'analytics policy', :view_code_analytics
end
describe 'view_productivity_analytics' do
include_examples 'analytics policy', :view_productivity_analytics
end
......
......@@ -493,10 +493,6 @@ describe GroupPolicy do
end
end
describe 'view_code_analytics' do
include_examples 'analytics policy', :view_code_analytics
end
describe 'view_productivity_analytics' do
include_examples 'analytics policy', :view_productivity_analytics
end
......
......@@ -4550,9 +4550,6 @@ msgstr ""
msgid "Code"
msgstr ""
msgid "Code Analytics"
msgstr ""
msgid "Code Owners"
msgstr ""
......
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