Merge remote-tracking branch 'origin/standalone_develop' into standalone_develop

This commit is contained in:
xxq250 2025-09-01 10:52:47 +08:00
commit 6d65d1163a
26 changed files with 318 additions and 27 deletions

View File

@ -23,7 +23,6 @@ class Api::Pm::PipelinesController < Api::Pm::BaseController
pipeline = Action::Pipeline.find_or_initialize_by(pipeline_name: file.to_s.gsub(".yml", "").gsub(".yaml", ""),
file_name: ".gitea/workflows/#{file}",
branch: project.default_branch,
is_graphic_design: false,
disable: false,
project_id: project.id)
interactor = Repositories::EntriesInteractor.call(@owner, project.identifier, ".gitea/workflows/#{file}", ref: project.default_branch)

View File

@ -24,6 +24,7 @@ class Api::V1::Projects::Pulls::PullsController < Api::V1::BaseController
else
@result_object = Api::V1::Projects::Pulls::ReopenService.call(@project, @pull_request, current_user)
if @result_object
ProjectActions::PullRequestEvent.build(@project, current_user, @pull_request, "reopened")
render_ok
else
render_error("合并请求重新打开失败!")

View File

@ -48,7 +48,7 @@ class JournalsController < ApplicationController
AtmeService.call(current_user, @atme_receivers, journal) if @atme_receivers.size > 0
TouchWebhookJob.set(wait: 5.seconds).perform_later('PullRequestComment', @issue&.id, current_user.id, journal.id, 'created', {})
# @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "journal")
ProjectActions::PullRequestReviewCommentEvent.build(@issue.project, current_user, @issue.pull_request, journal) if @issue.issue_classify == "pull_request"
# author: zxh
# 调用上链API
push_activity_2_blockchain("issue_comment_create", journal) if Site.has_blockchain? && @project.use_blockchain

View File

@ -75,7 +75,7 @@ class PullRequestsController < ApplicationController
SendTemplateMessageJob.perform_later('ProjectPullRequest', current_user.id, @pull_request&.id) if Site.has_notice_menu?
Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}"
AtmeService.call(current_user, @atme_receivers, @pull_request) if @atme_receivers.size > 0
ProjectActions::PullRequestEvent.build(@project, current_user, @pull_request, "opened")
# author: zxh
# 调用上链API
push_activity_2_blockchain("pull_request_create", @pull_request) if Site.has_blockchain? && @project.use_blockchain
@ -177,6 +177,7 @@ class PullRequestsController < ApplicationController
# 合并请求下issue处理为关闭
@issue&.update_attributes!({status_id:5})
SendTemplateMessageJob.perform_later('PullRequestClosed', current_user.id, @pull_request.id) if Site.has_notice_menu?
ProjectActions::PullRequestEvent.build(@project, current_user, @pull_request, "closed")
normal_status(1, "已拒绝")
else
normal_status(-1, '合并失败')
@ -247,6 +248,7 @@ class PullRequestsController < ApplicationController
# 合并请求下issue处理为关闭
@issue&.update_attributes!({status_id:5})
ProjectActions::PullRequestEvent.build(@project, current_user, @pull_request, "closed")
SendTemplateMessageJob.perform_later('PullRequestMerged', current_user.id, @pull_request.id) if Site.has_notice_menu?
normal_status(1, "合并成功")
else

View File

@ -56,6 +56,7 @@ class VersionReleasesController < ApplicationController
if params[:attachment_ids].present?
create_attachments(params[:attachment_ids], version_release)
end
ProjectActions::ReleaseEvent.build(@project, current_user, version_release)
normal_status(0, "发布成功")
else
normal_status(-1, "发布失败")

View File

@ -27,6 +27,9 @@ class WatchersController < ApplicationController
def follow
begin
return normal_status(2, "你已关注了") if current_user.watched?(@target)
if @target.is_a?(Project)
ProjectActions::StarEvent.build(@target, current_user)
end
current_user.watch!(@target)
render_ok({watchers_count: @target.watchers_count, watched: true})
rescue Exception => e

View File

@ -141,6 +141,20 @@ class Issue < ApplicationRecord
end
end
def pm_issue_type_chinese_string
case pm_issue_type
when 1
"需求"
when 2
"任务"
when 3
"缺陷"
else
"疑修"
end
end
def destroy_be_pm_links
PmLink.where(be_linkable_type:"Issue",be_linkable_id:self.id).map(&:destroy)
end

View File

