From 1300229d49a66eb51aeebda17c5d0131309512b1 Mon Sep 17 00:00:00 2001 From: lexiaozhang Date: Mon, 14 Aug 2023 09:54:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E9=9B=B7=E8=BE=BE=E5=9B=BE=E8=AE=A1=E7=AE=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/projects_controller.rb | 19 ++- app/models/project.rb | 1 + app/models/project_evaluations.rb | 160 ++++++++++++++++++ config/routes.rb | 1 + ...230812200835_create_project_evaluations.rb | 21 +++ 5 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 app/models/project_evaluations.rb create mode 100644 db/migrate/20230812200835_create_project_evaluations.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 04982d8c..a7723413 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -4,7 +4,7 @@ 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] 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 :authorizate_user_can_edit_project!, only: %i[update] @@ -291,6 +291,23 @@ class ProjectsController < ApplicationController 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 diff --git a/app/models/project.rb b/app/models/project.rb index 3a868357..38af2c85 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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 diff --git a/app/models/project_evaluations.rb b/app/models/project_evaluations.rb new file mode 100644 index 00000000..e6e83a2b --- /dev/null +++ b/app/models/project_evaluations.rb @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 0d04c498..f8bc66b5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -514,6 +514,7 @@ Rails.application.routes.draw do get :forks, to: 'projects#fork_users' match :about, :via => [:get, :put, :post] post :quit + get :score end end diff --git a/db/migrate/20230812200835_create_project_evaluations.rb b/db/migrate/20230812200835_create_project_evaluations.rb new file mode 100644 index 00000000..717f3e19 --- /dev/null +++ b/db/migrate/20230812200835_create_project_evaluations.rb @@ -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