feat: 新增项目雷达图计算功能

This commit is contained in:
lexiaozhang 2023-08-14 09:54:14 +08:00
parent 5fb8c7739f
commit 1300229d49
5 changed files with 201 additions and 1 deletions

View File

@ -4,7 +4,7 @@ class ProjectsController < ApplicationController
include ProjectsHelper include ProjectsHelper
include Acceleratorable include Acceleratorable
before_action :require_login, except: %i[index branches branches_slice group_type_list simple show fork_users praise_users watch_users recommend banner_recommend about menu_list verify_auth_token] before_action :require_login, except: %i[index branches branches_slice group_type_list simple show fork_users praise_users watch_users recommend banner_recommend about menu_list verify_auth_token score]
before_action :require_profile_completed, only: [:create, :migrate,:verify_auth_token] before_action :require_profile_completed, only: [:create, :migrate,:verify_auth_token]
before_action :load_repository, except: %i[index group_type_list migrate create recommend banner_recommend verify_auth_token] before_action :load_repository, except: %i[index group_type_list migrate create recommend banner_recommend verify_auth_token]
before_action :authorizate_user_can_edit_project!, only: %i[update] before_action :authorizate_user_can_edit_project!, only: %i[update]
@ -291,6 +291,23 @@ class ProjectsController < ApplicationController
end end
end end
def score
project_evaluation = ProjectEvaluations.find_by(project_id: @project.id)
response_data = {
influence_norm_score: project_evaluation&.norm_influence || 0,
community_norm_score: project_evaluation&.norm_community || 0,
vitality_norm_score: project_evaluation&.norm_vitality || 0,
health_norm_score: project_evaluation&.norm_health || 0,
trend_norm_score: project_evaluation&.norm_trend || 0,
score: project_evaluation&.score || 0,
updated_at: project_evaluation&.updated_at&.strftime("%Y-%m-%d %H:%M")
}
render json: response_data
end
private private

View File

@ -111,6 +111,7 @@ class Project < ApplicationRecord
has_one :project_educoder, dependent: :destroy has_one :project_educoder, dependent: :destroy
has_one :project_score, dependent: :destroy has_one :project_score, dependent: :destroy
has_one :project_evaluations, dependent: :destroy
has_one :repository, dependent: :destroy has_one :repository, dependent: :destroy
has_many :pull_requests, dependent: :destroy has_many :pull_requests, dependent: :destroy
has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy

View File

@ -0,0 +1,160 @@
# == Schema Information
#
# Table name: project_evaluations
#
# id :integer not null, primary key
# project_id :integer
# influence :float(24) default("0")
# community :float(24) default("0")
# vitality :float(24) default("0")
# health :float(24) default("0")
# trend :float(24) default("0")
# norm_influence :float(24) default("0")
# norm_community :float(24) default("0")
# norm_vitality :float(24) default("0")
# norm_health :float(24) default("0")
# norm_trend :float(24) default("0")
# score :float(24) default("0")
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# fk_rails_f2d256da0f (project_id)
#
class ProjectEvaluations < ApplicationRecord
belongs_to :project
def self.calculate_influence(project)
issues_count = project.issues.issue_issue.pluck(:id).size
pull_requests_count = project.issues.issue_pull_request.pluck(:id).size
comment_count = Journal.joins("INNER JOIN issues ON journals.journalized_id = issues.id")
.where("issues.project_id = ? ", project.id)
.count
influence = project.praises_count * 0.25 +
project.forked_count * 0.3 +
issues_count * 0.5 +
pull_requests_count * 0.5 +
comment_count * 0.5
influence += 500 if project.recommend
influence
end
def self.calculate_community(project)
issue_closed_count = project.issues.issue_issue.where(status_id: 5).size
pull_requests_closed_recent_count = project.issues.issue_pull_request.where("closed_on >= ?", 4.months.ago).size
issue_closed_count * 0.7 + pull_requests_closed_recent_count * 0.3
end
def self.calculate_vitality(project)
first_page = true
start_page = 1
end_page = 1
page = 1
limit = 50
last_page_count = 0
four_months_ago = 4.months.ago.to_date
loop do
result = Gitea::Repository::Commits::ListSliceService.new(project.owner.login, project.identifier, page: page, limit: limit).call
commits = result.is_a?(Hash) ? result[:body] : []
if first_page
page_count = (result[:total_count] + limit - 1) / limit
end_page = page_count + 1
first_page = false
end
last_page_count = 0
commits.each do |commits_per_day|
commit_date = commits_per_day["commit_date"].to_date
if commit_date > four_months_ago
last_page_count += commits_per_day["Commits"].size
end
end
break if 0 < last_page_count && last_page_count < limit
if last_page_count == limit
start_page = page + 1
else
end_page = page
end
break unless start_page < end_page
page = (start_page + end_page) / 2
end
commit_recent_count = (page - 1) * limit + last_page_count
@commit_recent_count = commit_recent_count
release_recent_count = project.repository.version_releases.where("created_at >= ?", 4.months.ago).size
commit_recent_count * 0.5 + release_recent_count * 0.8
end
def self.calculate_health(project)
page = 1
limit = 50
total_contributors_count = 0
key_contributors_count = 0
loop do
result = Gitea::Repository::Contributors::GetService.call(project.owner, project.identifier, { page: page, limit: limit })
contributors = result.is_a?(Hash) ? result[:body] : []
total_contributors_count = result[:total_count]
break if contributors.size < limit
key_contributors_count += contributors.count { |contributor| contributor["contributions"] >= 3 }
last_contributor = contributors.last
break unless last_contributor && last_contributor["contributions"] >= 3
page += 1
end
total_contributors_count + key_contributors_count * 3
end
def self.calculate_trend(project)
issue_recent_count = project.issues.issue_issue.where("closed_on >= ?", 4.months.ago).size
commit_recent_count = @commit_recent_count
comment_recent_count = Journal.joins("INNER JOIN issues ON journals.journalized_id = issues.id")
.where("issues.project_id = ? AND journals.created_on > ?", project.id, 4.months.ago)
.count
issue_recent_count * 0.5 + commit_recent_count * 0.35 + comment_recent_count * 0.5
end
def self.update_raw_scores(project)
evaluation = find_or_initialize_by(project_id: project.id)
evaluation.influence = calculate_influence(project)
evaluation.community = calculate_community(project)
evaluation.vitality = calculate_vitality(project)
evaluation.health = calculate_health(project)
evaluation.trend = calculate_trend(project)
evaluation.save
end
def self.update_normalized_scores
max_influence = ProjectEvaluations.maximum(:influence)
min_influence = ProjectEvaluations.minimum(:influence)
max_community = ProjectEvaluations.maximum(:community)
min_community = ProjectEvaluations.minimum(:community)
max_vitality = ProjectEvaluations.maximum(:vitality)
min_vitality = ProjectEvaluations.minimum(:vitality)
max_health = ProjectEvaluations.maximum(:health)
min_health = ProjectEvaluations.minimum(:health)
max_trend = ProjectEvaluations.maximum(:trend)
min_trend = ProjectEvaluations.minimum(:trend)
ProjectEvaluations.all.each do |evaluation|
evaluation.norm_influence = (evaluation.influence - min_influence) / (max_influence - min_influence)
evaluation.norm_community = (evaluation.community - min_community) / (max_community - min_community)
evaluation.norm_vitality = (evaluation.vitality - min_vitality) / (max_vitality - min_vitality)
evaluation.norm_health = (evaluation.health - min_health) / (max_health - min_health)
evaluation.norm_trend = (evaluation.trend - min_trend) / (max_trend - min_trend)
evaluation.score = evaluation.norm_influence * 0.3 +
evaluation.norm_community * 0.2 +
evaluation.norm_vitality * 0.15 +
evaluation.norm_health * 0.1 +
evaluation.norm_trend * 0.25
evaluation.save
end
end
end

View File

@ -514,6 +514,7 @@ Rails.application.routes.draw do
get :forks, to: 'projects#fork_users' get :forks, to: 'projects#fork_users'
match :about, :via => [:get, :put, :post] match :about, :via => [:get, :put, :post]
post :quit post :quit
get :score
end end
end end

View File

@ -0,0 +1,21 @@
# encoding: utf-8
class CreateProjectEvaluations < ActiveRecord::Migration[5.2]
def change
create_table :project_evaluations do |t|
t.integer :project_id, foreign_key: true, index: true
t.float :influence, default: 0, comment: "影响力"
t.float :community, default: 0, comment: "社区活跃度"
t.float :vitality, default: 0, comment: "代码活跃度"
t.float :health, default: 0, comment: "团队健康"
t.float :trend, default: 0, comment: "流行趋势"
t.float :norm_influence, default: 0, comment: "归一化影响力"
t.float :norm_community, default: 0, comment: "归一化社区活跃度"
t.float :norm_vitality, default: 0, comment: "归一化代码活跃度"
t.float :norm_health, default: 0, comment: "归一化团队健康"
t.float :norm_trend, default: 0, comment: "归一化流行趋势"
t.float :score, default: 0, comment: "总分"
t.timestamps
end
add_foreign_key :project_evaluations, :projects, column: :project_id
end
end