@ -483,36 +483,30 @@ class Journal < ApplicationRecord
end
return content
when 'link_issue'
old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b>[#{i.pm_issue_type}]-<#{i.subject}></b>"}.join("")
new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b>[#{i.pm_issue_type}]-<#{i.subject}></b>"}.join("")
old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| i.pm_issue_type.nil? && i.pm_project_id.nil? ? "疑修 <b>【#{i.subject}】</b>" : "工作项 <b>[#{i.pm_issue_type_chinese_string}]-<#{i.subject}></b>"}.join("")
new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| i.pm_issue_type.nil? && i.pm_project_id.nil? ? "疑修 <b>【#{i.subject}】</b>" : "工作项 <b>[#{i.pm_issue_type_chinese_string}]-<#{i.subject}></b>"}.join("")
if detail.old_value.nil? || detail.old_value.blank?
content += "新建了关联的工作项#{new_value}"
content += "新建了关联的#{new_value}"
else
if detail.value.nil? || detail.value.blank?
content += "删除了关联的工作项#{old_value}"
content += "删除了关联的#{old_value}"
else
content += "新建了关联的工作项#{new_value}"
content += "新建了关联的#{new_value}"
end
end
content.gsub!('1', "需求")
content.gsub!('2', "任务")
content.gsub!('3', "缺陷")
return content
when 'tag_link_issue'
old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b>[#{i.pm_issue_type}]-<#{i.subject}></b>"}.join("")
new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b>[#{i.pm_issue_type}]-<#{i.subject}></b>"}.join("")
old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| i.pm_issue_type.nil? && i.pm_project_id.nil? ? "疑修 <b>【#{i.subject}】</b>" : "工作项 <b>[#{i.pm_issue_type_chinese_string}]-<#{i.subject}></b>"}.join("")
new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| i.pm_issue_type.nil? && i.pm_project_id.nil? ? "疑修 <b>【#{i.subject}】</b>" : "工作项 <b>[#{i.pm_issue_type_chinese_string}]-<#{i.subject}></b>"}.join("")
if detail.old_value.nil? || detail.old_value.blank?
content += "关联了工作项#{new_value}"
content += "关联了#{new_value}"
else
if detail.value.nil? || detail.value.blank?
content += "取消了关联的工作项#{old_value}"
content += "取消了关联的#{old_value}"
else
content += "关联了工作项#{new_value}"
content += "关联了#{new_value}"
end
end
content.gsub!('1', "需求")
content.gsub!('2', "任务")
content.gsub!('3', "缺陷")
return content
when 'issue'
issue = self.issue
@ -535,6 +529,7 @@ class Journal < ApplicationRecord
case detail.property
when 'issue'
content += "创建了<b>疑修</b>"
return content
when 'attachment'
old_value = Attachment.where("BINARY id in (?) or uuid in (?)", detail.old_value.to_s.split(","), detail.old_value.to_s.split(",")).pluck(:filename).join("")
new_value = Attachment.where("BINARY id in (?) or uuid in (?)", detail.value.to_s.split(","), detail.value.to_s.split(",")).pluck(:filename).join("")
@ -544,6 +539,7 @@ class Journal < ApplicationRecord
new_value = "" if new_value.blank?
content += "将附件由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
return content
when 'issue_tag'
old_value = IssueTag.where(id: detail.old_value.split(",")).pluck(:name).join("")
new_value = IssueTag.where(id: detail.value.split(",")).pluck(:name).join("")
@ -553,6 +549,7 @@ class Journal < ApplicationRecord
new_value = "" if new_value.blank?
content += "将标记由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
return content
when 'assigner'
old_value = User.where(id: detail.old_value.split(",")).map{|u| u.real_name}.join("")
new_value = User.where(id: detail.value.split(",")).map{|u| u.real_name}.join("")
@ -562,6 +559,7 @@ class Journal < ApplicationRecord
new_value = "" if new_value.blank?
content += "将负责人由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
return content
when 'attr'
content += ""
case detail.prop_key
@ -604,8 +602,9 @@ class Journal < ApplicationRecord
new_value = "" if new_value.blank?
content += "由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
return content
end
content = self.pm_operate_content if content == ""
end
def journal_content

View File

@ -0,0 +1,24 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectAction < ApplicationRecord
belongs_to :project
belongs_to :user, optional: true
serialize :event_extra, Hash
end

View File

