forked from Trustie/forgeplus
Compare commits
4 Commits
5fb8c7739f
...
824767ca30
| Author | SHA1 | Date |
|---|---|---|
|
|
824767ca30 | |
|
|
8a847ee053 | |
|
|
af15971a3f | |
|
|
1300229d49 |
|
|
@ -0,0 +1,33 @@
|
|||
class Admins::ProjectEvaluationController < Admins::BaseController
|
||||
def update
|
||||
Project.where(is_public: 1, id: Repository.pluck(:project_id)).find_each do |project|
|
||||
ProjectEvaluation.update_raw_scores(project)
|
||||
end
|
||||
ProjectEvaluation.update_normalized_scores
|
||||
|
||||
def evaluate_and_update(interval)
|
||||
ProjectEvaluationTop.top_projects_selection(interval)
|
||||
ProjectEvaluation.update_scores_by_interval(interval)
|
||||
end
|
||||
|
||||
current_date = Date.today
|
||||
|
||||
if current_date.wday == 1
|
||||
evaluate_and_update(:weekly)
|
||||
end
|
||||
|
||||
if current_date.day == 1
|
||||
evaluate_and_update(:monthly)
|
||||
end
|
||||
|
||||
if current_date.day == 1 && [1, 4, 7, 10].include?(current_date.month)
|
||||
evaluate_and_update(:quarterly)
|
||||
end
|
||||
|
||||
if current_date.yday == 1
|
||||
evaluate_and_update(:yearly)
|
||||
end
|
||||
|
||||
render_ok()
|
||||
end
|
||||
end
|
||||
|
|
@ -4,9 +4,9 @@ class ProjectsController < ApplicationController
|
|||
include ProjectsHelper
|
||||
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 top]
|
||||
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 top]
|
||||
before_action :authorizate_user_can_edit_project!, only: %i[update]
|
||||
before_action :project_public?, only: %i[fork_users praise_users watch_users]
|
||||
before_action :request_limit, only: %i[index]
|
||||
|
|
@ -103,7 +103,7 @@ class ProjectsController < ApplicationController
|
|||
# result = Gitea::Repository::Branches::ListService.call(@owner, @project.identifier)
|
||||
result = Gitea::Repository::Branches::ListNameService.call(@owner, @project.identifier, params[:name])
|
||||
@branches = result.is_a?(Hash) ? (result.key?(:status) ? [] : result) : result
|
||||
end
|
||||
end
|
||||
|
||||
def branches_slice
|
||||
return @branches = [] unless @project.forge?
|
||||
|
|
@ -291,6 +291,38 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def score
|
||||
project_evaluation = ProjectEvaluation.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
|
||||
|
||||
def top
|
||||
period = params[:period]
|
||||
time_interval = params[:time_interval]
|
||||
|
||||
if period.present? && time_interval.present?
|
||||
project_evaluation_ids = ProjectEvaluationTop.where(evaluation_period: period, time_interval: time_interval)
|
||||
.pluck(:project_id)
|
||||
|
||||
projects = Project.where(id: project_evaluation_ids).includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units, :project_topics).project_statics_select
|
||||
render json: projects
|
||||
else
|
||||
render_error("参数错误")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ class Project < ApplicationRecord
|
|||
has_one :project_educoder, dependent: :destroy
|
||||
|
||||
has_one :project_score, dependent: :destroy
|
||||
has_one :project_evaluations, dependent: :destroy
|
||||
has_one :repository, dependent: :destroy
|
||||
has_many :pull_requests, dependent: :destroy
|
||||
has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
# == 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
|
||||
# weekly_score :float(24)
|
||||
# monthly_score :float(24)
|
||||
# quarterly_score :float(24)
|
||||
# yearly_score :float(24)
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_project_evaluations_on_project_id (project_id)
|
||||
#
|
||||
|
||||
class ProjectEvaluation < 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 = ProjectEvaluation.maximum(:influence)
|
||||
min_influence = ProjectEvaluation.minimum(:influence)
|
||||
|
||||
max_community = ProjectEvaluation.maximum(:community)
|
||||
min_community = ProjectEvaluation.minimum(:community)
|
||||
|
||||
max_vitality = ProjectEvaluation.maximum(:vitality)
|
||||
min_vitality = ProjectEvaluation.minimum(:vitality)
|
||||
|
||||
max_health = ProjectEvaluation.maximum(:health)
|
||||
min_health = ProjectEvaluation.minimum(:health)
|
||||
|
||||
max_trend = ProjectEvaluation.maximum(:trend)
|
||||
min_trend = ProjectEvaluation.minimum(:trend)
|
||||
|
||||
ProjectEvaluation.find_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
|
||||
|
||||
def self.update_scores_by_interval(interval)
|
||||
score_column = "#{interval}_score"
|
||||
ProjectEvaluation.where.not(score: nil).update_all("#{score_column} = score")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: project_evaluations_tops
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# project_id :integer
|
||||
# time_interval :integer
|
||||
# previous_score :float(24) default("0")
|
||||
# current_score :float(24) default("0")
|
||||
# evaluation_period :integer
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_project_evaluations_tops_on_evaluation_period (evaluation_period)
|
||||
# index_project_evaluations_tops_on_project_id (project_id)
|
||||
# index_project_evaluations_tops_on_time_interval (time_interval)
|
||||
#
|
||||
|
||||
class ProjectEvaluationTop < ApplicationRecord
|
||||
self.table_name = "project_evaluations_tops"
|
||||
belongs_to :project
|
||||
|
||||
enum time_interval: {
|
||||
weekly: 0,
|
||||
monthly: 1,
|
||||
quarterly: 2,
|
||||
yearly: 3,
|
||||
}
|
||||
|
||||
def self.top_projects_selection(time_interval)
|
||||
max_evaluation_period = ProjectEvaluationTop
|
||||
.where(time_interval: time_interval)
|
||||
.maximum(:evaluation_period).to_i
|
||||
last_evaluation = ProjectEvaluationTop
|
||||
.where(time_interval: time_interval, evaluation_period: max_evaluation_period)
|
||||
.first
|
||||
return if last_evaluation&.created_at&.to_date == Date.today
|
||||
|
||||
score_column = "#{time_interval}_score"
|
||||
|
||||
selected_projects = ProjectEvaluation.where.not(score_column.to_sym => nil)
|
||||
.order(Arel.sql("score - #{score_column} DESC"))
|
||||
.limit(100)
|
||||
|
||||
selected_projects.each do |project_evaluation|
|
||||
ProjectEvaluationTop.create!(
|
||||
project: project_evaluation.project,
|
||||
time_interval: time_interval,
|
||||
previous_score: project_evaluation.send(score_column),
|
||||
current_score: project_evaluation.score,
|
||||
evaluation_period: max_evaluation_period + 1,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -242,6 +242,7 @@ Rails.application.routes.draw do
|
|||
get :recommend
|
||||
get :banner_recommend
|
||||
post :verify_auth_token
|
||||
get :top
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -514,6 +515,7 @@ Rails.application.routes.draw do
|
|||
get :forks, to: 'projects#fork_users'
|
||||
match :about, :via => [:get, :put, :post]
|
||||
post :quit
|
||||
get :score
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -799,6 +801,11 @@ Rails.application.routes.draw do
|
|||
resources :sites
|
||||
resources :edu_settings
|
||||
resources :project_languages
|
||||
resources :project_evaluation, only: [] do
|
||||
collection do
|
||||
put :update
|
||||
end
|
||||
end
|
||||
resources :project_categories
|
||||
resources :project_licenses
|
||||
resources :project_ignores
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# encoding: utf-8
|
||||
class AddTimeBasedScoresToProjectEvaluations < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
change_table :project_evaluations do |t|
|
||||
t.float :weekly_score, comment: "每周分数"
|
||||
t.float :monthly_score, comment: "每月分数"
|
||||
t.float :quarterly_score, comment: "每季度分数"
|
||||
t.float :yearly_score, comment: "每年分数"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
class CreateProjectEvaluationTops < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :project_evaluations_tops do |t|
|
||||
t.integer :project_id, foreign_key: true, index: true
|
||||
t.column :time_interval, :integer, comment: "时间间隔,0: weekly, 1: monthly, 2: quarterly, 3: yearly"
|
||||
t.float :previous_score, default: 0, comment: "上次得分"
|
||||
t.float :current_score, default: 0, comment: "本次得分"
|
||||
t.integer :evaluation_period, comment: "评选期数"
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :project_evaluations_tops, :evaluation_period, name: "index_project_evaluations_tops_on_evaluation_period"
|
||||
add_index :project_evaluations_tops, :time_interval, name: "index_project_evaluations_tops_on_time_interval"
|
||||
add_foreign_key :project_evaluations_tops, :projects, column: :project_id
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue