Commit a33c1984 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch 'ag-remove-dormat-code-analytics-code' into 'master'

Remove dormant Code Analytics'  code

See merge request gitlab-org/gitlab!22984
parents 9fd594b7 b7910eb4
......@@ -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