@ -0,0 +1,30 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::ForkEvent < ProjectAction
def self.build(project, user, fork_project)
project_action = new(project: project, user: user)
project_action.event_extra = {
forkee_id: fork_project.id,
forkee_name: fork_project.identifier,
forkee_owner_id: fork_project.owner.id,
forkee_owner_login: fork_project.owner.login
}
project_action.save!
end
end

View File

@ -0,0 +1,29 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::IssueCommentEvent < ProjectAction
def self.build(project, user, issue, journal)
project_action = new(project: project, user: user)
project_action.event_extra = {
issue_id: issue.id,
issue_number: issue.project_issues_index,
body: journal.notes
}
project_action.save!
end
end

View File

@ -0,0 +1,33 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::IssuesEvent < ProjectAction
def self.build(project, user, issue, action)
project_action = new(project: project, user: user)
project_action.event_extra = {
issue_id: issue.id,
issue_number: issue.project_issues_index,
issue_author_id: issue.author_id,
issue_author_login: issue.user.login,
title: issue.subject,
body: issue.description,
action: action
}
project_action.save!
end
end

View File

@ -0,0 +1,41 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::PullRequestEvent < ProjectAction
def self.build(project, user, pull_request, action)
project_action = new(project: project, user: user)
gitea_pull_request = Gitea::PullRequest::FilesService.call(project.owner.login, project.identifier, pull_request.gitea_number, project&.owner&.gitea_token)
project_action.event_extra = {
action: action,
pull_id: pull_request.id,
pull_number: pull_request.gitea_number,
title: pull_request.title,
body: pull_request.body,
git_diff: gitea_pull_request,
additions: gitea_pull_request['TotalAddition'],
deletions: gitea_pull_request['TotalDeletion'],
changed_files: gitea_pull_request['NumFiles'],
merged: pull_request.status == 1,
base_ref: pull_request.base,
head_repo_id: pull_request.is_original ? pull_request.fork_project_id : pull_request.project_id,
head_repo_name: pull_request.project&.identifier,
head_ref: pull_request.head
}
project_action.save!
end
end

View File

@ -0,0 +1,29 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::PullRequestReviewCommentEvent < ProjectAction
def self.build(project, user, pull_request, journal)
project_action = new(project: project, user: user)
project_action.event_extra = {
pull_id: pull_request.id,
pull_number: pull_request.gitea_number,
body: journal.notes
}
project_action.save!
end
end

View File

@ -0,0 +1,29 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::ReleaseEvent < ProjectAction
def self.build(project, user, version_release)
project_action = new(project: project, user: user)
project_action.event_extra = {
tag_name: version_release.tag_name,
name: version_release.name,
body: version_release.body
}
project_action.save!
end
end

View File

@ -0,0 +1,24 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::StarEvent < ProjectAction
def self.build(project, user)
project_action = new(project: project, user: user)
project_action.save!
end
end

View File

@ -31,7 +31,7 @@ class UserAction < ApplicationRecord
def action_name
case action_type
when "DestroyUser" then "用户注销"
when "DestroyUser" then "删除用户"
when "DestroyProject" then "删除项目"
when "Login" then "登录"
when "Logout" then "退出登录"

View File

@ -97,6 +97,9 @@ class Api::V1::Issues::CreateService < ApplicationService
# @信息发送
AtmeService.call(current_user, @atme_receivers, @created_issue) unless receivers_login.blank?
# 项目行为构建
ProjectActions::IssuesEvent.build(@project, current_user, @created_issue, 'opened')
# 发消息
if Site.has_notice_menu?
SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @created_issue&.id, assigner_ids) unless assigner_ids.blank?

View File

@ -40,7 +40,7 @@ class Api::V1::Issues::Journals::CreateService < ApplicationService
@issue.save!
push_activity_2_blockchain("issue_comment_create", @created_journal) if Site.has_blockchain? && @issue.project&.use_blockchain
ProjectActions::IssueCommentEvent.build(@issue.project, current_user, @issue, @created_journal)
# @信息发送
AtmeService.call(current_user, @atme_receivers, @created_journal) unless receivers_login.blank?
TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueComment', @issue&.id, @current_user.id, @created_journal.id, 'created', {})

View File

@ -98,6 +98,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
build_after_issue_journal_details if @updated_issue.previous_changes.present? # 操作记录
build_previous_issue_changes
build_issue_project_trends if status_id.present? # 开关时间记录
build_project_actions
build_cirle_blockchain_token if blockchain_token_num.present?
unless @project.id.zero?
# @信息发送
@ -185,6 +186,15 @@ class Api::V1::Issues::UpdateService < ApplicationService
end
end
def build_project_actions
if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][1] == 5
ProjectActions::IssuesEvent.build(@project, current_user, @updated_issue, 'closed')
end
if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][0] == 5
ProjectActions::IssuesEvent.build(@project, current_user, @updated_issue, 'reopened')
end
end
def build_after_issue_journal_details
begin
# 更改标题

View File

@ -39,7 +39,8 @@ class Projects::ForkService < ApplicationService
ForkUser.create(project_id: @project.id, fork_project_id: clone_project.id, user_id: clone_project.user_id)
SendTemplateMessageJob.perform_later('ProjectForked', @target_owner.id, @project.id) if Site.has_notice_menu?
ProjectActions::ForkEvent.build(@project, @target_owner, clone_project)
clone_project
end
rescue => e

View File

@ -26,7 +26,7 @@
<li><%= sidebar_item(admins_faqs_path, 'FAQ', icon: 'question-circle', controller: 'admins-faqs', has_permission: current_user.admin? || current_user.business?) %></li>
<li><%= sidebar_item(admins_nps_path, 'NPS用户调研', icon: 'question-circle', controller: 'admins-nps', has_permission: current_user.admin? || current_user.business?) %></li>
<li><%= sidebar_item(admins_feedbacks_path, '用户反馈', icon: 'question-circle', controller: 'admins-feedbacks', has_permission: current_user.admin? || current_user.business?) %></li>
<li><%= sidebar_item(admins_user_actions_path, '操作记录', icon: 'pencil-square', controller: 'admins-user_actions', has_permission: current_user.admin?) %></li>
<li><%= sidebar_item(admins_user_actions_path, '操作日志', icon: 'pencil-square', controller: 'admins-user_actions', has_permission: current_user.admin?) %></li>
<li><%= sidebar_item(admins_system_notifications_path, '系统公告配置', icon: 'bell', controller: 'admins-system_notifications', has_permission: current_user.admin? || current_user.business?) %></li>
<% end %>
</li>

View File

@ -5,7 +5,7 @@
<div class="box search-form-container user-list-form">
<%= form_tag(admins_user_actions_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
操作类型:
<% action_type_options = [['自定义',''],['用户注销','DestroyUser'], ['删除项目', 'DestroyProject'], ['删除组织', 'DestroyOrganization']] %>
<% action_type_options = [['自定义',''],['删除用户','DestroyUser'], ['删除项目', 'DestroyProject'], ['删除组织', 'DestroyOrganization']] %>
<%= select_tag(:action_type_select, options_for_select(action_type_options), class: 'form-control') %>
<%= text_field_tag(:action_type, params[:action_type], class: 'form-control col-sm-2 ml-3',style: 'display:;', placeholder: '自定义操作类型检索') %>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: '操作人用户名/邮箱/手机号检索') %>

View File

@ -1,4 +1,11 @@
json.(issue, :id, :subject, :project_issues_index)
json.(issue, :id, :subject, :project_issues_index, :project_id)
json.project do
if issue.project.present?
json.partial! "api/v1/projects/simple_detail", locals: {project: issue.project}
else
json.nil!
end
end
json.blockchain_token_num (Site.has_blockchain? && issue.project&.use_blockchain) ? issue.blockchain_token_num : nil
json.created_at issue.created_on.strftime("%Y-%m-%d %H:%M")
json.updated_at issue.updated_on.strftime("%Y-%m-%d %H:%M")

View File

@ -8,7 +8,7 @@ end
if journal.is_journal_detail?
detail = journal.journal_details.take
json.operate_category @issue.pm_issue_type.nil? ? detail.property == "attr" ? detail.prop_key : detail.property : journal.pm_operate_category
json.operate_content journal.is_journal_detail? ? @issue.pm_issue_type.nil? ? journal.operate_content : journal.pm_operate_content : nil
json.operate_content journal.is_journal_detail? ? @issue.pm_project_id.nil? && @issue.pm_issue_type.nil? ? journal.operate_content : journal.pm_operate_content : nil
else
json.notes journal.notes
json.comments_count journal.comments_count

View File

@ -0,0 +1,12 @@
class CreateProjectActions < ActiveRecord::Migration[5.2]
def change
create_table :project_actions do |t|
t.references :project
t.string :type
t.integer :user_id
t.text :event_extra
t.timestamps
end
end
end