Merge branch 'pre_trustie_server' into trustie_server
This commit is contained in:
		
						commit
						38ef156b55
					
				| 
						 | 
					@ -10,6 +10,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Ignore lock config file
 | 
					# Ignore lock config file
 | 
				
			||||||
*.log
 | 
					*.log
 | 
				
			||||||
 | 
					.rubocop.yml
 | 
				
			||||||
.env
 | 
					.env
 | 
				
			||||||
# mac
 | 
					# mac
 | 
				
			||||||
*.DS_Store
 | 
					*.DS_Store
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										6
									
								
								Gemfile
								
								
								
								
							| 
						 | 
					@ -26,7 +26,7 @@ gem 'roo-xls'
 | 
				
			||||||
gem 'simple_xlsx_reader', '~>1.0.4'
 | 
					gem 'simple_xlsx_reader', '~>1.0.4'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'rubyzip'
 | 
					gem 'rubyzip'
 | 
				
			||||||
 | 
					gem 'sonarqube', :git => 'https://gitlink.org.cn/KingChan/sonarqube.git'
 | 
				
			||||||
gem 'spreadsheet'
 | 
					gem 'spreadsheet'
 | 
				
			||||||
gem 'ruby-ole'
 | 
					gem 'ruby-ole'
 | 
				
			||||||
# 导出为xlsx
 | 
					# 导出为xlsx
 | 
				
			||||||
| 
						 | 
					@ -70,6 +70,7 @@ group :development do
 | 
				
			||||||
  gem 'web-console', '>= 3.3.0'
 | 
					  gem 'web-console', '>= 3.3.0'
 | 
				
			||||||
  gem 'listen', '>= 3.0.5', '< 3.2'
 | 
					  gem 'listen', '>= 3.0.5', '< 3.2'
 | 
				
			||||||
  gem 'spring'
 | 
					  gem 'spring'
 | 
				
			||||||
 | 
					  gem 'pry-rails'
 | 
				
			||||||
  gem 'spring-watcher-listen', '~> 2.0.0'
 | 
					  gem 'spring-watcher-listen', '~> 2.0.0'
 | 
				
			||||||
  gem "annotate", "~> 2.6.0"
 | 
					  gem "annotate", "~> 2.6.0"
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -114,7 +115,6 @@ gem 'aasm'
 | 
				
			||||||
gem 'enumerize'
 | 
					gem 'enumerize'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'diffy'
 | 
					gem 'diffy'
 | 
				
			||||||
 | 
					 | 
				
			||||||
gem 'deep_cloneable', '~> 3.0.0'
 | 
					gem 'deep_cloneable', '~> 3.0.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# oauth2
 | 
					# oauth2
 | 
				
			||||||
| 
						 | 
					@ -142,3 +142,5 @@ gem 'doorkeeper'
 | 
				
			||||||
gem 'doorkeeper-jwt'
 | 
					gem 'doorkeeper-jwt'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'gitea-client', '~> 1.5.8'
 | 
					gem 'gitea-client', '~> 1.5.8'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gem 'loofah', '~> 2.20.0'
 | 
				
			||||||
							
								
								
									
										24
									
								
								Gemfile.lock
								
								
								
								
							
							
						
						
									
										24
									
								
								Gemfile.lock
								
								
								
								
							| 
						 | 
					@ -1,3 +1,11 @@
 | 
				
			||||||
 | 
					GIT
 | 
				
			||||||
 | 
					  remote: https://gitlink.org.cn/KingChan/sonarqube.git
 | 
				
			||||||
 | 
					  revision: 80f07d427322ef02c0714c77a382e87aed0bef81
 | 
				
			||||||
 | 
					  specs:
 | 
				
			||||||
 | 
					    sonarqube (1.3.0)
 | 
				
			||||||
 | 
					      httparty (~> 0.14, >= 0.14.0)
 | 
				
			||||||
 | 
					      terminal-table (~> 1.5, >= 1.5.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GEM
 | 
					GEM
 | 
				
			||||||
  remote: https://mirrors.cloud.tencent.com/rubygems/
 | 
					  remote: https://mirrors.cloud.tencent.com/rubygems/
 | 
				
			||||||
  specs:
 | 
					  specs:
 | 
				
			||||||
| 
						 | 
					@ -135,7 +143,7 @@ GEM
 | 
				
			||||||
    fugit (1.4.1)
 | 
					    fugit (1.4.1)
 | 
				
			||||||
      et-orbi (~> 1.1, >= 1.1.8)
 | 
					      et-orbi (~> 1.1, >= 1.1.8)
 | 
				
			||||||
      raabro (~> 1.4)
 | 
					      raabro (~> 1.4)
 | 
				
			||||||
    gitea-client (1.4.2)
 | 
					    gitea-client (1.4.6)
 | 
				
			||||||
      rest-client (~> 2.1.0)
 | 
					      rest-client (~> 2.1.0)
 | 
				
			||||||
    globalid (0.4.2)
 | 
					    globalid (0.4.2)
 | 
				
			||||||
      activesupport (>= 4.2.0)
 | 
					      activesupport (>= 4.2.0)
 | 
				
			||||||
| 
						 | 
					@ -150,6 +158,9 @@ GEM
 | 
				
			||||||
    http-accept (1.7.0)
 | 
					    http-accept (1.7.0)
 | 
				
			||||||
    http-cookie (1.0.5)
 | 
					    http-cookie (1.0.5)
 | 
				
			||||||
      domain_name (~> 0.5)
 | 
					      domain_name (~> 0.5)
 | 
				
			||||||
 | 
					    httparty (0.21.0)
 | 
				
			||||||
 | 
					      mini_mime (>= 1.0.0)
 | 
				
			||||||
 | 
					      multi_xml (>= 0.5.2)
 | 
				
			||||||
    i18n (1.8.2)
 | 
					    i18n (1.8.2)
 | 
				
			||||||
      concurrent-ruby (~> 1.0)
 | 
					      concurrent-ruby (~> 1.0)
 | 
				
			||||||
    io-like (0.3.1)
 | 
					    io-like (0.3.1)
 | 
				
			||||||
| 
						 | 
					@ -187,9 +198,9 @@ GEM
 | 
				
			||||||
      mimemagic (~> 0.3.2)
 | 
					      mimemagic (~> 0.3.2)
 | 
				
			||||||
    maruku (0.7.3)
 | 
					    maruku (0.7.3)
 | 
				
			||||||
    method_source (0.9.2)
 | 
					    method_source (0.9.2)
 | 
				
			||||||
    mime-types (3.4.1)
 | 
					    mime-types (3.5.2)
 | 
				
			||||||
      mime-types-data (~> 3.2015)
 | 
					      mime-types-data (~> 3.2015)
 | 
				
			||||||
    mime-types-data (3.2023.0218.1)
 | 
					    mime-types-data (3.2024.0507)
 | 
				
			||||||
    mimemagic (0.3.10)
 | 
					    mimemagic (0.3.10)
 | 
				
			||||||
      nokogiri (~> 1)
 | 
					      nokogiri (~> 1)
 | 
				
			||||||
      rake
 | 
					      rake
 | 
				
			||||||
| 
						 | 
					@ -437,6 +448,8 @@ GEM
 | 
				
			||||||
      actionpack (>= 4.0)
 | 
					      actionpack (>= 4.0)
 | 
				
			||||||
      activesupport (>= 4.0)
 | 
					      activesupport (>= 4.0)
 | 
				
			||||||
      sprockets (>= 3.0.0)
 | 
					      sprockets (>= 3.0.0)
 | 
				
			||||||
 | 
					    terminal-table (1.8.0)
 | 
				
			||||||
 | 
					      unicode-display_width (~> 1.1, >= 1.1.1)
 | 
				
			||||||
    thor (1.0.1)
 | 
					    thor (1.0.1)
 | 
				
			||||||
    thread_safe (0.3.6)
 | 
					    thread_safe (0.3.6)
 | 
				
			||||||
    tilt (2.0.10)
 | 
					    tilt (2.0.10)
 | 
				
			||||||
| 
						 | 
					@ -449,7 +462,7 @@ GEM
 | 
				
			||||||
      execjs (>= 0.3.0, < 3)
 | 
					      execjs (>= 0.3.0, < 3)
 | 
				
			||||||
    unf (0.1.4)
 | 
					    unf (0.1.4)
 | 
				
			||||||
      unf_ext
 | 
					      unf_ext
 | 
				
			||||||
    unf_ext (0.0.8.2)
 | 
					    unf_ext (0.0.9.1)
 | 
				
			||||||
    unicode-display_width (1.6.1)
 | 
					    unicode-display_width (1.6.1)
 | 
				
			||||||
    web-console (3.7.0)
 | 
					    web-console (3.7.0)
 | 
				
			||||||
      actionview (>= 5.0)
 | 
					      actionview (>= 5.0)
 | 
				
			||||||
| 
						 | 
					@ -491,7 +504,7 @@ DEPENDENCIES
 | 
				
			||||||
  enumerize
 | 
					  enumerize
 | 
				
			||||||
  faraday (~> 0.15.4)
 | 
					  faraday (~> 0.15.4)
 | 
				
			||||||
  font-awesome-sass (= 4.7.0)
 | 
					  font-awesome-sass (= 4.7.0)
 | 
				
			||||||
  gitea-client (~> 1.4.2)
 | 
					  gitea-client (~> 1.4.3)
 | 
				
			||||||
  grape-entity (~> 0.7.1)
 | 
					  grape-entity (~> 0.7.1)
 | 
				
			||||||
  groupdate (~> 4.1.0)
 | 
					  groupdate (~> 4.1.0)
 | 
				
			||||||
  harmonious_dictionary (~> 0.0.1)
 | 
					  harmonious_dictionary (~> 0.0.1)
 | 
				
			||||||
| 
						 | 
					@ -539,6 +552,7 @@ DEPENDENCIES
 | 
				
			||||||
  simple_xlsx_reader (~> 1.0.4)
 | 
					  simple_xlsx_reader (~> 1.0.4)
 | 
				
			||||||
  sinatra
 | 
					  sinatra
 | 
				
			||||||
  solargraph (~> 0.38.0)
 | 
					  solargraph (~> 0.38.0)
 | 
				
			||||||
 | 
					  sonarqube!
 | 
				
			||||||
  spreadsheet
 | 
					  spreadsheet
 | 
				
			||||||
  spring
 | 
					  spring
 | 
				
			||||||
  spring-watcher-listen (~> 2.0.0)
 | 
					  spring-watcher-listen (~> 2.0.0)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					// Place all the behaviors and hooks related to the matching controller here.
 | 
				
			||||||
 | 
					// All this logic will automatically be available in application.js.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					// Place all the behaviors and hooks related to the matching controller here.
 | 
				
			||||||
 | 
					// All this logic will automatically be available in application.js.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					// Place all the behaviors and hooks related to the matching controller here.
 | 
				
			||||||
 | 
					// All this logic will automatically be available in application.js.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					// Place all the behaviors and hooks related to the matching controller here.
 | 
				
			||||||
 | 
					// All this logic will automatically be available in application.js.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					// Place all the behaviors and hooks related to the matching controller here.
 | 
				
			||||||
 | 
					// All this logic will automatically be available in application.js.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					// Place all the behaviors and hooks related to the matching controller here.
 | 
				
			||||||
 | 
					// All this logic will automatically be available in application.js.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					// Place all the styles related to the api/pm/issue_links controller here.
 | 
				
			||||||
 | 
					// They will automatically be included in application.css.
 | 
				
			||||||
 | 
					// You can use Sass (SCSS) here: http://sass-lang.com/
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					// Place all the styles related to the api/pm/projects controller here.
 | 
				
			||||||
 | 
					// They will automatically be included in application.css.
 | 
				
			||||||
 | 
					// You can use Sass (SCSS) here: http://sass-lang.com/
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					// Place all the styles related to the api/v1/pm_issues controller here.
 | 
				
			||||||
 | 
					// They will automatically be included in application.css.
 | 
				
			||||||
 | 
					// You can use Sass (SCSS) here: http://sass-lang.com/
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					// Place all the styles related to the api/v1/sonarqube/issues controller here.
 | 
				
			||||||
 | 
					// They will automatically be included in application.css.
 | 
				
			||||||
 | 
					// You can use Sass (SCSS) here: http://sass-lang.com/
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					// Place all the styles related to the api/v1/sonarqubes controller here.
 | 
				
			||||||
 | 
					// They will automatically be included in application.css.
 | 
				
			||||||
 | 
					// You can use Sass (SCSS) here: http://sass-lang.com/
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					// Place all the styles related to the pm/journals controller here.
 | 
				
			||||||
 | 
					// They will automatically be included in application.css.
 | 
				
			||||||
 | 
					// You can use Sass (SCSS) here: http://sass-lang.com/
 | 
				
			||||||
| 
						 | 
					@ -61,9 +61,9 @@ class Action::NodesController < ApplicationController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def node_params
 | 
					  def node_params
 | 
				
			||||||
    if params.require(:action_node)
 | 
					    if params.require(:action_node)
 | 
				
			||||||
      params.require(:action_node).permit(:name, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no)
 | 
					      params.require(:action_node).permit(:name, :label, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no)
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      params.permit(:name, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no)
 | 
					      params.permit(:name, :label, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,26 +73,26 @@ class Admins::DashboardsController < Admins::BaseController
 | 
				
			||||||
    @subject_icon = ["fa-user","fa-git", "fa-sitemap", "fa-warning", "fa-comments", "fa-share-alt", "fa-upload"]
 | 
					    @subject_icon = ["fa-user","fa-git", "fa-sitemap", "fa-warning", "fa-comments", "fa-share-alt", "fa-upload"]
 | 
				
			||||||
    @subject_data = [@user_count, @project_count, @organization_count, @issue_count, @comment_count, @pr_count, @commit_count]
 | 
					    @subject_data = [@user_count, @project_count, @organization_count, @issue_count, @comment_count, @pr_count, @commit_count]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if EduSetting.get("open_baidu_tongji").to_s == "true"
 | 
				
			||||||
    tongji_service = Baidu::TongjiService.new
 | 
					      tongji_service = Baidu::TongjiService.new
 | 
				
			||||||
    @access_token = tongji_service.access_token
 | 
					      @access_token = tongji_service.access_token
 | 
				
			||||||
    Rails.logger.info "baidu_tongji_auth access_token ===== #{@access_token}"
 | 
					      Rails.logger.info "baidu_tongji_auth access_token ===== #{@access_token}"
 | 
				
			||||||
    # @overview_data = tongji_service.api_overview
 | 
					      # @overview_data = tongji_service.api_overview
 | 
				
			||||||
    last_date = DailyPlatformStatistic.order(:date).last
 | 
					      last_date = DailyPlatformStatistic.order(:date).last || Time.now
 | 
				
			||||||
    start_date = last_date.date
 | 
					      start_date = last_date.date
 | 
				
			||||||
    end_date = Time.now
 | 
					      end_date = Time.now
 | 
				
			||||||
    if @access_token.present?
 | 
					      if @access_token.present?
 | 
				
			||||||
      @overview_data = Rails.cache.fetch("dashboardscontroller:baidu_tongji:overview_data", expires_in: 10.minutes) do
 | 
					        @overview_data = Rails.cache.fetch("dashboardscontroller:baidu_tongji:overview_data", expires_in: 10.minutes) do
 | 
				
			||||||
        tongji_service.source_from_batch_add(start_date, end_date)
 | 
					          tongji_service.source_from_batch_add(start_date, end_date)
 | 
				
			||||||
        @overview_data = tongji_service.overview_batch_add(start_date, end_date)
 | 
					          @overview_data = tongji_service.overview_batch_add(start_date, end_date)
 | 
				
			||||||
        @overview_data
 | 
					          @overview_data
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      @current_week_statistic = DailyPlatformStatistic.where(date: current_week)
 | 
				
			||||||
 | 
					      @pre_week_statistic = DailyPlatformStatistic.where(date: pre_week)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @current_week_statistic = DailyPlatformStatistic.where(date: current_week)
 | 
					 | 
				
			||||||
    @pre_week_statistic = DailyPlatformStatistic.where(date: pre_week)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,60 @@
 | 
				
			||||||
 | 
					class Api::Pm::BaseController < ApplicationController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  include Api::ProjectHelper
 | 
				
			||||||
 | 
					  include Api::UserHelper
 | 
				
			||||||
 | 
					  include Api::PullHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # before_action :doorkeeper_authorize!
 | 
				
			||||||
 | 
					  # skip_before_action :user_setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def kaminary_select_paginate(relation)
 | 
				
			||||||
 | 
							limit = params[:limit] || params[:per_page]
 | 
				
			||||||
 | 
							limit = (limit.to_i.zero? || limit.to_i > 200) ? 200 : limit.to_i
 | 
				
			||||||
 | 
							page  = params[:page].to_i.zero? ? 1 : params[:page].to_i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							relation.page(page).per(limit)
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def limit
 | 
				
			||||||
 | 
					    params.fetch(:limit, 15) 
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def page 
 | 
				
			||||||
 | 
					    params.fetch(:page, 1)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def load_project
 | 
				
			||||||
 | 
					    @project = Project.find_by_id(params[:project_id]) || Project.new(id: 0, user_id: 0, name: 'pm_mm', identifier: 'pm_mm', is_public:true)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def load_issue
 | 
				
			||||||
 | 
					    return render_parameter_missing if params[:pm_project_id].blank?
 | 
				
			||||||
 | 
					    @issue = Issue.issue_issue.where(pm_project_id: params[:pm_project_id]).find_by_id(params[:issue_id])
 | 
				
			||||||
 | 
					    render_not_found('疑修不存在!') if @issue.blank?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  # 具有对仓库的管理权限
 | 
				
			||||||
 | 
					  def require_manager_above
 | 
				
			||||||
 | 
					    @project = load_project
 | 
				
			||||||
 | 
					    return render_forbidden if !current_user.admin? && !@project.manager?(current_user)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # 具有对仓库的操作权限
 | 
				
			||||||
 | 
					  def require_operate_above 
 | 
				
			||||||
 | 
					    @project = load_project
 | 
				
			||||||
 | 
					    return render_forbidden if !current_user.admin? && !@project.operator?(current_user)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # 具有仓库的操作权限或者fork仓库的操作权限
 | 
				
			||||||
 | 
					  def require_operate_above_or_fork_project 
 | 
				
			||||||
 | 
					    @project = load_project
 | 
				
			||||||
 | 
					    return render_forbidden if !current_user.admin? && !@project.operator?(current_user) && !(@project.fork_project.present? && @project.fork_project.operator?(current_user))
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # 具有对仓库的访问权限
 | 
				
			||||||
 | 
					  def require_public_and_member_above
 | 
				
			||||||
 | 
					    @project = load_project
 | 
				
			||||||
 | 
					    return render_forbidden if !@project.is_public && !current_user.admin? && !@project.member?(current_user)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					class Api::Pm::IssueLinksController <  Api::Pm::BaseController
 | 
				
			||||||
 | 
					  before_action :load_project
 | 
				
			||||||
 | 
					  before_action :load_issue
 | 
				
			||||||
 | 
					  def index
 | 
				
			||||||
 | 
					    @links = PmLink.where(be_linkable_id: @issue.id,be_linkable_type: 'Issue').or(PmLink.where(linkable_id: @issue.id,linkable_type: 'Issue'))
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def create
 | 
				
			||||||
 | 
					    begin  
 | 
				
			||||||
 | 
					      ActiveRecord::Base.transaction do 
 | 
				
			||||||
 | 
					        params[:link_ids].each do |e|
 | 
				
			||||||
 | 
					          @issue.pm_links.find_or_create_by!(be_linkable_type: 'Issue', be_linkable_id: e) 
 | 
				
			||||||
 | 
					          tag_issue = Issue.find_by_id(e)
 | 
				
			||||||
 | 
					          next unless tag_issue.present?
 | 
				
			||||||
 | 
					          journal = tag_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					          journal.journal_details.create!({property: "tag_link_issue", prop_key: "1", value: @issue.id.to_s})
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        journal = @issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "tag_link_issue", prop_key: "#{params[:link_ids].size}", value: params[:link_ids].join(",")})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    rescue 
 | 
				
			||||||
 | 
					      render_error('创建失败!')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy
 | 
				
			||||||
 | 
					    begin 
 | 
				
			||||||
 | 
					      ActiveRecord::Base.transaction do 
 | 
				
			||||||
 | 
					        @links = PmLink.where(be_linkable_id: @issue.id, be_linkable_type: 'Issue', linkable_id: params[:id], linkable_type: 'Issue').or(PmLink.where(linkable_id: @issue.id, linkable_type: 'Issue', be_linkable_id: params[:id], be_linkable_type: 'Issue'))
 | 
				
			||||||
 | 
					        journal = @issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "tag_link_issue", prop_key: "1", old_value: params[:id].to_s})
 | 
				
			||||||
 | 
					        another_issue = Issue.find_by_id(params[:id])
 | 
				
			||||||
 | 
					        if another_issue.present? 
 | 
				
			||||||
 | 
					          journal = another_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					          journal.journal_details.create!({property: "tag_link_issue", prop_key: "1", old_value: @issue.id.to_s})
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        @link = @links.last
 | 
				
			||||||
 | 
					        @link.destroy!
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    rescue 
 | 
				
			||||||
 | 
					      render_error('删除失败!')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,71 @@
 | 
				
			||||||
 | 
					class Api::Pm::IssueTagsController < Api::Pm::BaseController 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def index 
 | 
				
			||||||
 | 
					    @issue_tags = IssueTag.pm_able
 | 
				
			||||||
 | 
					    if params[:organization_id].present?
 | 
				
			||||||
 | 
					      IssueTag.pm_org_init_data(params[:organization_id]) unless $redis_cache.hget("pm_org_init_issue_tags", params[:organization_id])
 | 
				
			||||||
 | 
					      @issue_tags = @issue_tags.where(organization_id: params[:organization_id]) 
 | 
				
			||||||
 | 
					    end 
 | 
				
			||||||
 | 
					    @issue_tags = @issue_tags.where(pm_project_id: params[:pm_project_id]) if params[:pm_project_id].present? 
 | 
				
			||||||
 | 
					    @issue_tags = @issue_tags.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
 | 
				
			||||||
 | 
					    @issue_tags = @issue_tags.reorder("#{tag_sort_by} #{tag_sort_direction}")
 | 
				
			||||||
 | 
					    @issue_tags = kaminari_paginate(@issue_tags)
 | 
				
			||||||
 | 
					    render "api/v1/issues/issue_tags/index"
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def create 
 | 
				
			||||||
 | 
					    return render_error("请输入正确的OrganizationID") unless Organization.exists?(id: issue_tag_create_params[:organization_id])
 | 
				
			||||||
 | 
					    return render_error("项目标记名称不能为空!") unless issue_tag_create_params[:name].present?
 | 
				
			||||||
 | 
					    @issue_tag = IssueTag.new(issue_tag_create_params.merge!(project_id: 0))
 | 
				
			||||||
 | 
					    if @issue_tag.save!
 | 
				
			||||||
 | 
					      render_ok 
 | 
				
			||||||
 | 
					    else 
 | 
				
			||||||
 | 
					      render_error("创建标记失败!")
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before_action :load_issue_tag, only: [:update, :destroy]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update 
 | 
				
			||||||
 | 
					    @issue_tag.attributes = issue_tag_update_params
 | 
				
			||||||
 | 
					    if @issue_tag.save! 
 | 
				
			||||||
 | 
					      render_ok 
 | 
				
			||||||
 | 
					    else 
 | 
				
			||||||
 | 
					      render_error("更新标记失败!")
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end 
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def destroy 
 | 
				
			||||||
 | 
					    if @issue_tag.destroy! 
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    else 
 | 
				
			||||||
 | 
					      render_error("删除标记失败!")
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private 
 | 
				
			||||||
 | 
					  def tag_sort_by
 | 
				
			||||||
 | 
					    sort_by = params.fetch(:sort_by, "created_at")
 | 
				
			||||||
 | 
					    sort_by = IssueTag.column_names.include?(sort_by) ? sort_by : "created_at"
 | 
				
			||||||
 | 
					    sort_by
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def tag_sort_direction
 | 
				
			||||||
 | 
					    sort_direction = params.fetch(:sort_direction, "desc")&.downcase
 | 
				
			||||||
 | 
					    sort_direction = %w(desc asc).include?(sort_direction) ? sort_direction : "desc"
 | 
				
			||||||
 | 
					    sort_direction
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issue_tag_create_params 
 | 
				
			||||||
 | 
					    params.permit(:name, :description, :color, :pm_project_id, :organization_id)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issue_tag_update_params 
 | 
				
			||||||
 | 
					    params.permit(:name, :description, :color)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def load_issue_tag 
 | 
				
			||||||
 | 
					    @issue_tag = IssueTag.pm_able.find_by_id(params[:id])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,328 @@
 | 
				
			||||||
 | 
					class Api::Pm::IssuesController < Api::Pm::BaseController
 | 
				
			||||||
 | 
					  before_action :require_login, except: [:index]
 | 
				
			||||||
 | 
					  before_action :load_project
 | 
				
			||||||
 | 
					  before_action :load_issue, only: %i[show update destroy link_index link_issues parent_issues]
 | 
				
			||||||
 | 
					  before_action :load_issues, only: %i[batch_update batch_destroy]
 | 
				
			||||||
 | 
					  before_action :check_issue_operate_permission, only: %i[update destroy]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def index
 | 
				
			||||||
 | 
					    @object_result = Api::V1::Issues::ListService.call(@project, query_params, current_user)
 | 
				
			||||||
 | 
					    @total_issues_count = @object_result[:total_issues_count]
 | 
				
			||||||
 | 
					    @opened_issues_count = @object_result[:opened_issues_count]
 | 
				
			||||||
 | 
					    @closed_issues_count = @object_result[:closed_issues_count]
 | 
				
			||||||
 | 
					    @complete_issues_count = @object_result[:complete_issues_count]
 | 
				
			||||||
 | 
					    if params[:only_name].present?
 | 
				
			||||||
 | 
					      @issues = kaminary_select_paginate(
 | 
				
			||||||
 | 
					        @object_result[:data].select(:id, :subject, :project_issues_index, :updated_on, :created_on))
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      @issues = kaminari_paginate(@object_result[:data])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    render 'api/v1/issues/index'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def link_index
 | 
				
			||||||
 | 
					    pm_issue_type = params[:pm_issue_type] || [1, 2, 3]
 | 
				
			||||||
 | 
					    not_join_id = case params[:issue_filter_type]
 | 
				
			||||||
 | 
					                  when 'leaf_issue'
 | 
				
			||||||
 | 
					                    Issue.where(root_id: @issue.id).pluck(:id)
 | 
				
			||||||
 | 
					                  when 'link_issue'
 | 
				
			||||||
 | 
					                    @issue.pm_links.pluck(:be_linkable_id)
 | 
				
			||||||
 | 
					                  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    not_join_id << @issue.id
 | 
				
			||||||
 | 
					    object_issues = Issue.where(
 | 
				
			||||||
 | 
					      pm_project_id: params[:pm_project_id],
 | 
				
			||||||
 | 
					      pm_issue_type: pm_issue_type
 | 
				
			||||||
 | 
					    ).where.not(id: not_join_id).order(updated_on: :desc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    object_issues = object_issues.where(root_id: nil, child_count: 0) if params[:issue_filter_type] == 'leaf_issue'
 | 
				
			||||||
 | 
					    @issues = kaminari_paginate(object_issues)
 | 
				
			||||||
 | 
					    render 'api/v1/issues/index'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def parent_issues
 | 
				
			||||||
 | 
					    @issues = Issue.where(pm_project_id: params[:pm_project_id])
 | 
				
			||||||
 | 
					                   .where.not(id: @issue.id)
 | 
				
			||||||
 | 
					                   .where.not(id: Issue.full_children_issues(@issue).map{|i|i.id}) 
 | 
				
			||||||
 | 
					    @issues = @issues.where(pm_issue_type: params[:pm_issue_type]) if params[:pm_issue_type].present?
 | 
				
			||||||
 | 
					    @issues = @issues.ransack(id_or_project_issues_index_eq: params[:keyword]).result.or(@issues.ransack(subject_or_description_cont: params[:keyword]).result) if params[:keyword].present?
 | 
				
			||||||
 | 
					    @issues = @issues.reorder("#{issue_sort_by} #{issue_sort_direction}")
 | 
				
			||||||
 | 
					    if params[:only_name].present?
 | 
				
			||||||
 | 
					      @issues = kaminary_select_paginate(
 | 
				
			||||||
 | 
					        @issues.select(:id, :subject, :project_issues_index, :updated_on, :created_on))
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      @issues = @issues.includes(:priority, :issue_status, :user, :show_assigners, :show_issue_tags, :version, :comment_journals)
 | 
				
			||||||
 | 
					      @issues = kaminari_paginate(@issues)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def show
 | 
				
			||||||
 | 
					    @issue.associate_attachment_container
 | 
				
			||||||
 | 
					    render 'api/v1/issues/show'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def create
 | 
				
			||||||
 | 
					    @object_result = Api::Pm::Issues::CreateService.call(@project, issue_params, current_user)
 | 
				
			||||||
 | 
					    render 'api/v1/issues/create'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update
 | 
				
			||||||
 | 
					    @object_result = Api::Pm::Issues::UpdateService.call(@project, @issue, issue_params, current_user)
 | 
				
			||||||
 | 
					    render 'api/v1/issues/update'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def batch_update
 | 
				
			||||||
 | 
					    @object_result = Api::Pm::Issues::BatchUpdateService.call(@project, @issues, batch_issue_params, current_user)
 | 
				
			||||||
 | 
					    if @object_result
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      render_error('批量更新疑修失败!')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def batch_destroy
 | 
				
			||||||
 | 
					    return render_ok if params[:ids].is_a?(Array) && params[:ids].blank?
 | 
				
			||||||
 | 
					    @object_result = Api::Pm::Issues::BatchDeleteService.call(@project, @issues, current_user)
 | 
				
			||||||
 | 
					    if @object_result
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      render_error('批量删除疑修失败!')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def priorities
 | 
				
			||||||
 | 
					    @priorities = IssuePriority.order(position: :asc)
 | 
				
			||||||
 | 
					    @priorities = @priorities.ransack(name_cont: params[:keyword]).result if params[:keyword]
 | 
				
			||||||
 | 
					    @priorities = kaminary_select_paginate(@priorities)
 | 
				
			||||||
 | 
					    render "api/v1/issues/issue_priorities/index"
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def tags
 | 
				
			||||||
 | 
					    # IssueTag.pm_init_data(params[:pm_project_id]) unless $redis_cache.hget("pm_project_init_issue_tags", params[:pm_project_id])
 | 
				
			||||||
 | 
					    @issue_tags = IssueTag.where(pm_project_id: params[:pm_project_id]).reorder("#{tag_sort_by} #{tag_sort_direction}")
 | 
				
			||||||
 | 
					    @issue_tags = @issue_tags.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
 | 
				
			||||||
 | 
					    params[:only_name] = true #强制渲染 不走project
 | 
				
			||||||
 | 
					    @issue_tags = kaminary_select_paginate(@issue_tags.select(:id, :name, :color))
 | 
				
			||||||
 | 
					    render "api/v1/issues/issue_tags/index"
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def statues
 | 
				
			||||||
 | 
					    @statues = IssueStatus.order("position asc")
 | 
				
			||||||
 | 
					    @statues = @statues.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
 | 
				
			||||||
 | 
					    @statues = kaminary_select_paginate(@statues)
 | 
				
			||||||
 | 
					    render "api/v1/issues/statues/index"
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy
 | 
				
			||||||
 | 
					    @object_result = Api::Pm::Issues::DeleteService.call(@project, @issue, current_user)
 | 
				
			||||||
 | 
					    if @object_result
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      render_error('删除疑修失败!')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def export 
 | 
				
			||||||
 | 
					    return render_error('请输入正确的项目ID.') if params[:pm_project_id].blank?
 | 
				
			||||||
 | 
					    Axlsx::Package.new do |p|
 | 
				
			||||||
 | 
					      [['requirement', 1], ['task', 2], ['bug', 3]].each do |type|
 | 
				
			||||||
 | 
					        p.workbook.add_worksheet(:name => type[0]) do |sheet|
 | 
				
			||||||
 | 
					          @issues = Issue.where(pm_project_id: params[:pm_project_id], pm_issue_type: type[1])
 | 
				
			||||||
 | 
					          sheet.add_row ["ID", "标题*", "正文", "创建者*", "创建时间", "修改者", "更新时间", "状态", "负责人", "优先级", "标记", "开始时间","结束时间", "预估工时"]
 | 
				
			||||||
 | 
					          @issues.each do |issue| 
 | 
				
			||||||
 | 
					            sheet.add_row [issue.id, issue.subject, issue.description, issue.user.try(:login), issue.created_on.strftime("%Y-%m-%d %H:%M:%S"), issue.changer.try(:login), issue.updated_on.strftime("%Y-%m-%d %H:%M:%S"), issue.status_id, issue.assigners.pluck(:login).join(","), issue.priority_id, issue.issue_tags.pluck(:name, :color).join(","), issue.start_date.present? ? issue.start_date.strftime("%Y-%m-%d") : "", issue.due_date.present? ? issue.due_date.strftime("%Y-%m-%d") : "", issue.time_scale]
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      p.workbook.add_worksheet(:name => 'leaf_relations') do |sheet|
 | 
				
			||||||
 | 
					        leaf_issues = Issue.where(pm_project_id: params[:pm_project_id]).where.not(root_id: nil)
 | 
				
			||||||
 | 
					        sheet.add_row ["ID", "父工作项ID"]
 | 
				
			||||||
 | 
					        leaf_issues.each do |issue| 
 | 
				
			||||||
 | 
					          sheet.add_row [issue.id, issue.root_id]
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      p.workbook.add_worksheet(:name => 'link_relations') do |sheet| 
 | 
				
			||||||
 | 
					        # links = PmLink.joins(:linkable_issue).where(issues: {pm_project_id: params[:pm_project_id]})
 | 
				
			||||||
 | 
					        links = PmLink.find_by_sql("SELECT `pm_links`.* FROM `pm_links` INNER JOIN `issues` ON `issues`.`id` = `pm_links`.`linkable_id` AND `pm_links`.`linkable_type` = 'Issue' WHERE `issues`.`pm_project_id` = #{params[pm_project_id]}")
 | 
				
			||||||
 | 
					        sheet.add_row ["ID", "被关联工作项ID"]
 | 
				
			||||||
 | 
					        links.each do |link|
 | 
				
			||||||
 | 
					          sheet.add_row [link.linkable_id, link.be_linkable_id]
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      p.serialize('public/导出工作项.xlsx')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    send_file('public/导出工作项.xlsx', :type => 'application/octet-stream;charset=utf-8')
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def import 
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
 | 
					      return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile)
 | 
				
			||||||
 | 
					      return render_error('请输入正确的项目ID.') if params[:pm_project_id].blank?
 | 
				
			||||||
 | 
					      return render_error('请输入正确的组织ID.') if params[:organization_id].blank?
 | 
				
			||||||
 | 
					      ActiveRecord::Base.transaction do 
 | 
				
			||||||
 | 
					        types = {requirement: 1, task: 2, bug: 3}
 | 
				
			||||||
 | 
					        doc = SimpleXlsxReader.open(params[:file].tempfile)
 | 
				
			||||||
 | 
					        doc.sheets.each do |sheet|
 | 
				
			||||||
 | 
					          case sheet.name
 | 
				
			||||||
 | 
					          when 'requirement', 'task', 'bug'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            type = types["#{sheet.name}".to_sym]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            sheet.rows.each.with_index do |row, index|
 | 
				
			||||||
 | 
					              next if index == 0
 | 
				
			||||||
 | 
					              issue = Issue.new(issue_classify: "issue", project_id: 0, pm_project_id: params[:pm_project_id], pm_issue_type: type, tracker_id: Tracker.first.id)
 | 
				
			||||||
 | 
					              issue.fake_id = row[0]
 | 
				
			||||||
 | 
					              issue.subject = row[1]
 | 
				
			||||||
 | 
					              issue.description = row[2]
 | 
				
			||||||
 | 
					              author = User.find_by(login: row[3]) 
 | 
				
			||||||
 | 
					              issue.user = author 
 | 
				
			||||||
 | 
					              issue.created_on = row[4]
 | 
				
			||||||
 | 
					              changer = User.find_by(login: row[5])
 | 
				
			||||||
 | 
					              issue.changer = changer 
 | 
				
			||||||
 | 
					              issue.updated_on = row[6]
 | 
				
			||||||
 | 
					              issue.status_id = row[7].to_i
 | 
				
			||||||
 | 
					              if row[8].present?
 | 
				
			||||||
 | 
					                row[8].split(',').each do |a| 
 | 
				
			||||||
 | 
					                  u = User.find_by(login: a)
 | 
				
			||||||
 | 
					                  next unless u.present? 
 | 
				
			||||||
 | 
					                  issue.assigners << u
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					              end
 | 
				
			||||||
 | 
					              issue.priority_id = row[9]
 | 
				
			||||||
 | 
					              if row[10].present? 
 | 
				
			||||||
 | 
					                row[10].split(',').each_slice(2).to_a.each do |t|
 | 
				
			||||||
 | 
					                  tag = IssueTag.find_by(project_id: 0, organization_id: params[:organization_id], name: t[0])
 | 
				
			||||||
 | 
					                  if tag.present? 
 | 
				
			||||||
 | 
					                    issue.issue_tags << tag
 | 
				
			||||||
 | 
					                  else
 | 
				
			||||||
 | 
					                    tag = IssueTag.create(project_id: 0,organization_id: params[:organization_id], name: t[0], color: t[1])
 | 
				
			||||||
 | 
					                    issue.issue_tags << tag
 | 
				
			||||||
 | 
					                  end
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					              end
 | 
				
			||||||
 | 
					              issue.start_date = row[11]
 | 
				
			||||||
 | 
					              issue.due_date = row[12]
 | 
				
			||||||
 | 
					              issue.time_scale = row[13]
 | 
				
			||||||
 | 
					              issue.save!
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          when 'leaf_relations'
 | 
				
			||||||
 | 
					            sheet.rows.each.with_index do |row, index|
 | 
				
			||||||
 | 
					              next if index == 0
 | 
				
			||||||
 | 
					              children_issue = Issue.where(fake_id: row[0]).last
 | 
				
			||||||
 | 
					              parent_issue = Issue.where(fake_id: row[1]).last
 | 
				
			||||||
 | 
					              next if children_issue.blank? || parent_issue.blank? 
 | 
				
			||||||
 | 
					              children_issue.root_id = parent_issue.id
 | 
				
			||||||
 | 
					              children_issue.save(touch: false)
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          when 'link_relations'
 | 
				
			||||||
 | 
					            sheet.rows.each.with_index do |row, index|
 | 
				
			||||||
 | 
					              next if index == 0
 | 
				
			||||||
 | 
					              link_issue = Issue.where(fake_id: row[0]).last 
 | 
				
			||||||
 | 
					              be_link_issue = Issue.where(fake_id: row[1]).last
 | 
				
			||||||
 | 
					              next if link_issue.blank? || be_link_issue.blank? 
 | 
				
			||||||
 | 
					              PmLink.create!(linkable_type: 'Issue', linkable_id: link_issue.id, be_linkable_type: 'Issue', be_linkable_id: be_link_issue.id)
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            return render_error('导入失败,请上传正确格式的excel文件')
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    rescue 
 | 
				
			||||||
 | 
					      return render_error('导入失败,请上传正确格式的excel文件')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					  def check_issue_operate_permission
 | 
				
			||||||
 | 
					    return if params[:project_id].to_i.zero?
 | 
				
			||||||
 | 
					    render_forbidden('您没有操作权限!') unless @project.member?(current_user) || current_user.admin? || @issue.user == current_user
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def load_issue
 | 
				
			||||||
 | 
					    return render_parameter_missing if params[:pm_project_id].blank?
 | 
				
			||||||
 | 
					    @issue = Issue.issue_issue.where(pm_project_id: params[:pm_project_id]).find_by_id(params[:id])
 | 
				
			||||||
 | 
					    render_not_found('疑修不存在!') if @issue.blank?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def load_issues
 | 
				
			||||||
 | 
					    return render_error('请输入正确的ID数组!') unless params[:ids].is_a?(Array)
 | 
				
			||||||
 | 
					    params[:ids].each do |id|
 | 
				
			||||||
 | 
					      @issue = Issue.find_by(id: id, pm_project_id: params[:pm_project_id])
 | 
				
			||||||
 | 
					      return render_not_found("ID为#{id}的疑修不存在!") if @issue.blank?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    if params[:ids].blank? 
 | 
				
			||||||
 | 
					      @issues = Issue.where(pm_project_id: params[:pm_project_id])
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      @issues = Issue.where(id: params[:ids], pm_project_id: params[:pm_project_id])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def query_params
 | 
				
			||||||
 | 
					    params.permit(
 | 
				
			||||||
 | 
					      :only_name,
 | 
				
			||||||
 | 
					      :category,
 | 
				
			||||||
 | 
					      :participant_category,
 | 
				
			||||||
 | 
					      :keyword, :author_id,
 | 
				
			||||||
 | 
					      :milestone_id, :assigner_id,
 | 
				
			||||||
 | 
					      :status_id, :priority_id,
 | 
				
			||||||
 | 
					      :begin_date, :end_date,
 | 
				
			||||||
 | 
					      :update_begin_date, :update_end_date,
 | 
				
			||||||
 | 
					      :sort_by, :sort_direction, :root_id,
 | 
				
			||||||
 | 
					      :issue_tag_ids, :pm_project_id, :pm_sprint_id, :pm_issue_type, :pm_project_ids,
 | 
				
			||||||
 | 
					      :status_ids, :ids, :exclude_ids, :pm_issue_types, :participator_id
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issue_params
 | 
				
			||||||
 | 
					    params.permit(
 | 
				
			||||||
 | 
					      :status_id, :priority_id, :milestone_id,
 | 
				
			||||||
 | 
					      :branch_name, :start_date, :due_date, :time_scale,
 | 
				
			||||||
 | 
					      :subject, :description, :blockchain_token_num, :root_subject,
 | 
				
			||||||
 | 
					      :pm_project_id, :pm_sprint_id, :pm_issue_type, :root_id, :link_able_id, :project_id,
 | 
				
			||||||
 | 
					      issue_tag_ids: [],
 | 
				
			||||||
 | 
					      assigner_ids: [],
 | 
				
			||||||
 | 
					      attachment_ids: [],
 | 
				
			||||||
 | 
					      receivers_login: []
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def batch_issue_params
 | 
				
			||||||
 | 
					    params.permit(
 | 
				
			||||||
 | 
					      :status_id, :priority_id, :milestone_id, :pm_sprint_id, :due_date, :pm_issue_type, :root_id, :target_pm_project_id, :project_id,
 | 
				
			||||||
 | 
					      :issue_tag_ids => [],
 | 
				
			||||||
 | 
					      :assigner_ids => [] )
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issue_sort_by 
 | 
				
			||||||
 | 
					    sort_by = params.fetch(:sort_by, "updated_on")
 | 
				
			||||||
 | 
					    sort_by = Issue.column_names.include?(sort_by) ? sort_by : "updated_on"
 | 
				
			||||||
 | 
					    sort_by
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issue_sort_direction 
 | 
				
			||||||
 | 
					    sort_direction = params.fetch(:sort_direction, "desc").downcase
 | 
				
			||||||
 | 
					    sort_direction = %w(desc asc).include?(sort_direction) ? sort_direction : "desc"
 | 
				
			||||||
 | 
					    sort_direction
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def tag_sort_by
 | 
				
			||||||
 | 
					    sort_by = params.fetch(:sort_by, "created_at")
 | 
				
			||||||
 | 
					    sort_by = IssueTag.column_names.include?(sort_by) ? sort_by : "created_at"
 | 
				
			||||||
 | 
					    sort_by
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def tag_sort_direction
 | 
				
			||||||
 | 
					    sort_direction = params.fetch(:sort_direction, "desc").downcase
 | 
				
			||||||
 | 
					    sort_direction = %w(desc asc).include?(sort_direction) ? sort_direction : "desc"
 | 
				
			||||||
 | 
					    sort_direction
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					class Api::Pm::JournalsController < Api::Pm::BaseController
 | 
				
			||||||
 | 
					  before_action :require_login, except: [:index, :children_journals]
 | 
				
			||||||
 | 
					  before_action :load_project
 | 
				
			||||||
 | 
					  before_action :load_issue
 | 
				
			||||||
 | 
					  before_action :load_journal, only: [:children_journals, :update, :destroy]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def index 
 | 
				
			||||||
 | 
					    @object_result = Api::V1::Issues::Journals::ListService.call(@issue, query_params, current_user)
 | 
				
			||||||
 | 
					    @total_journals_count = @object_result[:total_journals_count]
 | 
				
			||||||
 | 
					    @total_operate_journals_count = @object_result[:total_operate_journals_count]
 | 
				
			||||||
 | 
					    @total_comment_journals_count = @object_result[:total_comment_journals_count]
 | 
				
			||||||
 | 
					    @journals = kaminary_select_paginate(@object_result[:data])
 | 
				
			||||||
 | 
					    render 'api/pm/issues/journals/index'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def create 
 | 
				
			||||||
 | 
					    @object_result = Api::V1::Issues::Journals::CreateService.call(@issue, journal_params, current_user)
 | 
				
			||||||
 | 
					    render 'api/v1/issues/journals/create'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def children_journals 
 | 
				
			||||||
 | 
					    @object_results = Api::V1::Issues::Journals::ChildrenListService.call(@issue, @journal, query_params, current_user)
 | 
				
			||||||
 | 
					    @journals = kaminari_paginate(@object_results)
 | 
				
			||||||
 | 
					    render 'api/v1/issues/journals/children_journals'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update
 | 
				
			||||||
 | 
					    @object_result = Api::V1::Issues::Journals::UpdateService.call(@issue, @journal, journal_params, current_user)
 | 
				
			||||||
 | 
					    render 'api/v1/issues/journals/update'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy
 | 
				
			||||||
 | 
					    TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueComment', @issue&.id, current_user.id, @journal.id, 'deleted', JSON.parse(@journal.to_builder.target!))
 | 
				
			||||||
 | 
					    if @journal.destroy!
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      render_error('删除评论失败!')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def query_params
 | 
				
			||||||
 | 
					    params.permit(:category, :keyword, :sort_by, :sort_direction)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def journal_params 
 | 
				
			||||||
 | 
					    params.permit(:notes, :parent_id, :reply_id, :attachment_ids => [], :receivers_login => [])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def load_issue
 | 
				
			||||||
 | 
					    @issue = Issue.issue_issue.where(pm_project_id: params[:pm_project_id]).find_by_id(params[:issue_id])
 | 
				
			||||||
 | 
					    render_not_found('疑修不存在!') if @issue.blank?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def load_journal
 | 
				
			||||||
 | 
					    @journal = Journal.find_by_id(params[:id])
 | 
				
			||||||
 | 
					    render_not_found('评论不存在!') unless @journal.present?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,146 @@
 | 
				
			||||||
 | 
					class Api::Pm::ProjectsController < Api::Pm::BaseController
 | 
				
			||||||
 | 
					  before_action :require_login, except: [:convert]
 | 
				
			||||||
 | 
					  before_action :load_project, only: [:convert]
 | 
				
			||||||
 | 
					  def convert
 | 
				
			||||||
 | 
					    data = {
 | 
				
			||||||
 | 
					      owner: @project.owner.try(:login),
 | 
				
			||||||
 | 
					      identifier: @project.identifier,
 | 
				
			||||||
 | 
					      name: @project.name
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    render_ok(data: data)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issues_count
 | 
				
			||||||
 | 
					    return tip_exception '参数错误' unless params[:pm_project_id].present?
 | 
				
			||||||
 | 
					    @issues = Issue.where(pm_project_id: params[:pm_project_id])
 | 
				
			||||||
 | 
					    case params[:category].to_s
 | 
				
			||||||
 | 
					    when 'closed'
 | 
				
			||||||
 | 
					      @issues = @issues.closed
 | 
				
			||||||
 | 
					    when 'opened'
 | 
				
			||||||
 | 
					      @issues = @issues.opened
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    @participant_category_count = {}
 | 
				
			||||||
 | 
					    if params[:participant_category].to_s == "authoredme" or params[:participant_category].to_s == "assignedme"
 | 
				
			||||||
 | 
					      issues_category = @issues.joins(:issue_participants).where(pm_issue_type: [1, 2, 3]).where(issue_participants: {participant_type: %w[authored assigned atme], participant_id: current_user&.id})
 | 
				
			||||||
 | 
					      @participant_category_count = issues_category.group(:pm_project_id, "issue_participants.participant_type").count
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    case params[:participant_category].to_s
 | 
				
			||||||
 | 
					    when 'aboutme' # 关于我的
 | 
				
			||||||
 | 
					      @issues = @issues.joins(:issue_participants).where(issue_participants: {participant_type: %w[authored assigned atme], participant_id: current_user&.id})
 | 
				
			||||||
 | 
					    when 'authoredme' # 我创建的
 | 
				
			||||||
 | 
					      @issues = @issues.joins(:issue_participants).where(issue_participants: {participant_type: 'authored', participant_id: current_user&.id})
 | 
				
			||||||
 | 
					    when 'assignedme' # 我负责的
 | 
				
			||||||
 | 
					      @issues = @issues.joins(:issue_participants).where(issue_participants: {participant_type: 'assigned', participant_id: current_user&.id})
 | 
				
			||||||
 | 
					    when 'atme' # @我的
 | 
				
			||||||
 | 
					      @issues = @issues.joins(:issue_participants).where(issue_participants: {participant_type: 'atme', participant_id: current_user&.id})
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    data = {}
 | 
				
			||||||
 | 
					    @issues_count = @issues.group(:pm_project_id).count
 | 
				
			||||||
 | 
					    # requirement 1 task 2 bug 3
 | 
				
			||||||
 | 
					    @issues_type_count = @issues.group(:pm_project_id, :pm_issue_type).count
 | 
				
			||||||
 | 
					    params[:pm_project_id].map(&:to_i).map do |project_id|
 | 
				
			||||||
 | 
					      data[project_id] = {
 | 
				
			||||||
 | 
					        total: @issues_count[project_id] || 0,
 | 
				
			||||||
 | 
					        requirement: @issues_type_count[[project_id, 1]] || 0,
 | 
				
			||||||
 | 
					        task: @issues_type_count[[project_id, 2]] || 0,
 | 
				
			||||||
 | 
					        bug: @issues_type_count[[project_id, 3]] || 0,
 | 
				
			||||||
 | 
					        authoredme: @participant_category_count[[project_id, 0]] || 0,
 | 
				
			||||||
 | 
					        assignedme: @participant_category_count[[project_id, 1]] || 0,
 | 
				
			||||||
 | 
					        atme: @participant_category_count[[project_id, 4]] || 0,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    render_ok(data: data)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def statistics
 | 
				
			||||||
 | 
					    return tip_exception '参数错误' if params[:pm_project_id].blank?
 | 
				
			||||||
 | 
					    @issues = Issue.where(pm_project_id: params[:pm_project_id], pm_issue_type:[1, 2, 3])
 | 
				
			||||||
 | 
					    type_count_data =  @issues.group(:pm_issue_type).count
 | 
				
			||||||
 | 
					    type_status = @issues.group(:pm_issue_type,:status_id).count
 | 
				
			||||||
 | 
					    type_status_data = {}
 | 
				
			||||||
 | 
					    IssueStatus.all.map do |e|
 | 
				
			||||||
 | 
					      # next if e.id == 5
 | 
				
			||||||
 | 
					      [1,2,3].map{ |type|
 | 
				
			||||||
 | 
					        type_status_data[type] = {} if type_status_data[type].nil?
 | 
				
			||||||
 | 
					        if type_status[[type,e.id]].nil?
 | 
				
			||||||
 | 
					          type_status_data[type][e.id] = 0
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          type_status_data[type][e.id] = type_status[[type,e.id]]
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    open_data = {
 | 
				
			||||||
 | 
					      "1": type_status_data[1][1].to_i + type_status_data[1][2].to_i,
 | 
				
			||||||
 | 
					      "2": type_status_data[2][1].to_i + type_status_data[2][2].to_i,
 | 
				
			||||||
 | 
					      "3": type_status_data[3][1].to_i + type_status_data[3][2].to_i,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if type_count_data.keys.size < 3
 | 
				
			||||||
 | 
					      nedd_add = [1,2,3] - type_count_data.keys
 | 
				
			||||||
 | 
					      nedd_add.map{ |e| 
 | 
				
			||||||
 | 
					        type_count_data[e] = 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    data = {
 | 
				
			||||||
 | 
					      pie_chart: type_count_data,
 | 
				
			||||||
 | 
					      bar_chart: type_status_data,
 | 
				
			||||||
 | 
					      open_data: open_data
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    render_ok(data: data)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def polyline
 | 
				
			||||||
 | 
					    return tip_exception '参数错误' if params[:pm_project_id].blank?
 | 
				
			||||||
 | 
					    time_line = (Time.current.beginning_of_day - 6.day) .. Time.current
 | 
				
			||||||
 | 
					    @create_issues = Issue.where(pm_project_id: params[:pm_project_id],created_on: time_line)
 | 
				
			||||||
 | 
					    @due_issues = Issue.where(pm_project_id: params[:pm_project_id],status_id:[3,5],due_date: time_line)
 | 
				
			||||||
 | 
					    @create_issues_count = @create_issues.group(:pm_issue_type,"DATE(created_on)").count
 | 
				
			||||||
 | 
					    @due_issues_count = @due_issues.group(:pm_issue_type,"DATE(due_date)").count
 | 
				
			||||||
 | 
					    data = {
 | 
				
			||||||
 | 
					      create_issues: {},
 | 
				
			||||||
 | 
					      due_issues: {}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    7.times do |time|
 | 
				
			||||||
 | 
					      current_time = Date.current - time.day
 | 
				
			||||||
 | 
					      if @create_issues_count.present?
 | 
				
			||||||
 | 
					        data[:create_issues][current_time] = {
 | 
				
			||||||
 | 
					          "1": @create_issues_count[[1,current_time]] || 0,
 | 
				
			||||||
 | 
					          "2": @create_issues_count[[2,current_time]] || 0,
 | 
				
			||||||
 | 
					          "3": @create_issues_count[[3,current_time]] || 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        data[:create_issues][current_time] = {
 | 
				
			||||||
 | 
					          "1": 0,
 | 
				
			||||||
 | 
					          "2": 0,
 | 
				
			||||||
 | 
					          "3": 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      if @due_issues_count.present?
 | 
				
			||||||
 | 
					        data[:due_issues][current_time] = {
 | 
				
			||||||
 | 
					          "1": @due_issues_count[[1,current_time]] || 0,
 | 
				
			||||||
 | 
					          "2": @due_issues_count[[2,current_time]] || 0,
 | 
				
			||||||
 | 
					          "3": @due_issues_count[[3,current_time]] || 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        data[:due_issues][current_time] = {
 | 
				
			||||||
 | 
					          "1": 0,
 | 
				
			||||||
 | 
					          "2": 0,
 | 
				
			||||||
 | 
					          "3": 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    render_ok(data: data)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def bind_project
 | 
				
			||||||
 | 
					    return  render_forbidden('您没有操作权限!') unless @project.member?(current_user) || current_user.admin?
 | 
				
			||||||
 | 
					    Issue.where(pm_project_id: params[:pm_project_id], user_id: current_user).update_all(project_id: params[:project_id])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					  def load_project
 | 
				
			||||||
 | 
					    @project = Project.joins(:owner).find params[:project_id]
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,108 @@
 | 
				
			||||||
 | 
					class Api::Pm::SprintIssuesController < Api::Pm::BaseController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before_action :require_login, except: [:index]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def index
 | 
				
			||||||
 | 
					    @issues = Api::Pm::SprintIssues::ListService.call(query_params, current_user)
 | 
				
			||||||
 | 
					    @issues = kaminari_paginate(@issues)
 | 
				
			||||||
 | 
					    render 'api/v1/issues/index'
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def burndown_charts
 | 
				
			||||||
 | 
					    return tip_exception '参数错误' if params[:pm_sprint_id].blank? || params[:start_time].blank? || params[:end_time].blank?
 | 
				
			||||||
 | 
					    @issues = Issue.where(pm_sprint_id: params[:pm_sprint_id])
 | 
				
			||||||
 | 
					    start_time = Date.parse params[:start_time]
 | 
				
			||||||
 | 
					    end_time = Date.parse params[:end_time]
 | 
				
			||||||
 | 
					    time_count = (end_time - start_time).to_i + 1 # 计算间隔时间 加上最后一天
 | 
				
			||||||
 | 
					    data = []
 | 
				
			||||||
 | 
					    curren_issues = @issues.group(:status_id, :due_date).count
 | 
				
			||||||
 | 
					    total_count = @issues.count
 | 
				
			||||||
 | 
					    cardinality = BigDecimal.new(total_count) / BigDecimal.new(time_count)
 | 
				
			||||||
 | 
					    time_count.times do |x|
 | 
				
			||||||
 | 
					      e_time = start_time + x
 | 
				
			||||||
 | 
					      completed = curren_issues[[5, e_time]].to_i + curren_issues[[3, e_time]].to_i - @issues.where(pm_issue_type: 3, status_id: 3).size
 | 
				
			||||||
 | 
					      total_count = total_count - completed
 | 
				
			||||||
 | 
					      data << { time: e_time, undone: total_count, completed: completed, base_number: (cardinality * (time_count - x - 1)).to_f.round(2) }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    render_ok(data: data)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def statistics
 | 
				
			||||||
 | 
					    pm_sprint_ids = params[:pm_sprint_ids].split(",") rescue []
 | 
				
			||||||
 | 
					    return tip_exception '参数错误' if pm_sprint_ids.blank?
 | 
				
			||||||
 | 
					    @issues = Issue.where(pm_sprint_id: pm_sprint_ids)
 | 
				
			||||||
 | 
					    data = {}
 | 
				
			||||||
 | 
					    # requirement 1 task 2 bug 3
 | 
				
			||||||
 | 
					    @issues_count = @issues.group(:pm_sprint_id).count
 | 
				
			||||||
 | 
					    @issues_type_count = @issues.group(:pm_sprint_id, :status_id).count
 | 
				
			||||||
 | 
					    @issues_pm_type_count = @issues.group(:pm_sprint_id, :pm_issue_type).count
 | 
				
			||||||
 | 
					    @issues_hour_count = @issues.group(:pm_sprint_id).sum(:time_scale)
 | 
				
			||||||
 | 
					    @issues_hour_type_count = @issues.group(:pm_sprint_id, :status_id).sum(:time_scale)
 | 
				
			||||||
 | 
					    @issues_hour_pm_type_count = @issues.group(:pm_sprint_id, :pm_issue_type).sum(:time_scale)
 | 
				
			||||||
 | 
					    @issues_status_pm_type_count = @issues.group(:pm_sprint_id, :pm_issue_type, :status_id).count
 | 
				
			||||||
 | 
					    pm_sprint_ids.map(&:to_i).map do |sprint_id|
 | 
				
			||||||
 | 
					      # count_closed 工作项已完成/已关闭数量,需排除已修复的缺陷数量
 | 
				
			||||||
 | 
					      count_closed = @issues_type_count[[sprint_id, 5]].to_i + @issues_type_count[[sprint_id, 3]].to_i - @issues.where(pm_sprint_id: sprint_id, pm_issue_type: 3, status_id: 3).size
 | 
				
			||||||
 | 
					      # hour_closed 已完成/已关闭 预估工时之和,需排除已修复的缺陷预估工时
 | 
				
			||||||
 | 
					      hour_closed = @issues_hour_type_count[[sprint_id, 5]].to_f + @issues_hour_type_count[[sprint_id, 3]].to_f - @issues.where(pm_sprint_id: sprint_id, pm_issue_type: 3, status_id: 3).sum(:time_scale).to_f
 | 
				
			||||||
 | 
					      data[sprint_id] = {
 | 
				
			||||||
 | 
					        count_total: @issues_count[sprint_id] || 0,
 | 
				
			||||||
 | 
					        count_closed: count_closed || 0,
 | 
				
			||||||
 | 
					        hour_total: @issues_hour_count[sprint_id].to_f || 0,
 | 
				
			||||||
 | 
					        hour_closed: hour_closed || 0,
 | 
				
			||||||
 | 
					        requirement: @issues_pm_type_count[[sprint_id, 1]] || 0,
 | 
				
			||||||
 | 
					        task: @issues_pm_type_count[[sprint_id, 2]] || 0,
 | 
				
			||||||
 | 
					        bug: @issues_pm_type_count[[sprint_id, 3]] || 0,
 | 
				
			||||||
 | 
					        requirement_hour: @issues_hour_pm_type_count[[sprint_id, 1]].to_i || 0,
 | 
				
			||||||
 | 
					        task_hour: @issues_hour_pm_type_count[[sprint_id, 2]].to_i || 0,
 | 
				
			||||||
 | 
					        bug_hour: @issues_hour_pm_type_count[[sprint_id, 3]].to_i || 0,
 | 
				
			||||||
 | 
					        requirement_open: (@issues_status_pm_type_count[[sprint_id, 1, 1]].to_i + @issues_status_pm_type_count[[sprint_id, 1, 2]].to_i) || 0,
 | 
				
			||||||
 | 
					        task_open: @issues_status_pm_type_count[[sprint_id, 2, 1]].to_i + @issues_status_pm_type_count[[sprint_id, 2, 2]].to_i || 0,
 | 
				
			||||||
 | 
					        bug_open: @issues_status_pm_type_count[[sprint_id, 3, 1]].to_i + @issues_status_pm_type_count[[sprint_id, 3, 2]].to_i || 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    render_ok(data: data)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before_action :load_uncomplete_issues, only: [:complete]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def complete
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
 | 
					      case complete_params[:complete_type].to_i
 | 
				
			||||||
 | 
					      when 1
 | 
				
			||||||
 | 
					        @issues.update_all(status_id: 5)
 | 
				
			||||||
 | 
					      when 2
 | 
				
			||||||
 | 
					        @issues.update_all(pm_sprint_id: 0)
 | 
				
			||||||
 | 
					      when 3
 | 
				
			||||||
 | 
					        @issues.update_all(pm_sprint_id: complete_params[:target_pm_project_sprint_id])
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      render_ok
 | 
				
			||||||
 | 
					    rescue => e
 | 
				
			||||||
 | 
					      render_error(e.message)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def load_uncomplete_issues
 | 
				
			||||||
 | 
					    @issues = Issue.where(pm_sprint_id: complete_params[:pm_project_sprint_id]).where.not(status_id: 5)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def complete_params
 | 
				
			||||||
 | 
					    params.permit(:pm_project_sprint_id, :complete_type, :target_pm_project_sprint_id)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def query_params
 | 
				
			||||||
 | 
					    params.permit(
 | 
				
			||||||
 | 
					      :category,
 | 
				
			||||||
 | 
					      :pm_project_id,
 | 
				
			||||||
 | 
					      :pm_issue_type, # 需求1  任务2 缺陷3
 | 
				
			||||||
 | 
					      :assigner_id,
 | 
				
			||||||
 | 
					      :priority_id,
 | 
				
			||||||
 | 
					      :status_id,
 | 
				
			||||||
 | 
					      :keyword, :status_ids, :pm_issue_types,
 | 
				
			||||||
 | 
					      :sort_by, :sort_direction
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -62,7 +62,7 @@ class Api::V1::BaseController < ApplicationController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # 具有对仓库的访问权限
 | 
					  # 具有对仓库的访问权限
 | 
				
			||||||
  def require_public_and_member_above
 | 
					  def require_public_and_member_above
 | 
				
			||||||
    @project = load_project 
 | 
					    @project = load_project
 | 
				
			||||||
    return render_forbidden if !@project.is_public && !current_user.admin? && !@project.member?(current_user)
 | 
					    return render_forbidden if !@project.is_public && !current_user.admin? && !@project.member?(current_user)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -7,12 +7,4 @@ class Api::V1::Issues::IssuePrioritiesController < Api::V1::BaseController
 | 
				
			||||||
    @priorities = @priorities.ransack(name_cont: params[:keyword]).result if params[:keyword]
 | 
					    @priorities = @priorities.ransack(name_cont: params[:keyword]).result if params[:keyword]
 | 
				
			||||||
    @priorities = kaminary_select_paginate(@priorities)
 | 
					    @priorities = kaminary_select_paginate(@priorities)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def pm_index 
 | 
					 | 
				
			||||||
    @priorities = IssuePriority.order(position: :asc)
 | 
					 | 
				
			||||||
    @priorities = @priorities.ransack(name_cont: params[:keyword]).result if params[:keyword]
 | 
					 | 
				
			||||||
    @priorities = kaminary_select_paginate(@priorities)
 | 
					 | 
				
			||||||
    render "index"
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -13,12 +13,7 @@ class Api::V1::Issues::IssueTagsController < Api::V1::BaseController
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def pm_index
 | 
					  def create
 | 
				
			||||||
    @issue_tags = IssueTag.init_mp_issues_tags
 | 
					 | 
				
			||||||
    render_ok(@issue_tags)
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def create 
 | 
					 | 
				
			||||||
    @issue_tag = @project.issue_tags.new(issue_tag_params)
 | 
					    @issue_tag = @project.issue_tags.new(issue_tag_params)
 | 
				
			||||||
    if @issue_tag.save!
 | 
					    if @issue_tag.save!
 | 
				
			||||||
      render_ok 
 | 
					      render_ok 
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,11 +8,4 @@ class Api::V1::Issues::StatuesController < Api::V1::BaseController
 | 
				
			||||||
    @statues = @statues.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
 | 
					    @statues = @statues.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
 | 
				
			||||||
    @statues = kaminary_select_paginate(@statues)
 | 
					    @statues = kaminary_select_paginate(@statues)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def pm_index
 | 
					 | 
				
			||||||
    @statues = IssueStatus.order("position asc")
 | 
					 | 
				
			||||||
    @statues = @statues.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
 | 
					 | 
				
			||||||
    @statues = kaminary_select_paginate(@statues)
 | 
					 | 
				
			||||||
    render "index"
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					class Api::V1::PmIssuesController < ApplicationController
 | 
				
			||||||
 | 
					    before_action :require_login, except: [:index, :show]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def index
 | 
				
			||||||
 | 
					        project = Project.find_by_id(params[:project_id]) || Project.new( id: 0, user_id: 0, name:"pm_mm", identifier:"pm_mm" )
 | 
				
			||||||
 | 
					        object_result = Api::V1::Issues::ListService.call(@project, query_params, current_user)
 | 
				
			||||||
 | 
					        @total_issues_count = @object_result[:total_issues_count]
 | 
				
			||||||
 | 
					        @opened_issues_count = @object_result[:opened_issues_count]
 | 
				
			||||||
 | 
					        @closed_issues_count = @object_result[:closed_issues_count]
 | 
				
			||||||
 | 
					        if params[:only_name].present? 
 | 
				
			||||||
 | 
					        @issues = kaminary_select_paginate(@object_result[:data].select(:id, :subject, :project_issues_index, :updated_on, :created_on))
 | 
				
			||||||
 | 
					        else 
 | 
				
			||||||
 | 
					        @issues = kaminari_paginate(@object_result[:data])
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create 
 | 
				
			||||||
 | 
					        project = Project.find_by_id(params[:project_id]) || Project.new( id: 0, user_id: 0, name:"pm_mm", identifier:"pm_mm" )
 | 
				
			||||||
 | 
					        @object_result = Api::V1::Issues::CreateService.call(project, issue_params, current_user)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private
 | 
				
			||||||
 | 
					    def issue_params 
 | 
				
			||||||
 | 
					        params.permit(
 | 
				
			||||||
 | 
					            :status_id, :priority_id, :milestone_id,
 | 
				
			||||||
 | 
					            :branch_name, :start_date, :due_date, 
 | 
				
			||||||
 | 
					            :subject, :description, :blockchain_token_num,
 | 
				
			||||||
 | 
					            :pm_project_id, :pm_sprint_id,
 | 
				
			||||||
 | 
					            :issue_tag_ids => [], 
 | 
				
			||||||
 | 
					            :assigner_ids => [], 
 | 
				
			||||||
 | 
					            :attachment_ids => [],
 | 
				
			||||||
 | 
					            :receivers_login => []
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,463 @@
 | 
				
			||||||
 | 
					class Api::V1::Projects::PipelinesController < Api::V1::BaseController
 | 
				
			||||||
 | 
					  before_action :require_operate_above
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def index
 | 
				
			||||||
 | 
					    @pipelines = Action::Pipeline.where(project_id: @project.id).order(updated_at: :desc)
 | 
				
			||||||
 | 
					    @pipelines = paginate @pipelines
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def create
 | 
				
			||||||
 | 
					    size = Action::Pipeline.where(pipeline_name: params[:pipeline_name], project_id: @project.id).size
 | 
				
			||||||
 | 
					    tip_exception("已经存在#{params[:pipeline_name]}流水线!") if size > 0
 | 
				
			||||||
 | 
					    @pipeline = Action::Pipeline.new(pipeline_name: params[:pipeline_name], project_id: @project.id)
 | 
				
			||||||
 | 
					    @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml"
 | 
				
			||||||
 | 
					    @pipeline.branch = params[:branch] || @project.default_branch
 | 
				
			||||||
 | 
					    @pipeline.json = params[:pipeline_json].to_json
 | 
				
			||||||
 | 
					    pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json])
 | 
				
			||||||
 | 
					    tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank?
 | 
				
			||||||
 | 
					    @pipeline.yaml = pipeline_yaml
 | 
				
			||||||
 | 
					    @pipeline.save!
 | 
				
			||||||
 | 
					    sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch)
 | 
				
			||||||
 | 
					    tip_exception("#{@pipeline.file_name}已存在") if sha
 | 
				
			||||||
 | 
					    interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create"))
 | 
				
			||||||
 | 
					    tip_exception(interactor.error) unless interactor.success?
 | 
				
			||||||
 | 
					    render_ok({ id: @pipeline.id })
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def save_yaml
 | 
				
			||||||
 | 
					    @pipeline = Action::Pipeline.new(pipeline_name: params[:pipeline_name], project_id: @project.id)
 | 
				
			||||||
 | 
					    @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml"
 | 
				
			||||||
 | 
					    @pipeline.branch = params[:branch] || @project.default_branch
 | 
				
			||||||
 | 
					    @pipeline.json = params[:pipeline_json].to_json
 | 
				
			||||||
 | 
					    pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json])
 | 
				
			||||||
 | 
					    tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank?
 | 
				
			||||||
 | 
					    @pipeline.yaml = pipeline_yaml
 | 
				
			||||||
 | 
					    Rails.logger.info "pipeline_yaml base64=========================#{Base64.encode64(@pipeline.yaml).gsub(/\n/, '')}"
 | 
				
			||||||
 | 
					    sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch)
 | 
				
			||||||
 | 
					    interactor = sha.present? ? Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update").merge(sha: sha)) : Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create"))
 | 
				
			||||||
 | 
					    tip_exception(interactor.error) unless interactor.success?
 | 
				
			||||||
 | 
					    file = interactor.result
 | 
				
			||||||
 | 
					    render_ok({ pipeline_yaml: pipeline_yaml, pipeline_name: params[:pipeline_name], file_name: @pipeline.file_name, sha: sha.present? ?  sha : file['content']['sha'] })
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_yaml
 | 
				
			||||||
 | 
					    if params[:pipeline_json].present?
 | 
				
			||||||
 | 
					      pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json])
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      pipeline_yaml = build_test_yaml
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    # render plain: pipeline_yaml
 | 
				
			||||||
 | 
					    render_ok({ pipeline_yaml: pipeline_yaml })
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update
 | 
				
			||||||
 | 
					    @pipeline = Action::Pipeline.find(params[:id])
 | 
				
			||||||
 | 
					    @pipeline.pipeline_name = params[:pipeline_name]
 | 
				
			||||||
 | 
					    @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml"
 | 
				
			||||||
 | 
					    @pipeline.branch = params[:branch] || @project.default_branch
 | 
				
			||||||
 | 
					    @pipeline.json = params[:pipeline_json].to_json
 | 
				
			||||||
 | 
					    pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json])
 | 
				
			||||||
 | 
					    tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank?
 | 
				
			||||||
 | 
					    @pipeline.yaml = pipeline_yaml
 | 
				
			||||||
 | 
					    @pipeline.save
 | 
				
			||||||
 | 
					    sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch)
 | 
				
			||||||
 | 
					    interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create").merge(sha: sha))
 | 
				
			||||||
 | 
					    tip_exception(interactor.error) unless interactor.success?
 | 
				
			||||||
 | 
					    file = interactor.result
 | 
				
			||||||
 | 
					    render_ok({ pipeline_yaml: pipeline_yaml, pipeline_name: params[:pipeline_name], file_name: @pipeline.file_name, sha: file['content']['sha'] })
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy
 | 
				
			||||||
 | 
					    @pipeline = Action::Pipeline.find(params[:id])
 | 
				
			||||||
 | 
					    if pipeline
 | 
				
			||||||
 | 
					      interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update"))
 | 
				
			||||||
 | 
					      tip_exception(interactor.error) unless interactor.success?
 | 
				
			||||||
 | 
					      @pipeline.destroy!
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    render_ok
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def show
 | 
				
			||||||
 | 
					    @pipeline = Action::Pipeline.find_by(id: params[:id])
 | 
				
			||||||
 | 
					    @pipeline = Action::Pipeline.new(id: 0, pipeline_name: "test-ss", yaml: build_test_yaml) if @pipeline.blank?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_pipeline_yaml(pipeline_name, pipeline_json)
 | 
				
			||||||
 | 
					    if pipeline_json.present? && pipeline_json.present?
 | 
				
			||||||
 | 
					      @pipeline_name = pipeline_name
 | 
				
			||||||
 | 
					      params_nodes = pipeline_json["nodes"].select { |node| !["on-push", "on-schedule"].include?(node["name"]) }
 | 
				
			||||||
 | 
					      on_nodes = pipeline_json["nodes"].select { |node| ["on-push", "on-schedule"].include?(node["name"]) }
 | 
				
			||||||
 | 
					      @on_nodes = build_nodes(on_nodes)
 | 
				
			||||||
 | 
					      @steps_nodes = build_nodes(params_nodes)
 | 
				
			||||||
 | 
					      yaml = ERB.new(File.read(File.join(Rails.root, "app/views/api/v1/projects/pipelines", "build_pipeline.yaml.erb"))).result(binding)
 | 
				
			||||||
 | 
					      # 删除空行内容
 | 
				
			||||||
 | 
					      pipeline_yaml = yaml.gsub(/^\s*\n/, "")
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      pipeline_yaml = params[:pipeline_yaml]
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    Rails.logger.info "pipeline_yaml========================="
 | 
				
			||||||
 | 
					    Rails.logger.info pipeline_yaml
 | 
				
			||||||
 | 
					    pipeline_yaml
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_test_yaml
 | 
				
			||||||
 | 
					    @pipeline_name = "I like it"
 | 
				
			||||||
 | 
					    params_nodes = JSON.parse(demo.to_json)["nodes"].select { |node| !["on-push", "on-schedule"].include?(node["name"]) }
 | 
				
			||||||
 | 
					    on_nodes = JSON.parse(demo.to_json)["nodes"].select { |node| ["on-push", "on-schedule"].include?(node["name"]) }
 | 
				
			||||||
 | 
					    @on_nodes = build_nodes(on_nodes)
 | 
				
			||||||
 | 
					    @steps_nodes = []
 | 
				
			||||||
 | 
					    params_nodes.each do |input_node|
 | 
				
			||||||
 | 
					      # Rails.logger.info "input_node=====0===#{input_node["name"]}======#{input_node["inputs"]}"
 | 
				
			||||||
 | 
					      node = Action::Node.find_by(name: input_node["name"])
 | 
				
			||||||
 | 
					      next if node.blank?
 | 
				
			||||||
 | 
					      node.label = input_node["label"] if input_node["label"].present?
 | 
				
			||||||
 | 
					      run_values = {}
 | 
				
			||||||
 | 
					      input_values = {}
 | 
				
			||||||
 | 
					      if input_node["inputs"].present?
 | 
				
			||||||
 | 
					        Rails.logger.info "@inputs=====11===#{input_node["name"]}======#{input_node["inputs"]}"
 | 
				
			||||||
 | 
					        input_node["inputs"].each do |input|
 | 
				
			||||||
 | 
					          # Rails.logger.info "@inputs.input_name===#{input[:name]}"
 | 
				
			||||||
 | 
					          # Rails.logger.info "@inputs.input_value===#{input["value"]}"
 | 
				
			||||||
 | 
					          if input[:name].to_s.gsub("--", "") == "run"
 | 
				
			||||||
 | 
					            run_values = run_values.merge({ "#{input[:name].gsub("--", "")}": "#{input["value"]}" })
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            input_values = input_values.merge({ "#{input[:name].gsub("--", "")}": "#{input["value"]}" })
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        node.run_values = run_values
 | 
				
			||||||
 | 
					        node.input_values = input_values
 | 
				
			||||||
 | 
					        # Rails.logger.info "@input_values run_values===#{node.run_values.to_json}"
 | 
				
			||||||
 | 
					        # Rails.logger.info "@input_values input_values===#{node.input_values.to_json}"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      @steps_nodes.push(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    Rails.logger.info "@@on_nodes===#{@on_nodes.to_json}"
 | 
				
			||||||
 | 
					    Rails.logger.info "@steps_nodes===#{@steps_nodes.to_json}"
 | 
				
			||||||
 | 
					    yaml = ERB.new(File.read(File.join(Rails.root, "app/views/api/v1/projects/pipelines", "build_pipeline.yaml.erb"))).result(binding)
 | 
				
			||||||
 | 
					    pipeline_yaml = yaml.gsub(/^\s*\n/, "")
 | 
				
			||||||
 | 
					    Rails.logger.info "========================="
 | 
				
			||||||
 | 
					    Rails.logger.info pipeline_yaml
 | 
				
			||||||
 | 
					    pipeline_yaml
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def get_pipeline_file_sha(file_name, branch)
 | 
				
			||||||
 | 
					    file_path_uri = URI.parse(URI.encode(file_name))
 | 
				
			||||||
 | 
					    interactor = Repositories::EntriesInteractor.call(@project.owner, @project.identifier, file_path_uri, ref: branch || @project.default_branch)
 | 
				
			||||||
 | 
					    if interactor.success?
 | 
				
			||||||
 | 
					      file = interactor.result
 | 
				
			||||||
 | 
					      file['sha']
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def content_params(opt)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      filepath: ".gitea/workflows/#{@pipeline.pipeline_name}.yml",
 | 
				
			||||||
 | 
					      branch: @pipeline.branch,
 | 
				
			||||||
 | 
					      new_branch: @pipeline.branch,
 | 
				
			||||||
 | 
					      content: opt == "create" ? Base64.encode64(@pipeline.yaml).gsub(/\n/, '') : @pipeline.yaml,
 | 
				
			||||||
 | 
					      message: "#{opt} pipeline",
 | 
				
			||||||
 | 
					      committer: {
 | 
				
			||||||
 | 
					        email: current_user.mail,
 | 
				
			||||||
 | 
					        name: current_user.login
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      identifier: @project.identifier
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_nodes(params_nodes)
 | 
				
			||||||
 | 
					    steps_nodes = []
 | 
				
			||||||
 | 
					    params_nodes.each do |input_node|
 | 
				
			||||||
 | 
					      node = Action::Node.find_by(name: input_node["name"])
 | 
				
			||||||
 | 
					      next if node.blank?
 | 
				
			||||||
 | 
					      node.label = input_node["label"] if input_node["label"].present?
 | 
				
			||||||
 | 
					      run_values = {}
 | 
				
			||||||
 | 
					      input_values = {}
 | 
				
			||||||
 | 
					      if input_node["inputs"].present?
 | 
				
			||||||
 | 
					        Rails.logger.info "@inputs=====11===#{input_node["name"]}======#{input_node["inputs"]}"
 | 
				
			||||||
 | 
					        input_node["inputs"].each do |input|
 | 
				
			||||||
 | 
					          # Rails.logger.info "@inputs.input_name===#{input[:name]}"
 | 
				
			||||||
 | 
					          # Rails.logger.info "@inputs.input_value===#{input["value"]}"
 | 
				
			||||||
 | 
					          if input[:name].to_s.gsub("--", "") == "run"
 | 
				
			||||||
 | 
					            run_values = run_values.merge({ "#{input[:name].gsub("--", "")}": "#{input["value"]}" })
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            input_values = input_values.merge({ "#{input[:name].gsub("--", "")}": "#{input["value"]}" })
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        node.run_values = run_values
 | 
				
			||||||
 | 
					        node.input_values = input_values
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      steps_nodes.push(node)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    steps_nodes
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def demo
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "nodes": [{
 | 
				
			||||||
 | 
					                  "id": "on-schedule-2fcf505",
 | 
				
			||||||
 | 
					                  "name": "on-schedule",
 | 
				
			||||||
 | 
					                  "full_name": "on-schedule",
 | 
				
			||||||
 | 
					                  "description": " 定时器计划器",
 | 
				
			||||||
 | 
					                  "icon": "https://testforgeplus.trustie.net/api/attachments/0445403c-5d9e-4495-8414-339f87981ca1",
 | 
				
			||||||
 | 
					                  "action_node_types_id": 3,
 | 
				
			||||||
 | 
					                  "yaml": "",
 | 
				
			||||||
 | 
					                  "sort_no": 0,
 | 
				
			||||||
 | 
					                  "use_count": 0,
 | 
				
			||||||
 | 
					                  "inputs": [{
 | 
				
			||||||
 | 
					                               "id": 8,
 | 
				
			||||||
 | 
					                               "name": "cron",
 | 
				
			||||||
 | 
					                               "input_type": "input",
 | 
				
			||||||
 | 
					                               "description": "示例:\r\n- cron: '20 8 * * *'",
 | 
				
			||||||
 | 
					                               "is_required": true,
 | 
				
			||||||
 | 
					                               "value": "- corn: '0 10 * * *'"
 | 
				
			||||||
 | 
					                             }],
 | 
				
			||||||
 | 
					                  "x": 586,
 | 
				
			||||||
 | 
					                  "y": 165.328125,
 | 
				
			||||||
 | 
					                  "label": "on-schedule",
 | 
				
			||||||
 | 
					                  "img": "https://testforgeplus.trustie.net/api/attachments/0445403c-5d9e-4495-8414-339f87981ca1",
 | 
				
			||||||
 | 
					                  "isCluster": false,
 | 
				
			||||||
 | 
					                  "type": "rect-node",
 | 
				
			||||||
 | 
					                  "size": [110, 36],
 | 
				
			||||||
 | 
					                  "labelCfg": {
 | 
				
			||||||
 | 
					                    "style": {
 | 
				
			||||||
 | 
					                      "fill": "transparent",
 | 
				
			||||||
 | 
					                      "fontSize": 0,
 | 
				
			||||||
 | 
					                      "boxShadow": "0px 0px 12px rgba(75, 84, 137, 0.05)",
 | 
				
			||||||
 | 
					                      "overflow": "hidden",
 | 
				
			||||||
 | 
					                      "x": -20,
 | 
				
			||||||
 | 
					                      "y": 0,
 | 
				
			||||||
 | 
					                      "textAlign": "left",
 | 
				
			||||||
 | 
					                      "textBaseline": "middle"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "style": {
 | 
				
			||||||
 | 
					                    "active": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(247, 250, 255)",
 | 
				
			||||||
 | 
					                      "stroke": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 2,
 | 
				
			||||||
 | 
					                      "shadowColor": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "shadowBlur": 10
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "selected": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(255, 255, 255)",
 | 
				
			||||||
 | 
					                      "stroke": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 4,
 | 
				
			||||||
 | 
					                      "shadowColor": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "shadowBlur": 10,
 | 
				
			||||||
 | 
					                      "text-shape": {
 | 
				
			||||||
 | 
					                        "fontWeight": 500
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "highlight": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(223, 234, 255)",
 | 
				
			||||||
 | 
					                      "stroke": "#4572d9",
 | 
				
			||||||
 | 
					                      "lineWidth": 2,
 | 
				
			||||||
 | 
					                      "text-shape": {
 | 
				
			||||||
 | 
					                        "fontWeight": 500
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "inactive": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(247, 250, 255)",
 | 
				
			||||||
 | 
					                      "stroke": "rgb(191, 213, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 1
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "disable": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(250, 250, 250)",
 | 
				
			||||||
 | 
					                      "stroke": "rgb(224, 224, 224)",
 | 
				
			||||||
 | 
					                      "lineWidth": 1
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "nodeSelected": {
 | 
				
			||||||
 | 
					                      "fill": "red",
 | 
				
			||||||
 | 
					                      "shadowColor": "red",
 | 
				
			||||||
 | 
					                      "stroke": "red",
 | 
				
			||||||
 | 
					                      "text-shape": {
 | 
				
			||||||
 | 
					                        "fill": "red",
 | 
				
			||||||
 | 
					                        "stroke": "red"
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "fill": "#fff",
 | 
				
			||||||
 | 
					                    "stroke": "transparent",
 | 
				
			||||||
 | 
					                    "cursor": "pointer",
 | 
				
			||||||
 | 
					                    "radius": 10,
 | 
				
			||||||
 | 
					                    "overflow": "hidden",
 | 
				
			||||||
 | 
					                    "lineWidth": 0.5,
 | 
				
			||||||
 | 
					                    "shadowColor": "rgba(75,84,137,0.05)",
 | 
				
			||||||
 | 
					                    "shadowBlur": 12
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "cron": "- corn: '0 10 * * *'",
 | 
				
			||||||
 | 
					                  "depth": 0
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                  "id": "actions/setup-node@v3-257f29d",
 | 
				
			||||||
 | 
					                  "name": "node",
 | 
				
			||||||
 | 
					                  "full_name": "actions/setup-node@v3",
 | 
				
			||||||
 | 
					                  "description": "",
 | 
				
			||||||
 | 
					                  "icon": "https://testforgeplus.trustie.net/api/attachments/c4774fc1-ecd9-47fd-9878-1847bdaf98f6",
 | 
				
			||||||
 | 
					                  "action_node_types_id": 1,
 | 
				
			||||||
 | 
					                  "yaml": "",
 | 
				
			||||||
 | 
					                  "sort_no": 0,
 | 
				
			||||||
 | 
					                  "use_count": 0,
 | 
				
			||||||
 | 
					                  "inputs": [{
 | 
				
			||||||
 | 
					                               "id": 2,
 | 
				
			||||||
 | 
					                               "name": "node-version",
 | 
				
			||||||
 | 
					                               "input_type": "select",
 | 
				
			||||||
 | 
					                               "is_required": false,
 | 
				
			||||||
 | 
					                               "value": 55
 | 
				
			||||||
 | 
					                             }],
 | 
				
			||||||
 | 
					                  "x": 608,
 | 
				
			||||||
 | 
					                  "y": 357.328125,
 | 
				
			||||||
 | 
					                  "label": "node",
 | 
				
			||||||
 | 
					                  "img": "https://testforgeplus.trustie.net/api/attachments/c4774fc1-ecd9-47fd-9878-1847bdaf98f6",
 | 
				
			||||||
 | 
					                  "isCluster": false,
 | 
				
			||||||
 | 
					                  "type": "rect-node",
 | 
				
			||||||
 | 
					                  "size": [110, 36],
 | 
				
			||||||
 | 
					                  "labelCfg": {
 | 
				
			||||||
 | 
					                    "style": {
 | 
				
			||||||
 | 
					                      "fill": "transparent",
 | 
				
			||||||
 | 
					                      "fontSize": 0,
 | 
				
			||||||
 | 
					                      "boxShadow": "0px 0px 12px rgba(75, 84, 137, 0.05)",
 | 
				
			||||||
 | 
					                      "overflow": "hidden",
 | 
				
			||||||
 | 
					                      "x": -20,
 | 
				
			||||||
 | 
					                      "y": 0,
 | 
				
			||||||
 | 
					                      "textAlign": "left",
 | 
				
			||||||
 | 
					                      "textBaseline": "middle"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "style": {
 | 
				
			||||||
 | 
					                    "active": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(247, 250, 255)",
 | 
				
			||||||
 | 
					                      "stroke": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 2,
 | 
				
			||||||
 | 
					                      "shadowColor": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "shadowBlur": 10
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "selected": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(255, 255, 255)",
 | 
				
			||||||
 | 
					                      "stroke": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 4,
 | 
				
			||||||
 | 
					                      "shadowColor": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "shadowBlur": 10,
 | 
				
			||||||
 | 
					                      "text-shape": {
 | 
				
			||||||
 | 
					                        "fontWeight": 500
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "highlight": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(223, 234, 255)",
 | 
				
			||||||
 | 
					                      "stroke": "#4572d9",
 | 
				
			||||||
 | 
					                      "lineWidth": 2,
 | 
				
			||||||
 | 
					                      "text-shape": {
 | 
				
			||||||
 | 
					                        "fontWeight": 500
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "inactive": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(247, 250, 255)",
 | 
				
			||||||
 | 
					                      "stroke": "rgb(191, 213, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 1
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "disable": {
 | 
				
			||||||
 | 
					                      "fill": "rgb(250, 250, 250)",
 | 
				
			||||||
 | 
					                      "stroke": "rgb(224, 224, 224)",
 | 
				
			||||||
 | 
					                      "lineWidth": 1
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "nodeSelected": {
 | 
				
			||||||
 | 
					                      "fill": "red",
 | 
				
			||||||
 | 
					                      "shadowColor": "red",
 | 
				
			||||||
 | 
					                      "stroke": "red",
 | 
				
			||||||
 | 
					                      "text-shape": {
 | 
				
			||||||
 | 
					                        "fill": "red",
 | 
				
			||||||
 | 
					                        "stroke": "red"
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "fill": "#fff",
 | 
				
			||||||
 | 
					                    "stroke": "transparent",
 | 
				
			||||||
 | 
					                    "cursor": "pointer",
 | 
				
			||||||
 | 
					                    "radius": 10,
 | 
				
			||||||
 | 
					                    "overflow": "hidden",
 | 
				
			||||||
 | 
					                    "lineWidth": 0.5,
 | 
				
			||||||
 | 
					                    "shadowColor": "rgba(75,84,137,0.05)",
 | 
				
			||||||
 | 
					                    "shadowBlur": 12
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "depth": 0,
 | 
				
			||||||
 | 
					                  "node-version": 55
 | 
				
			||||||
 | 
					                }],
 | 
				
			||||||
 | 
					      "edges": [{
 | 
				
			||||||
 | 
					                  "source": "on-schedule-2fcf505",
 | 
				
			||||||
 | 
					                  "target": "actions/setup-node@v3-257f29d",
 | 
				
			||||||
 | 
					                  "style": {
 | 
				
			||||||
 | 
					                    "active": {
 | 
				
			||||||
 | 
					                      "stroke": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 1
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "selected": {
 | 
				
			||||||
 | 
					                      "stroke": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 2,
 | 
				
			||||||
 | 
					                      "shadowColor": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "shadowBlur": 10,
 | 
				
			||||||
 | 
					                      "text-shape": {
 | 
				
			||||||
 | 
					                        "fontWeight": 500
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "highlight": {
 | 
				
			||||||
 | 
					                      "stroke": "rgb(95, 149, 255)",
 | 
				
			||||||
 | 
					                      "lineWidth": 2,
 | 
				
			||||||
 | 
					                      "text-shape": {
 | 
				
			||||||
 | 
					                        "fontWeight": 500
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "inactive": {
 | 
				
			||||||
 | 
					                      "stroke": "rgb(234, 234, 234)",
 | 
				
			||||||
 | 
					                      "lineWidth": 1
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "disable": {
 | 
				
			||||||
 | 
					                      "stroke": "rgb(245, 245, 245)",
 | 
				
			||||||
 | 
					                      "lineWidth": 1
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "endArrow": {
 | 
				
			||||||
 | 
					                      "path": "M 6,0 L 9,-1.5 L 9,1.5 Z",
 | 
				
			||||||
 | 
					                      "d": 4.5,
 | 
				
			||||||
 | 
					                      "fill": "#CDD0DC"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "cursor": "pointer",
 | 
				
			||||||
 | 
					                    "lineWidth": 1,
 | 
				
			||||||
 | 
					                    "opacity": 1,
 | 
				
			||||||
 | 
					                    "stroke": "#CDD0DC",
 | 
				
			||||||
 | 
					                    "radius": 1
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "nodeStateStyle": {
 | 
				
			||||||
 | 
					                    "hover": {
 | 
				
			||||||
 | 
					                      "opacity": 1,
 | 
				
			||||||
 | 
					                      "stroke": "#8fe8ff"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "labelCfg": {
 | 
				
			||||||
 | 
					                    "autoRotate": true,
 | 
				
			||||||
 | 
					                    "style": {
 | 
				
			||||||
 | 
					                      "fontSize": 10,
 | 
				
			||||||
 | 
					                      "fill": "#FFF"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "id": "edge-0.96904321945951241716516719464",
 | 
				
			||||||
 | 
					                  "startPoint": {
 | 
				
			||||||
 | 
					                    "x": 586,
 | 
				
			||||||
 | 
					                    "y": 183.578125,
 | 
				
			||||||
 | 
					                    "anchorIndex": 1
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "endPoint": {
 | 
				
			||||||
 | 
					                    "x": 608,
 | 
				
			||||||
 | 
					                    "y": 339.078125,
 | 
				
			||||||
 | 
					                    "anchorIndex": 0
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "sourceAnchor": 1,
 | 
				
			||||||
 | 
					                  "targetAnchor": 0,
 | 
				
			||||||
 | 
					                  "type": "cubic-vertical",
 | 
				
			||||||
 | 
					                  "curveOffset": [0, 0],
 | 
				
			||||||
 | 
					                  "curvePosition": [0.5, 0.5],
 | 
				
			||||||
 | 
					                  "minCurveOffset": [0, 0]
 | 
				
			||||||
 | 
					                }],
 | 
				
			||||||
 | 
					      "combos": []
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
class Api::V1::ProjectsController < Api::V1::BaseController 
 | 
					class Api::V1::ProjectsController < Api::V1::BaseController 
 | 
				
			||||||
  before_action :require_public_and_member_above, only: [:show, :compare, :blame]
 | 
					  before_action :require_public_and_member_above, only: [:show, :compare, :blame, :sonar_search]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def index 
 | 
					  def index 
 | 
				
			||||||
    render_ok
 | 
					    render_ok
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ class Api::V1::ProjectsController < Api::V1::BaseController
 | 
				
			||||||
    @result_object = Api::V1::Projects::GetService.call(@project, current_user.gitea_token)
 | 
					    @result_object = Api::V1::Projects::GetService.call(@project, current_user.gitea_token)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def compare
 | 
					  def compare
 | 
				
			||||||
    @result_object = Api::V1::Projects::CompareService.call(@project, params[:from], params[:to], current_user&.gitea_token)
 | 
					    @result_object = Api::V1::Projects::CompareService.call(@project, params[:from], params[:to], current_user&.gitea_token)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,171 @@
 | 
				
			||||||
 | 
					class Api::V1::SonarqubesController < Api::V1::BaseController
 | 
				
			||||||
 | 
					  before_action :load_repository
 | 
				
			||||||
 | 
					  def sonar_initialize
 | 
				
			||||||
 | 
					    gitea_params = { has_actions: params[:has_actions] == 'true' ? true :false }
 | 
				
			||||||
 | 
					    gitea_setting = Gitea::Repository::UpdateService.call(@owner, @project.identifier, gitea_params)
 | 
				
			||||||
 | 
					    if gitea_setting['has_actions'] == true
 | 
				
			||||||
 | 
					      Gitea::Repository::ActionSecretsService.new(@owner, @project.identifier, 'SONAR_HOST_URL', Rails.application.config_for(:configuration)['sonarqube']['url'] ).call
 | 
				
			||||||
 | 
					      Gitea::Repository::ActionSecretsService.new(@owner, @project.identifier, 'SONAR_TOKEN', Rails.application.config_for(:configuration)['sonarqube']['secret'] ).call
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      Gitea::Repository::ActionSecretsService.new(@owner, @project.identifier, 'SONAR_HOST_URL', Rails.application.config_for(:configuration)['sonarqube']['url'] ).destroy
 | 
				
			||||||
 | 
					      Gitea::Repository::ActionSecretsService.new(@owner, @project.identifier, 'SONAR_TOKEN', Rails.application.config_for(:configuration)['sonarqube']['secret'] ).destroy
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    @project.update(gitea_params)
 | 
				
			||||||
 | 
					    render_ok
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def insert_file
 | 
				
			||||||
 | 
					    checkout_url = 'https://gitlink.org.cn/KingChan/checkout@v4'
 | 
				
			||||||
 | 
					    scanner_url = 'https://gitlink.org.cn/KingChan/sonarqube-scan-action@master'
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
 | 
					      config = Rails.application.config_for(:configuration)
 | 
				
			||||||
 | 
					      sonarqube_config = config.dig('sonarqube')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if sonarqube_config.present? && sonarqube_config['checkout'].present?
 | 
				
			||||||
 | 
					        checkout_url = sonarqube_config['checkout']
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      if sonarqube_config.present? && sonarqube_config['scanner'].present?
 | 
				
			||||||
 | 
					        scanner_url = sonarqube_config['scanner']
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      raise 'sonar config missing' if sonarqube_config.blank?
 | 
				
			||||||
 | 
					    rescue => ex
 | 
				
			||||||
 | 
					      raise ex if Rails.env.production?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      puts %Q{\033[33m [warning] soanrqube config or configuration.yml missing,
 | 
				
			||||||
 | 
					           please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m}
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sonar_scanner_content = {
 | 
				
			||||||
 | 
					      filepath: '.gitea/workflows/SonarScanner.yaml',
 | 
				
			||||||
 | 
					      branch: params[:branch],
 | 
				
			||||||
 | 
					      new_branch: nil,
 | 
				
			||||||
 | 
					      content: "
 | 
				
			||||||
 | 
					              on:
 | 
				
			||||||
 | 
					                # Trigger analysis when pushing to your main branches, and when creating a pull request.
 | 
				
			||||||
 | 
					                push:
 | 
				
			||||||
 | 
					                  branches:
 | 
				
			||||||
 | 
					                    - main
 | 
				
			||||||
 | 
					                    - master
 | 
				
			||||||
 | 
					                    - develop
 | 
				
			||||||
 | 
					                    - 'releases/**'
 | 
				
			||||||
 | 
					                pull_request:
 | 
				
			||||||
 | 
					                    types: [opened, synchronize, reopened]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              name: Main Workflow
 | 
				
			||||||
 | 
					              jobs:
 | 
				
			||||||
 | 
					                sonarqube:
 | 
				
			||||||
 | 
					                  runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					                  steps:
 | 
				
			||||||
 | 
					                  - uses: #{checkout_url}
 | 
				
			||||||
 | 
					                    with:
 | 
				
			||||||
 | 
					                      # Disabling shallow clones is recommended for improving the relevancy of reporting
 | 
				
			||||||
 | 
					                      fetch-depth: 0
 | 
				
			||||||
 | 
					                  - name: SonarQube Scan
 | 
				
			||||||
 | 
					                    uses:  #{scanner_url}
 | 
				
			||||||
 | 
					                    env:
 | 
				
			||||||
 | 
					                      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
 | 
				
			||||||
 | 
					                      SONAR_HOST_URL:  ${{ secrets.SONAR_HOST_URL }}
 | 
				
			||||||
 | 
					       ",
 | 
				
			||||||
 | 
					      message: 'Add .gitea/workflows/SonarScanner.yaml',
 | 
				
			||||||
 | 
					      committer: {
 | 
				
			||||||
 | 
					        email: @owner.mail,
 | 
				
			||||||
 | 
					        name: @owner.login
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      identifier: @project.identifier
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    @path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{params[:branch]}/"
 | 
				
			||||||
 | 
					    sonar_scanner_exit = Repositories::EntriesInteractor.call(@owner, @project.identifier, '.gitea/workflows/SonarScanner.yaml', ref: params[:branch])
 | 
				
			||||||
 | 
					    if sonar_scanner_exit.success?
 | 
				
			||||||
 | 
					      Gitea::UpdateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_scanner_content.merge(sha:sonar_scanner_exit.result['sha']))
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      sonar_scanner_content[:content] = Base64.strict_encode64(sonar_scanner_content[:content])
 | 
				
			||||||
 | 
					      Gitea::CreateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_scanner_content)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sonar_project_content = {
 | 
				
			||||||
 | 
					      filepath: 'sonar-project.properties',
 | 
				
			||||||
 | 
					      branch: params[:branch],
 | 
				
			||||||
 | 
					      new_branch: nil,
 | 
				
			||||||
 | 
					      "content": "sonar.projectKey=#{params[:owner]}-#{@project.id}\nsonar.sources=.\nsonar.java.binaries=.",
 | 
				
			||||||
 | 
					      "message": 'Add sonar-project.properties',
 | 
				
			||||||
 | 
					      committer: {
 | 
				
			||||||
 | 
					        email: @owner.mail,
 | 
				
			||||||
 | 
					        name: @owner.login
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      identifier: @project.identifier
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    sonar_project_exit = Repositories::EntriesInteractor.call(@owner, @project.identifier, 'sonar-project.properties', ref: params[:branch])
 | 
				
			||||||
 | 
					    if sonar_project_exit.success?
 | 
				
			||||||
 | 
					      Gitea::UpdateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_project_content.merge(sha:sonar_project_exit.result['sha']))
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      sonar_project_content[:content] = Base64.strict_encode64(sonar_project_content[:content])
 | 
				
			||||||
 | 
					      Gitea::CreateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_project_content)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    render_ok
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issues_search
 | 
				
			||||||
 | 
					    params_data = {
 | 
				
			||||||
 | 
					      components: "#{params[:owner]}-#{@project.id}",
 | 
				
			||||||
 | 
					      s: params[:s],
 | 
				
			||||||
 | 
					      impactSoftwareQualities: params[:impactSoftwareQualities],
 | 
				
			||||||
 | 
					      issueStatuses: params[:issueStatuses],
 | 
				
			||||||
 | 
					      ps:  params[:ps],
 | 
				
			||||||
 | 
					      p:  params[:p],
 | 
				
			||||||
 | 
					      facets: params[:facets],
 | 
				
			||||||
 | 
					      additionalFields: params[:additionalFields],
 | 
				
			||||||
 | 
					      timeZone: params[:timeZone],
 | 
				
			||||||
 | 
					      types: params[:types],
 | 
				
			||||||
 | 
					      impactSeverities: params[:impactSeverities],
 | 
				
			||||||
 | 
					      tags: params[:tags]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = Sonarqube.client.get('/api/issues/search', query: params_data)
 | 
				
			||||||
 | 
					    render_ok data
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def ce_component
 | 
				
			||||||
 | 
					    params_data = {
 | 
				
			||||||
 | 
					      components:  "#{params[:owner]}-#{@project.id}",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = Sonarqube.client.get('/api/ce/component', query: params_data)
 | 
				
			||||||
 | 
					    render_ok data
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def sources_issue_snippet
 | 
				
			||||||
 | 
					    params_data = {
 | 
				
			||||||
 | 
					      issueKey: params[:issueKey]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = Sonarqube.client.get('/api/sources/issue_snippets', query: params_data)
 | 
				
			||||||
 | 
					    render_ok data
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def rules_show
 | 
				
			||||||
 | 
					    params_data = {
 | 
				
			||||||
 | 
					      key: params[:key]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = Sonarqube.client.get('/api/rules/show', query: params_data)
 | 
				
			||||||
 | 
					    render_ok data
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def measures_search_history
 | 
				
			||||||
 | 
					    params_data = {
 | 
				
			||||||
 | 
					      from: params[:form],
 | 
				
			||||||
 | 
					      component: "#{params[:owner]}-#{@project.id}",
 | 
				
			||||||
 | 
					      metrics: params[:metrics],
 | 
				
			||||||
 | 
					      ps: params[:ps]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = Sonarqube.client.get('/api/measures/search_history', query: params_data)
 | 
				
			||||||
 | 
					    render_ok data
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def measures_component
 | 
				
			||||||
 | 
					    params_data = {
 | 
				
			||||||
 | 
					      component: "#{params[:owner]}-#{@project.id}",
 | 
				
			||||||
 | 
					      additionalFields: params[:additionalFields],
 | 
				
			||||||
 | 
					      metricKeys: params[:metricKeys]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = Sonarqube.client.get('/api/measures/component', query: params_data)
 | 
				
			||||||
 | 
					    render_ok data
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -326,19 +326,19 @@ class ApplicationController < ActionController::Base
 | 
				
			||||||
			User.current = find_current_user
 | 
								User.current = find_current_user
 | 
				
			||||||
			uid_logger("user_setup: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous"))
 | 
								uid_logger("user_setup: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			# 开放课程通过链接访问的用户
 | 
								# # 开放课程通过链接访问的用户
 | 
				
			||||||
			if !User.current.logged? && !params[:chinaoocTimestamp].blank? && !params[:websiteName].blank? && !params[:chinaoocKey].blank?
 | 
								# if !User.current.logged? && !params[:chinaoocTimestamp].blank? && !params[:websiteName].blank? && !params[:chinaoocKey].blank?
 | 
				
			||||||
				content = "#{OPENKEY}#{params[:websiteName]}#{params[:chinaoocTimestamp]}"
 | 
								# 	content = "#{OPENKEY}#{params[:websiteName]}#{params[:chinaoocTimestamp]}"
 | 
				
			||||||
 | 
								#
 | 
				
			||||||
				if Digest::MD5.hexdigest(content) == params[:chinaoocKey]
 | 
								# 	if Digest::MD5.hexdigest(content) == params[:chinaoocKey]
 | 
				
			||||||
					user = open_class_user
 | 
								# 		user = open_class_user
 | 
				
			||||||
					if user
 | 
								# 		if user
 | 
				
			||||||
						start_user_session(user)
 | 
								# 			start_user_session(user)
 | 
				
			||||||
						set_autologin_cookie(user)
 | 
								# 			set_autologin_cookie(user)
 | 
				
			||||||
					end
 | 
								# 		end
 | 
				
			||||||
					User.current = user
 | 
								# 		User.current = user
 | 
				
			||||||
				end
 | 
								# 	end
 | 
				
			||||||
			end
 | 
								# end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !User.current.logged? && Rails.env.development?
 | 
								if !User.current.logged? && Rails.env.development?
 | 
				
			||||||
				user = User.find 1
 | 
									user = User.find 1
 | 
				
			||||||
| 
						 | 
					@ -371,15 +371,14 @@ class ApplicationController < ActionController::Base
 | 
				
			||||||
		uid_logger("user setup start: session[:user_id] is #{session[:user_id]}")
 | 
							uid_logger("user setup start: session[:user_id] is #{session[:user_id]}")
 | 
				
			||||||
		uid_logger("0000000000000user setup start: default_yun_session is #{default_yun_session}, session[:current_user_id] is #{session[:"#{default_yun_session}"]}")
 | 
							uid_logger("0000000000000user setup start: default_yun_session is #{default_yun_session}, session[:current_user_id] is #{session[:"#{default_yun_session}"]}")
 | 
				
			||||||
		current_domain_session = session[:"#{default_yun_session}"]
 | 
							current_domain_session = session[:"#{default_yun_session}"]
 | 
				
			||||||
		if current_domain_session
 | 
							autologin_user = try_to_autologin
 | 
				
			||||||
			# existing session
 | 
							uid_logger("user setup start: autologin_user is #{autologin_user}")
 | 
				
			||||||
			User.current = (User.active.find(current_domain_session) rescue nil)
 | 
							# 多浏览器退出账号时,token不存在处理
 | 
				
			||||||
		elsif autologin_user = try_to_autologin
 | 
							if current_domain_session && autologin_user.nil?
 | 
				
			||||||
			autologin_user
 | 
								autologin_user = (User.active.find(current_domain_session) rescue nil)
 | 
				
			||||||
		elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
 | 
								set_autologin_cookie(autologin_user)
 | 
				
			||||||
			# RSS key authentication does not start a session
 | 
					 | 
				
			||||||
			User.find_by_rss_key(params[:key])
 | 
					 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
							autologin_user
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def try_to_autologin
 | 
						def try_to_autologin
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,9 @@ class Organizations::ProjectsController < Organizations::BaseController
 | 
				
			||||||
    @projects = Project.from("( #{ public_projects_sql} UNION #{ private_projects_sql } ) AS projects")
 | 
					    @projects = Project.from("( #{ public_projects_sql} UNION #{ private_projects_sql } ) AS projects")
 | 
				
			||||||
    # 表情处理
 | 
					    # 表情处理
 | 
				
			||||||
    keywords = params[:search].to_s.each_char.select { |c| c.bytes.first < 240 }.join('')
 | 
					    keywords = params[:search].to_s.each_char.select { |c| c.bytes.first < 240 }.join('')
 | 
				
			||||||
 | 
					    @projects = @projects.where(id: params[:pm_project_repository_ids].split(',')) if params[:pm_project_repository_ids].present?
 | 
				
			||||||
 | 
					    @projects = @projects.where.not(id: params[:exclude_ids].to_s.split(",")) if params[:exclude_ids].present?
 | 
				
			||||||
 | 
					    @projects = @projects.where("gpid is not null") if params[:actived].present?
 | 
				
			||||||
    @projects = @projects.ransack(name_or_identifier_cont: keywords).result if params[:search].present?
 | 
					    @projects = @projects.ransack(name_or_identifier_cont: keywords).result if params[:search].present?
 | 
				
			||||||
    @projects = @projects.includes(:owner).order("projects.#{sort} #{sort_direction}")
 | 
					    @projects = @projects.includes(:owner).order("projects.#{sort} #{sort_direction}")
 | 
				
			||||||
    @projects = paginate(@projects)
 | 
					    @projects = paginate(@projects)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -250,15 +250,6 @@ class ProjectsController < ApplicationController
 | 
				
			||||||
  def show
 | 
					  def show
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def mp_show
 | 
					 | 
				
			||||||
    @project = Project.joins(:owner).find params[:project_id]
 | 
					 | 
				
			||||||
    data={
 | 
					 | 
				
			||||||
      owner:@project.owner.try(:login),
 | 
					 | 
				
			||||||
      identifier:@project.identifier
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    render_ok(data:data)
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def destroy
 | 
					  def destroy
 | 
				
			||||||
    if current_user.admin? || @project.manager?(current_user)
 | 
					    if current_user.admin? || @project.manager?(current_user)
 | 
				
			||||||
      ActiveRecord::Base.transaction do
 | 
					      ActiveRecord::Base.transaction do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					module Api::Pm::IssueLinksHelper
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					module Api::Pm::ProjectsHelper
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					module Api::V1::PmIssuesHelper
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					module Api::V1::Sonarqube::IssuesHelper
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					module Api::V1::SonarqubesHelper
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					module Pm::JournalsHelper
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -16,11 +16,12 @@
 | 
				
			||||||
#  user_id              :integer
 | 
					#  user_id              :integer
 | 
				
			||||||
#  created_at           :datetime         not null
 | 
					#  created_at           :datetime         not null
 | 
				
			||||||
#  updated_at           :datetime         not null
 | 
					#  updated_at           :datetime         not null
 | 
				
			||||||
 | 
					#  label                :string(255)
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  index_action_nodes_on_action_types_id  (action_node_types_id)
 | 
					#  index_action_nodes_on_action_node_types_id  (action_node_types_id)
 | 
				
			||||||
#  index_action_nodes_on_user_id          (user_id)
 | 
					#  index_action_nodes_on_user_id               (user_id)
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Action::Node < ApplicationRecord
 | 
					class Action::Node < ApplicationRecord
 | 
				
			||||||
| 
						 | 
					@ -33,39 +34,36 @@ class Action::Node < ApplicationRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  belongs_to :user, optional: true
 | 
					  belongs_to :user, optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  attr_accessor :cust_name, :run_values, :input_values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # def content_yaml
 | 
					  validates :name, presence: { message: "不能为空" }
 | 
				
			||||||
  #   "foo".to_yaml
 | 
					  validates :full_name, length: { maximum: 200, too_long: "不能超过200个字符" }
 | 
				
			||||||
  #   <<~YAML
 | 
					  validates :label, length: { maximum: 200, too_long: "不能超过200个字符" }
 | 
				
			||||||
  #   - name: Set up JDK ${{ matrix.java }}
 | 
					  validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
 | 
				
			||||||
  #     uses: actions/setup-java@v3
 | 
					 | 
				
			||||||
  #     with:
 | 
					 | 
				
			||||||
  #       distribution: 'temurin'
 | 
					 | 
				
			||||||
  #       java-version: ${{ matrix.java }}
 | 
					 | 
				
			||||||
  #   YAML
 | 
					 | 
				
			||||||
  # end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def yaml_hash
 | 
					
 | 
				
			||||||
 | 
					  def content_yaml
 | 
				
			||||||
 | 
					    "foo".to_yaml
 | 
				
			||||||
    <<~YAML
 | 
					    <<~YAML
 | 
				
			||||||
      name: Check dist
 | 
					    - name: Set up JDK ${{ matrix.java }}
 | 
				
			||||||
 | 
					      uses: actions/setup-java@v3
 | 
				
			||||||
      on:
 | 
					      with:
 | 
				
			||||||
        push:
 | 
					        distribution: 'temurin'
 | 
				
			||||||
          branches:
 | 
					        java-version: ${{ matrix.java }}
 | 
				
			||||||
            - main
 | 
					 | 
				
			||||||
          paths-ignore:
 | 
					 | 
				
			||||||
            - '**.md'
 | 
					 | 
				
			||||||
        pull_request:
 | 
					 | 
				
			||||||
          paths-ignore:
 | 
					 | 
				
			||||||
            - '**.md'
 | 
					 | 
				
			||||||
        workflow_dispatch:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      jobs:
 | 
					 | 
				
			||||||
        call-check-dist:
 | 
					 | 
				
			||||||
          name: Check dist/
 | 
					 | 
				
			||||||
          uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main
 | 
					 | 
				
			||||||
          with:
 | 
					 | 
				
			||||||
            node-version: '20.x'
 | 
					 | 
				
			||||||
    YAML
 | 
					    YAML
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def node
 | 
				
			||||||
 | 
					    self
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_yaml
 | 
				
			||||||
 | 
					    yaml = ERB.new(File.read(File.join(Rails.root, "app/views/api/v1/projects/pipelines", "build_node.yaml.erb"))).result(binding)
 | 
				
			||||||
 | 
					    # 删除空行内容
 | 
				
			||||||
 | 
					    yaml = yaml.gsub(/^\s*\n/, "")
 | 
				
			||||||
 | 
					    # Rails.logger.info "========================="
 | 
				
			||||||
 | 
					    # Rails.logger.info yaml
 | 
				
			||||||
 | 
					    yaml
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@
 | 
				
			||||||
#  input_type      :string(255)
 | 
					#  input_type      :string(255)
 | 
				
			||||||
#  description     :string(255)
 | 
					#  description     :string(255)
 | 
				
			||||||
#  is_required     :boolean          default("0")
 | 
					#  is_required     :boolean          default("0")
 | 
				
			||||||
#  sort_no         :string(255)      default("0")
 | 
					#  sort_no         :integer          default("0")
 | 
				
			||||||
#  user_id         :integer
 | 
					#  user_id         :integer
 | 
				
			||||||
#  created_at      :datetime         not null
 | 
					#  created_at      :datetime         not null
 | 
				
			||||||
#  updated_at      :datetime         not null
 | 
					#  updated_at      :datetime         not null
 | 
				
			||||||
| 
						 | 
					@ -24,4 +24,7 @@ class Action::NodeInput < ApplicationRecord
 | 
				
			||||||
  default_scope { order(sort_no: :asc) }
 | 
					  default_scope { order(sort_no: :asc) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  belongs_to :action_node, :class_name => 'Action::Node', foreign_key: "action_nodes_id"
 | 
					  belongs_to :action_node, :class_name => 'Action::Node', foreign_key: "action_nodes_id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :name, presence: { message: "不能为空" }
 | 
				
			||||||
 | 
					  validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,9 @@ class Action::NodeSelect < ApplicationRecord
 | 
				
			||||||
  belongs_to :action_node, :class_name => 'Action::Node', foreign_key: "action_nodes_id"
 | 
					  belongs_to :action_node, :class_name => 'Action::Node', foreign_key: "action_nodes_id"
 | 
				
			||||||
  belongs_to :user, optional: true
 | 
					  belongs_to :user, optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :name, presence: { message: "不能为空" }
 | 
				
			||||||
 | 
					  validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def value
 | 
					  def value
 | 
				
			||||||
    if self.val_ext.blank?
 | 
					    if self.val_ext.blank?
 | 
				
			||||||
      self.val
 | 
					      self.val
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@
 | 
				
			||||||
#  id          :integer          not null, primary key
 | 
					#  id          :integer          not null, primary key
 | 
				
			||||||
#  name        :string(255)
 | 
					#  name        :string(255)
 | 
				
			||||||
#  description :string(255)
 | 
					#  description :string(255)
 | 
				
			||||||
#  sort_no     :integer
 | 
					#  sort_no     :integer          default("0")
 | 
				
			||||||
#  created_at  :datetime         not null
 | 
					#  created_at  :datetime         not null
 | 
				
			||||||
#  updated_at  :datetime         not null
 | 
					#  updated_at  :datetime         not null
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -15,4 +15,7 @@ class Action::NodeType < ApplicationRecord
 | 
				
			||||||
  default_scope { order(sort_no: :asc) }
 | 
					  default_scope { order(sort_no: :asc) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  has_many :action_nodes, :class_name => 'Action::Node', foreign_key: "action_node_types_id"
 | 
					  has_many :action_nodes, :class_name => 'Action::Node', foreign_key: "action_node_types_id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :name, presence: { message: "不能为空" }
 | 
				
			||||||
 | 
					  validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					# == Schema Information
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Table name: action_pipelines
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  id                :integer          not null, primary key
 | 
				
			||||||
 | 
					#  project_id        :integer
 | 
				
			||||||
 | 
					#  user_id           :integer
 | 
				
			||||||
 | 
					#  pipeline_name     :string(255)
 | 
				
			||||||
 | 
					#  pipeline_status   :string(255)
 | 
				
			||||||
 | 
					#  description       :string(255)
 | 
				
			||||||
 | 
					#  file_name         :string(255)
 | 
				
			||||||
 | 
					#  is_graphic_design :boolean          default("0")
 | 
				
			||||||
 | 
					#  repo_name         :string(255)
 | 
				
			||||||
 | 
					#  repo_identifier   :string(255)
 | 
				
			||||||
 | 
					#  repo_owner        :string(255)
 | 
				
			||||||
 | 
					#  branch            :string(255)
 | 
				
			||||||
 | 
					#  event             :string(255)
 | 
				
			||||||
 | 
					#  sha               :string(255)
 | 
				
			||||||
 | 
					#  json              :text(65535)
 | 
				
			||||||
 | 
					#  yaml              :text(65535)
 | 
				
			||||||
 | 
					#  disable           :boolean          default("0")
 | 
				
			||||||
 | 
					#  created_at        :datetime         not null
 | 
				
			||||||
 | 
					#  updated_at        :datetime         not null
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Indexes
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  index_action_pipelines_on_project_id  (project_id)
 | 
				
			||||||
 | 
					#  index_action_pipelines_on_user_id     (user_id)
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Action::Pipeline < ApplicationRecord
 | 
				
			||||||
 | 
					  self.table_name = 'action_pipelines'
 | 
				
			||||||
 | 
					  belongs_to :user, optional: true
 | 
				
			||||||
 | 
					  belongs_to :project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :name, presence: { message: "不能为空" }
 | 
				
			||||||
 | 
					  validates :json, length: { maximum: 65535, too_long: "不能超过65535个字符"}
 | 
				
			||||||
 | 
					  validates :yaml, length: { maximum: 65535, too_long: "不能超过65535个字符"}
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@
 | 
				
			||||||
#  name        :string(255)
 | 
					#  name        :string(255)
 | 
				
			||||||
#  description :string(255)
 | 
					#  description :string(255)
 | 
				
			||||||
#  img         :string(255)
 | 
					#  img         :string(255)
 | 
				
			||||||
#  sort_no     :string(255)      default("0")
 | 
					#  sort_no     :integer          default("0")
 | 
				
			||||||
#  json        :text(65535)
 | 
					#  json        :text(65535)
 | 
				
			||||||
#  yaml        :text(65535)
 | 
					#  yaml        :text(65535)
 | 
				
			||||||
#  created_at  :datetime         not null
 | 
					#  created_at  :datetime         not null
 | 
				
			||||||
| 
						 | 
					@ -17,4 +17,7 @@ class Action::Template < ApplicationRecord
 | 
				
			||||||
  self.table_name = 'action_templates'
 | 
					  self.table_name = 'action_templates'
 | 
				
			||||||
  default_scope { order(sort_no: :asc) }
 | 
					  default_scope { order(sort_no: :asc) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :name, presence: { message: "不能为空" }
 | 
				
			||||||
 | 
					  validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,8 +26,6 @@
 | 
				
			||||||
#  cloud_url                 :string(255)      default("")
 | 
					#  cloud_url                 :string(255)      default("")
 | 
				
			||||||
#  course_second_category_id :integer          default("0")
 | 
					#  course_second_category_id :integer          default("0")
 | 
				
			||||||
#  delay_publish             :boolean          default("0")
 | 
					#  delay_publish             :boolean          default("0")
 | 
				
			||||||
#  memo_image                :boolean          default("0")
 | 
					 | 
				
			||||||
#  extra_type                :integer          default("0")
 | 
					 | 
				
			||||||
#  uuid                      :string(255)
 | 
					#  uuid                      :string(255)
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
| 
						 | 
					@ -38,12 +36,14 @@
 | 
				
			||||||
#  index_attachments_on_created_on                       (created_on)
 | 
					#  index_attachments_on_created_on                       (created_on)
 | 
				
			||||||
#  index_attachments_on_is_public                        (is_public)
 | 
					#  index_attachments_on_is_public                        (is_public)
 | 
				
			||||||
#  index_attachments_on_quotes                           (quotes)
 | 
					#  index_attachments_on_quotes                           (quotes)
 | 
				
			||||||
 | 
					#  index_attachments_on_uuid                             (uuid)
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Attachment < ApplicationRecord
 | 
					class Attachment < ApplicationRecord
 | 
				
			||||||
  include BaseModel
 | 
					  include BaseModel
 | 
				
			||||||
  include Publicable
 | 
					  include Publicable
 | 
				
			||||||
| 
						 | 
					@ -76,6 +76,42 @@ class Attachment < ApplicationRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z)
 | 
					  DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def self.build_from_remote_url(user, name, url, container=nil)
 | 
				
			||||||
 | 
					    ext = name.split('.')[-1]
 | 
				
			||||||
 | 
					    tmp_path = "#{Rails.root}/#{name}"
 | 
				
			||||||
 | 
					    uri = URI(url)
 | 
				
			||||||
 | 
					    size = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
 | 
				
			||||||
 | 
					      response = http.get(uri.path)
 | 
				
			||||||
 | 
					      File.open(tmp_path, 'wb') do |file| 
 | 
				
			||||||
 | 
					        file.write(response.body)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    digest = "#{Digest::MD5.file(tmp_path).hexdigest}_#{(Time.now.to_f * 1000).to_i}.#{ext}"
 | 
				
			||||||
 | 
					    month_folder = "#{Time.now.year}/#{Time.now.month.to_s.rjust(2, '0')}"
 | 
				
			||||||
 | 
					    save_path = "#{Rails.root}#{EduSetting.get("attachment_folder")}#{month_folder}"
 | 
				
			||||||
 | 
					    unless Dir.exists?(save_path)
 | 
				
			||||||
 | 
					      FileUtils.mkdir_p(save_path) ##不成功这里会抛异常
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    path = File.join(save_path, digest)
 | 
				
			||||||
 | 
					    FileUtils.mv(tmp_path, path)
 | 
				
			||||||
 | 
					    attachment = Attachment.new 
 | 
				
			||||||
 | 
					    attachment.filename = name 
 | 
				
			||||||
 | 
					    attachment.disk_filename = path[save_path.size+1, path.size]
 | 
				
			||||||
 | 
					    attachment.filesize = size 
 | 
				
			||||||
 | 
					    attachment.content_type = 'application/octet-stream'
 | 
				
			||||||
 | 
					    attachment.digest = digest.split('.')[0]
 | 
				
			||||||
 | 
					    attachment.author_id = user.id 
 | 
				
			||||||
 | 
					    attachment.disk_directory = month_folder
 | 
				
			||||||
 | 
					    attachment.cloud_url = url 
 | 
				
			||||||
 | 
					    attachment.uuid = SecureRandom.uuid 
 | 
				
			||||||
 | 
					    attachment.container = container
 | 
				
			||||||
 | 
					    attachment.save! 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return attachment
 | 
				
			||||||
 | 
					  rescue 
 | 
				
			||||||
 | 
					    return nil
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def diskfile
 | 
					  def diskfile
 | 
				
			||||||
    File.join(File.join(Rails.root, "files"), disk_directory.to_s, disk_filename.to_s)
 | 
					    File.join(File.join(Rails.root, "files"), disk_directory.to_s, disk_filename.to_s)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,23 @@
 | 
				
			||||||
 | 
					# == Schema Information
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Table name: gitlink_competition_applies
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  id                     :integer          not null, primary key
 | 
				
			||||||
 | 
					#  competition_id         :integer
 | 
				
			||||||
 | 
					#  competition_identifier :string(255)
 | 
				
			||||||
 | 
					#  team_id                :integer
 | 
				
			||||||
 | 
					#  team_name              :string(255)
 | 
				
			||||||
 | 
					#  school_name            :string(255)
 | 
				
			||||||
 | 
					#  educoder_login         :string(255)
 | 
				
			||||||
 | 
					#  nickname               :string(255)
 | 
				
			||||||
 | 
					#  phone                  :string(255)
 | 
				
			||||||
 | 
					#  email                  :string(255)
 | 
				
			||||||
 | 
					#  identity               :string(255)
 | 
				
			||||||
 | 
					#  role                   :string(255)
 | 
				
			||||||
 | 
					#  created_at             :datetime         not null
 | 
				
			||||||
 | 
					#  updated_at             :datetime         not null
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 # == Schema Information
 | 
					 # == Schema Information
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Table name: gitlink_competition_applies
 | 
					# Table name: gitlink_competition_applies
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,16 +63,17 @@ class Issue < ApplicationRecord
 | 
				
			||||||
  has_many :project_trends, as: :trend, dependent: :destroy
 | 
					  has_many :project_trends, as: :trend, dependent: :destroy
 | 
				
			||||||
  has_one :pull_request
 | 
					  has_one :pull_request
 | 
				
			||||||
  # belongs_to :issue_tag,optional: true
 | 
					  # belongs_to :issue_tag,optional: true
 | 
				
			||||||
  belongs_to :priority, :class_name => 'IssuePriority', foreign_key: :priority_id,optional: true
 | 
					  belongs_to :priority, class_name: 'IssuePriority', foreign_key: :priority_id,optional: true
 | 
				
			||||||
  belongs_to :version, foreign_key: :fixed_version_id,optional: true, counter_cache: true
 | 
					  belongs_to :version, foreign_key: :fixed_version_id,optional: true, counter_cache: true
 | 
				
			||||||
  belongs_to :user,optional: true, foreign_key: :author_id
 | 
					  belongs_to :user,optional: true, foreign_key: :author_id
 | 
				
			||||||
  belongs_to :issue_status, foreign_key: :status_id,optional: true
 | 
					  belongs_to :issue_status, foreign_key: :status_id,optional: true
 | 
				
			||||||
 | 
					  belongs_to :parent_issue, class_name: 'Issue', optional: true, foreign_key: :root_id, counter_cache: :child_count
 | 
				
			||||||
  has_many :commit_issues
 | 
					  has_many :commit_issues
 | 
				
			||||||
  has_many :attachments, as: :container, dependent: :destroy
 | 
					  has_many :attachments, as: :container, dependent: :destroy
 | 
				
			||||||
  # has_many :memos
 | 
					  # has_many :memos
 | 
				
			||||||
  has_many :journals, :as => :journalized, :dependent => :destroy
 | 
					  has_many :journals, as: :journalized, dependent: :destroy
 | 
				
			||||||
  has_many :journal_details, through: :journals
 | 
					  has_many :journal_details, through: :journals
 | 
				
			||||||
  has_many :claims, :dependent => :destroy
 | 
					  has_many :claims, dependent: :destroy
 | 
				
			||||||
  has_many :claim_users, through: :claims, source: :user
 | 
					  has_many :claim_users, through: :claims, source: :user
 | 
				
			||||||
  has_many :issue_tags_relates, dependent: :destroy
 | 
					  has_many :issue_tags_relates, dependent: :destroy
 | 
				
			||||||
  has_many :issue_tags, through: :issue_tags_relates
 | 
					  has_many :issue_tags, through: :issue_tags_relates
 | 
				
			||||||
| 
						 | 
					@ -82,25 +83,67 @@ class Issue < ApplicationRecord
 | 
				
			||||||
  has_many :assigners, through: :issue_assigners
 | 
					  has_many :assigners, through: :issue_assigners
 | 
				
			||||||
  has_many :issue_participants, dependent: :destroy
 | 
					  has_many :issue_participants, dependent: :destroy
 | 
				
			||||||
  has_many :participants, through: :issue_participants
 | 
					  has_many :participants, through: :issue_participants
 | 
				
			||||||
  has_many :show_participants, -> {joins(:issue_participants).where.not(issue_participants: {participant_type: "atme"}).distinct}, through: :issue_participants, source: :participant
 | 
					  has_many :children_issues, class_name: 'Issue', foreign_key: :root_id, dependent: :destroy
 | 
				
			||||||
 | 
					  has_many :show_participants, -> {joins(:issue_participants).where.not(issue_participants: {participant_type: 'atme'}).distinct}, through: :issue_participants, source: :participant
 | 
				
			||||||
  has_many :show_assigners, -> {joins(:issue_assigners).distinct}, through: :issue_assigners, source: :assigner
 | 
					  has_many :show_assigners, -> {joins(:issue_assigners).distinct}, through: :issue_assigners, source: :assigner
 | 
				
			||||||
  has_many :show_issue_tags, -> {joins(:issue_tags_relates).distinct}, through: :issue_tags_relates, source: :issue_tag
 | 
					  has_many :show_issue_tags, -> {joins(:issue_tags_relates).distinct}, through: :issue_tags_relates, source: :issue_tag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  has_many :comment_journals, -> {where.not(notes: nil)}, class_name: "Journal", :as => :journalized
 | 
					  has_many :comment_journals, -> {where.not(notes: nil)}, class_name: 'Journal', as: :journalized
 | 
				
			||||||
  has_many :operate_journals, -> {where(notes: nil)}, class_name: "Journal", :as => :journalized
 | 
					  has_many :operate_journals, -> {where(notes: nil)}, class_name: 'Journal', as: :journalized
 | 
				
			||||||
  has_many :pull_attached_issues, dependent: :destroy 
 | 
					  has_many :pull_attached_issues, dependent: :destroy
 | 
				
			||||||
  has_many :attach_pull_requests, through: :pull_attached_issues, source: :pull_request
 | 
					  has_many :attach_pull_requests, through: :pull_attached_issues, source: :pull_request
 | 
				
			||||||
 | 
					  # PM 关联工作项目
 | 
				
			||||||
 | 
					  has_many :pm_links, as: :linkable, dependent: :destroy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  belongs_to :changer, class_name: 'User', foreign_key: :changer_id, optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  scope :issue_includes, ->{includes(:user)}
 | 
					  scope :issue_includes, ->{includes(:user)}
 | 
				
			||||||
  scope :issue_many_includes, ->{includes(journals: :user)}
 | 
					  scope :issue_many_includes, ->{includes(journals: :user)}
 | 
				
			||||||
  scope :issue_issue, ->{where(issue_classify: [nil,"issue"])}
 | 
					  scope :issue_issue, ->{where(issue_classify: [nil, 'issue'])}
 | 
				
			||||||
  scope :issue_pull_request, ->{where(issue_classify: "pull_request")}
 | 
					  scope :issue_pull_request, ->{where(issue_classify: 'pull_request')}
 | 
				
			||||||
  scope :issue_index_includes, ->{includes(:tracker, :priority, :version, :issue_status, :journals,:issue_tags,user: :user_extension)}
 | 
					  scope :issue_index_includes, ->{includes(:tracker, :priority, :version, :issue_status, :journals,:issue_tags,user: :user_extension)}
 | 
				
			||||||
  scope :closed, ->{where(status_id: 5)}
 | 
					  scope :closed, ->{where(status_id: 5)}
 | 
				
			||||||
  scope :opened, ->{where.not(status_id: 5)}
 | 
					  scope :opened, ->{where.not(status_id: 5)}
 | 
				
			||||||
  after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic
 | 
					  after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic
 | 
				
			||||||
  after_save :incre_or_decre_closed_issues_count, :change_versions_count, :send_update_message_to_notice_system, :associate_attachment_container
 | 
					  before_save :check_pm_and_update_due_date
 | 
				
			||||||
  after_destroy :update_closed_issues_count_in_project!, :decre_project_common, :decre_user_statistic, :decre_platform_statistic
 | 
					  after_save :incre_or_decre_closed_issues_count, :change_versions_count, :send_update_message_to_notice_system, :associate_attachment_container, :generate_uuid
 | 
				
			||||||
 | 
					  after_destroy :update_closed_issues_count_in_project!, :decre_project_common, :decre_user_statistic, :decre_platform_statistic, :destroy_be_pm_links
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def pm_issue_type_string
 | 
				
			||||||
 | 
					    case pm_issue_type
 | 
				
			||||||
 | 
					    when 1
 | 
				
			||||||
 | 
					      "requirement"
 | 
				
			||||||
 | 
					    when 2
 | 
				
			||||||
 | 
					      "task"
 | 
				
			||||||
 | 
					    when 3
 | 
				
			||||||
 | 
					      "bug"
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      "issue"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy_be_pm_links
 | 
				
			||||||
 | 
					    PmLink.where(be_linkable_type:"Issue",be_linkable_id:self.id).map(&:destroy)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def check_pm_and_update_due_date
 | 
				
			||||||
 | 
					    if pm_project_id.present? && pm_issue_type.present? && status_id_changed?
 | 
				
			||||||
 | 
					      status_ids = case pm_issue_type
 | 
				
			||||||
 | 
					                   when 1
 | 
				
			||||||
 | 
					                     [3,5]
 | 
				
			||||||
 | 
					                   when 2
 | 
				
			||||||
 | 
					                     [3,5]
 | 
				
			||||||
 | 
					                   when 3
 | 
				
			||||||
 | 
					                     [5]
 | 
				
			||||||
 | 
					                   else
 | 
				
			||||||
 | 
					                     []
 | 
				
			||||||
 | 
					                   end
 | 
				
			||||||
 | 
					      if status_ids.include? self.status_id
 | 
				
			||||||
 | 
					        self.due_date = self.due_date || Time.current
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def is_issuely_issue?
 | 
					  def is_issuely_issue?
 | 
				
			||||||
    self.issue_classify.nil? || self.issue_classify == 'issue'
 | 
					    self.issue_classify.nil? || self.issue_classify == 'issue'
 | 
				
			||||||
| 
						 | 
					@ -118,27 +161,34 @@ class Issue < ApplicationRecord
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def incre_project_common
 | 
					  def incre_project_common
 | 
				
			||||||
    CacheAsyncSetJob.perform_later("project_common_service", {issues: 1}, self.project_id) if is_issuely_issue?
 | 
					    CacheAsyncSetJob.perform_later('project_common_service', {issues: 1}, self.project_id) if is_issuely_issue?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def decre_project_common
 | 
					  def decre_project_common
 | 
				
			||||||
    CacheAsyncSetJob.perform_later("project_common_service", {issues: -1}, self.project_id) if is_issuely_issue?
 | 
					    CacheAsyncSetJob.perform_later('project_common_service', {issues: -1}, self.project_id) if is_issuely_issue?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def incre_user_statistic 
 | 
					  def incre_user_statistic 
 | 
				
			||||||
    CacheAsyncSetJob.perform_later("user_statistic_service", {issue_count: 1}, self.author_id) if is_issuely_issue?
 | 
					    CacheAsyncSetJob.perform_later('user_statistic_service', {issue_count: 1}, self.author_id) if is_issuely_issue?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def decre_user_statistic
 | 
					  def decre_user_statistic
 | 
				
			||||||
    CacheAsyncSetJob.perform_later("user_statistic_service", {issue_count: -1}, self.author_id) if is_issuely_issue?
 | 
					    CacheAsyncSetJob.perform_later('user_statistic_service', {issue_count: -1}, self.author_id) if is_issuely_issue?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def refresh_root_issue_count
 | 
				
			||||||
 | 
					    return if root_id.nil? || root_id.zero?
 | 
				
			||||||
 | 
					    root_issue = Issue.find_by(id: root_id)
 | 
				
			||||||
 | 
					    root_count = Issue.where(root_id: root_id).count
 | 
				
			||||||
 | 
					    root_issue.update(child_count: root_count)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def incre_platform_statistic
 | 
					  def incre_platform_statistic
 | 
				
			||||||
    CacheAsyncSetJob.perform_later("platform_statistic_service", {issue_count: 1}) if is_issuely_issue?
 | 
					    CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: 1}) if is_issuely_issue?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def decre_platform_statistic
 | 
					  def decre_platform_statistic
 | 
				
			||||||
    CacheAsyncSetJob.perform_later("platform_statistic_service", {issue_count: -1}) if is_issuely_issue?
 | 
					    CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: -1}) if is_issuely_issue?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def get_assign_user
 | 
					  def get_assign_user
 | 
				
			||||||
| 
						 | 
					@ -147,20 +197,20 @@ class Issue < ApplicationRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def create_journal_detail(change_files, issue_files, issue_file_ids, user_id)
 | 
					  def create_journal_detail(change_files, issue_files, issue_file_ids, user_id)
 | 
				
			||||||
    journal_params = {
 | 
					    journal_params = {
 | 
				
			||||||
      journalized_id: self.id, journalized_type: "Issue", user_id: user_id
 | 
					      journalized_id: self.id, journalized_type: 'Issue', user_id: user_id
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    journal = Journal.new journal_params
 | 
					    journal = Journal.new journal_params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if journal.save
 | 
					    if journal.save
 | 
				
			||||||
      if change_files
 | 
					      if change_files
 | 
				
			||||||
        old_attachment_names = self.attachments.select(:filename,:id).where(id: issue_file_ids).pluck(:filename).join(",")
 | 
					        old_attachment_names = self.attachments.select(:filename,:id).where(id: issue_file_ids).pluck(:filename).join(',')
 | 
				
			||||||
        new_attachment_name = self.attachments.select(:filename,:id).where(id: issue_files).pluck(:filename).join(",")
 | 
					        new_attachment_name = self.attachments.select(:filename,:id).where(id: issue_files).pluck(:filename).join(',')
 | 
				
			||||||
        journal.journal_details.create(property: "attachment", prop_key: "#{issue_files.size}", old_value: old_attachment_names, value: new_attachment_name)
 | 
					        journal.journal_details.create(property: 'attachment', prop_key: "#{issue_files.size}", old_value: old_attachment_names, value: new_attachment_name)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
      change_values = %w(subject description is_private assigned_to_id tracker_id status_id priority_id fixed_version_id start_date due_date estimated_hours done_ratio issue_tags_value issue_type token branch_name)
 | 
					      change_values = %w(subject description is_private assigned_to_id tracker_id status_id priority_id fixed_version_id start_date due_date estimated_hours done_ratio issue_tags_value issue_type token branch_name)
 | 
				
			||||||
      change_values.each do |at|
 | 
					      change_values.each do |at|
 | 
				
			||||||
        if self.send("saved_change_to_#{at}?")
 | 
					        if self.send("saved_change_to_#{at}?")
 | 
				
			||||||
          journal.journal_details.create(property: "attr", prop_key: "#{at}", old_value: self.send("#{at}_before_last_save"), value: self.send(at))
 | 
					          journal.journal_details.create(property: 'attr', prop_key: "#{at}", old_value: self.send("#{at}_before_last_save"), value: self.send(at))
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -168,11 +218,11 @@ class Issue < ApplicationRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def custom_journal_detail(prop_key, old_value, value, user_id)
 | 
					  def custom_journal_detail(prop_key, old_value, value, user_id)
 | 
				
			||||||
    journal_params = {
 | 
					    journal_params = {
 | 
				
			||||||
      journalized_id: self.id, journalized_type: "Issue", user_id: user_id
 | 
					      journalized_id: self.id, journalized_type: 'Issue', user_id: user_id
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    journal = Journal.new journal_params
 | 
					    journal = Journal.new journal_params
 | 
				
			||||||
    if journal.save
 | 
					    if journal.save
 | 
				
			||||||
      journal.journal_details.create(property: "attr", prop_key: prop_key, old_value: old_value, value: value)
 | 
					      journal.journal_details.create(property: 'attr', prop_key: prop_key, old_value: old_value, value: value)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -192,20 +242,29 @@ class Issue < ApplicationRecord
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def generate_uuid
 | 
				
			||||||
 | 
					    # return if pm_project_id.nil?
 | 
				
			||||||
 | 
					    # attachments.map(&:generate_uuid)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def is_collaborators?
 | 
					  def is_collaborators?
 | 
				
			||||||
    self.assigned_to_id.present? ? self.project.member?(self.assigned_to_id) : false
 | 
					    if self.assigned_to_id.present? && self.project.present?
 | 
				
			||||||
 | 
					      self.project.member?(self.assigned_to_id)
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def get_issue_tags_name
 | 
					  def get_issue_tags_name
 | 
				
			||||||
    if issue_tags.present?
 | 
					    if issue_tags.present?
 | 
				
			||||||
      issue_tags.select(:name).uniq.pluck(:name).join(",")
 | 
					      issue_tags.select(:name).uniq.pluck(:name).join(',')
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      nil
 | 
					      nil
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def only_reply_journals
 | 
					  def only_reply_journals
 | 
				
			||||||
    journals.where.not(notes: [nil, ""]).journal_includes.limit(2)
 | 
					    journals.where.not(notes: [nil, '']).journal_includes.limit(2)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def change_versions_count
 | 
					  def change_versions_count
 | 
				
			||||||
| 
						 | 
					@ -235,7 +294,7 @@ class Issue < ApplicationRecord
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def update_closed_issues_count_in_project!
 | 
					  def update_closed_issues_count_in_project!
 | 
				
			||||||
    self.project.decrement!(:closed_issues_count) if self.status_id == 5
 | 
					    self.project.decrement!(:closed_issues_count) if self.status_id == 5 && self.project.present?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def send_update_message_to_notice_system
 | 
					  def send_update_message_to_notice_system
 | 
				
			||||||
| 
						 | 
					@ -267,8 +326,8 @@ class Issue < ApplicationRecord
 | 
				
			||||||
  def to_builder
 | 
					  def to_builder
 | 
				
			||||||
    Jbuilder.new do |issue|
 | 
					    Jbuilder.new do |issue|
 | 
				
			||||||
      issue.(self, :id, :project_issues_index, :subject, :description, :branch_name, :start_date, :due_date)
 | 
					      issue.(self, :id, :project_issues_index, :subject, :description, :branch_name, :start_date, :due_date)
 | 
				
			||||||
      issue.created_at self.created_on.strftime("%Y-%m-%d %H:%M")
 | 
					      issue.created_at self.created_on.strftime('%Y-%m-%d %H:%M')
 | 
				
			||||||
      issue.updated_at self.updated_on.strftime("%Y-%m-%d %H:%M")
 | 
					      issue.updated_at self.updated_on.strftime('%Y-%m-%d %H:%M')
 | 
				
			||||||
      issue.tags self.show_issue_tags.map{|t| JSON.parse(t.to_builder.target!)}
 | 
					      issue.tags self.show_issue_tags.map{|t| JSON.parse(t.to_builder.target!)}
 | 
				
			||||||
      issue.status self.issue_status.to_builder 
 | 
					      issue.status self.issue_status.to_builder 
 | 
				
			||||||
      if self.priority.present? 
 | 
					      if self.priority.present? 
 | 
				
			||||||
| 
						 | 
					@ -290,4 +349,12 @@ class Issue < ApplicationRecord
 | 
				
			||||||
    end 
 | 
					    end 
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def self.full_children_issues(issue, issues = [])
 | 
				
			||||||
 | 
					    issue.children_issues.each do |i| 
 | 
				
			||||||
 | 
					      issues << i
 | 
				
			||||||
 | 
					      full_children_issues(i, issues)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    issues
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,4 +38,21 @@ class IssuePriority < ApplicationRecord
 | 
				
			||||||
      priority.(self, :id, :name)
 | 
					      priority.(self, :id, :name)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def pm_color
 | 
				
			||||||
 | 
					    case name
 | 
				
			||||||
 | 
					    when '低'
 | 
				
			||||||
 | 
					      '#13b33e'
 | 
				
			||||||
 | 
					    when '正常'
 | 
				
			||||||
 | 
					      '#0d5ef8'
 | 
				
			||||||
 | 
					    when '高'
 | 
				
			||||||
 | 
					      '#ff6f00'
 | 
				
			||||||
 | 
					    when '紧急'
 | 
				
			||||||
 | 
					      '#d20f0f'
 | 
				
			||||||
 | 
					    # when '立刻'
 | 
				
			||||||
 | 
					    #   '#f5222d'
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      '#13b33e'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,9 +45,28 @@ class IssueStatus < ApplicationRecord
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def to_builder 
 | 
					  def to_builder
 | 
				
			||||||
    Jbuilder.new do |status|
 | 
					    Jbuilder.new do |status|
 | 
				
			||||||
      status.(self, :id, :name)
 | 
					      status.(self, :id, :name)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def pm_color
 | 
				
			||||||
 | 
					    case name
 | 
				
			||||||
 | 
					    when '新增'
 | 
				
			||||||
 | 
					      '#ff6f00'
 | 
				
			||||||
 | 
					    when '正在解决'
 | 
				
			||||||
 | 
					      '#0d5ef8'
 | 
				
			||||||
 | 
					    when '已解决'
 | 
				
			||||||
 | 
					      '#13b33e'
 | 
				
			||||||
 | 
					    when '关闭'
 | 
				
			||||||
 | 
					      '#b1aaa5'
 | 
				
			||||||
 | 
					    # when '反馈'
 | 
				
			||||||
 | 
					    #   '#13c2c2'
 | 
				
			||||||
 | 
					    when '拒绝'
 | 
				
			||||||
 | 
					      '#ff0000'
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      '#ff6f00'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,9 +15,11 @@
 | 
				
			||||||
#  gitea_url           :string(255)
 | 
					#  gitea_url           :string(255)
 | 
				
			||||||
#  pull_requests_count :integer          default("0")
 | 
					#  pull_requests_count :integer          default("0")
 | 
				
			||||||
#  pm_project_id       :integer
 | 
					#  pm_project_id       :integer
 | 
				
			||||||
 | 
					#  organization_id     :integer
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					#  index_issue_tags_on_organization_id                  (organization_id)
 | 
				
			||||||
#  index_issue_tags_on_user_id_and_name_and_project_id  (user_id,name,project_id)
 | 
					#  index_issue_tags_on_user_id_and_name_and_project_id  (user_id,name,project_id)
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,8 +31,15 @@ class IssueTag < ApplicationRecord
 | 
				
			||||||
  has_many :pull_request_issues, -> {where(issue_classify: "pull_request")}, source: :issue, through: :issue_tags_relates
 | 
					  has_many :pull_request_issues, -> {where(issue_classify: "pull_request")}, source: :issue, through: :issue_tags_relates
 | 
				
			||||||
  belongs_to :project, optional: true, counter_cache: true
 | 
					  belongs_to :project, optional: true, counter_cache: true
 | 
				
			||||||
  belongs_to :user, optional: true
 | 
					  belongs_to :user, optional: true
 | 
				
			||||||
 | 
					  belongs_to :organization, optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :name, uniqueness: {scope: :project_id, message: "已存在" }
 | 
					  scope :pm_able, -> {where(project_id: 0)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :name, uniqueness: {scope: :project_id, message: "已存在" }, if: :pm_project?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def pm_project?
 | 
				
			||||||
 | 
					    !project_id.zero?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def self.init_data(project_id)
 | 
					  def self.init_data(project_id)
 | 
				
			||||||
    data = init_issue_tag_data
 | 
					    data = init_issue_tag_data
 | 
				
			||||||
| 
						 | 
					@ -41,6 +50,24 @@ class IssueTag < ApplicationRecord
 | 
				
			||||||
    $redis_cache.hset("project_init_issue_tags", project_id, 1)
 | 
					    $redis_cache.hset("project_init_issue_tags", project_id, 1)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def self.pm_init_data(pm_project_id)
 | 
				
			||||||
 | 
					    data = init_issue_tag_data
 | 
				
			||||||
 | 
					    data.each do |item|
 | 
				
			||||||
 | 
					      next if IssueTag.exists?(pm_project_id: pm_project_id, project_id: 0, name: item[0])
 | 
				
			||||||
 | 
					      IssueTag.create!(pm_project_id: pm_project_id, project_id: 0, name: item[0], description: item[1], color: item[2])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    $redis_cache.hset("pm_project_init_issue_tags", pm_project_id, 1)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def self.pm_org_init_data(organization_id)
 | 
				
			||||||
 | 
					    data = init_issue_tag_data
 | 
				
			||||||
 | 
					    data.each do |item|
 | 
				
			||||||
 | 
					      next if IssueTag.exists?(organization_id: organization_id, project_id: 0, name: item[0])
 | 
				
			||||||
 | 
					      IssueTag.create!(organization_id: organization_id, project_id: 0, name: item[0], description: item[1], color: item[2])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    $redis_cache.hset("pm_org_init_issue_tags", organization_id, 1)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def reset_counter_field 
 | 
					  def reset_counter_field 
 | 
				
			||||||
    self.update_column(:issues_count, issue_issues.size)
 | 
					    self.update_column(:issues_count, issue_issues.size)
 | 
				
			||||||
    self.update_column(:pull_requests_count, pull_request_issues.size)
 | 
					    self.update_column(:pull_requests_count, pull_request_issues.size)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,15 +82,432 @@ class Journal < ApplicationRecord
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def operate_content
 | 
					  def pm_operate_category
 | 
				
			||||||
 | 
					    detail = self.journal_details.take 
 | 
				
			||||||
 | 
					    if %w(requirement task bug).include?(detail.property) && detail.prop_key.to_s == "1"
 | 
				
			||||||
 | 
					      return "issue"
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      return %w(requirement task bug attr).include?(detail.property) ? detail.prop_key : detail.property
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def pm_operate_content
 | 
				
			||||||
 | 
					    content = ""
 | 
				
			||||||
 | 
					    detail = self.journal_details.take 
 | 
				
			||||||
 | 
					    case detail.property
 | 
				
			||||||
 | 
					    when 'requirement'
 | 
				
			||||||
 | 
					        case detail.prop_key
 | 
				
			||||||
 | 
					        when 'status_id'
 | 
				
			||||||
 | 
					          old_value = IssueStatus.find_by_id(detail.old_value)&.name 
 | 
				
			||||||
 | 
					          new_value = IssueStatus.find_by_id(detail.value)&.name 
 | 
				
			||||||
 | 
					          content += "将状态"
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "设置为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          else 
 | 
				
			||||||
 | 
					            new_value = "未设置" if detail.value.blank?
 | 
				
			||||||
 | 
					            content += "由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          content.gsub!('新增', '待评审')
 | 
				
			||||||
 | 
					          content.gsub!('正在解决', '进行中')
 | 
				
			||||||
 | 
					          content.gsub!('已解决', '已完成')
 | 
				
			||||||
 | 
					          content.gsub!('关闭', '已关闭')
 | 
				
			||||||
 | 
					          content.gsub!('拒绝', '已拒绝')
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'root_id'
 | 
				
			||||||
 | 
					          old_value = "<b><#{Issue.find_by_id(detail.old_value)&.subject}></b>"
 | 
				
			||||||
 | 
					          new_value = "<b><#{Issue.find_by_id(detail.value)&.subject}></b>"
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "关联了父需求#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "取消了关联的父需求#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "将关联的父需求由#{old_value}更改为#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'leaf_issue'
 | 
				
			||||||
 | 
					          old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "新建了子需求#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "删除了关联的子需求#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "新建了子需求#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'tag_leaf_issue'
 | 
				
			||||||
 | 
					          old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "关联了子需求#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "取消了关联的子需求#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "关联了子需求#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          return "创建了需求"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    when 'task'
 | 
				
			||||||
 | 
					        case detail.prop_key
 | 
				
			||||||
 | 
					        when 'status_id'
 | 
				
			||||||
 | 
					          old_value = IssueStatus.find_by_id(detail.old_value)&.name 
 | 
				
			||||||
 | 
					          new_value = IssueStatus.find_by_id(detail.value)&.name 
 | 
				
			||||||
 | 
					          content += "将状态"
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "设置为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          else 
 | 
				
			||||||
 | 
					            new_value = "未设置" if detail.value.blank?
 | 
				
			||||||
 | 
					            content += "由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          content.gsub!('新增', '待处理')
 | 
				
			||||||
 | 
					          content.gsub!('正在解决', '进行中')
 | 
				
			||||||
 | 
					          content.gsub!('已解决', '已完成')
 | 
				
			||||||
 | 
					          content.gsub!('关闭', '已关闭')
 | 
				
			||||||
 | 
					          content.gsub!('拒绝', '已拒绝')
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'root_id'
 | 
				
			||||||
 | 
					          old_value = "<b><#{Issue.find_by_id(detail.old_value)&.subject}></b>" 
 | 
				
			||||||
 | 
					          new_value = "<b><#{Issue.find_by_id(detail.value)&.subject}></b>" 
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "关联了父任务#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "取消了关联的父任务#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "将关联的父任务由#{old_value}>更改为#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'leaf_issue'
 | 
				
			||||||
 | 
					          old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "新建了子任务#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "删除了关联的子任务#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "新建了子任务#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'tag_leaf_issue'
 | 
				
			||||||
 | 
					          old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "关联了子任务#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "取消了关联的子任务#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "关联了子任务#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          return "创建了任务"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    when 'bug'
 | 
				
			||||||
 | 
					        case detail.prop_key
 | 
				
			||||||
 | 
					        when 'status_id'
 | 
				
			||||||
 | 
					          old_value = IssueStatus.find_by_id(detail.old_value)&.name 
 | 
				
			||||||
 | 
					          new_value = IssueStatus.find_by_id(detail.value)&.name 
 | 
				
			||||||
 | 
					          content += "将状态"
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "设置为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          else 
 | 
				
			||||||
 | 
					            new_value = "未设置" if detail.value.blank?
 | 
				
			||||||
 | 
					            content += "由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          content.gsub!('新增', '待修复')
 | 
				
			||||||
 | 
					          content.gsub!('正在解决', '修复中')
 | 
				
			||||||
 | 
					          content.gsub!('已解决', '已修复')
 | 
				
			||||||
 | 
					          content.gsub!('关闭', '已关闭')
 | 
				
			||||||
 | 
					          content.gsub!('拒绝', '已拒绝')
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'root_id'
 | 
				
			||||||
 | 
					          old_value = "<b><#{Issue.find_by_id(detail.old_value)&.subject}></b>" 
 | 
				
			||||||
 | 
					          new_value = "<b><#{Issue.find_by_id(detail.value)&.subject}></b>" 
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "关联了父缺陷#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "取消了关联的父缺陷#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "将关联的父缺陷由#{old_value}更改为#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'leaf_issue'
 | 
				
			||||||
 | 
					          old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "新建了子缺陷#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "删除了关联的子缺陷#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "新建了子缺陷#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        when 'tag_leaf_issue'
 | 
				
			||||||
 | 
					          old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b><#{i.subject}></b>"}.join("、")
 | 
				
			||||||
 | 
					          if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					            content += "关联了子缺陷#{new_value}"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					              content += "取消了关联的子缺陷#{old_value}"
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					              content += "关联了子缺陷#{new_value}"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          return content
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          return "创建了缺陷"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    when 'attr'
 | 
				
			||||||
 | 
					      case detail.prop_key
 | 
				
			||||||
 | 
					      when 'subject'
 | 
				
			||||||
 | 
					        return "修改了<b>标题</b>"
 | 
				
			||||||
 | 
					      when 'description'
 | 
				
			||||||
 | 
					        return "修改了<b>正文</b>"
 | 
				
			||||||
 | 
					      when 'priority_id'
 | 
				
			||||||
 | 
					        old_value = IssuePriority.find_by_id(detail.old_value)&.name 
 | 
				
			||||||
 | 
					        new_value = IssuePriority.find_by_id(detail.value)&.name
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "将优先级设置为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        else 
 | 
				
			||||||
 | 
					          new_value = "未设置" if detail.value.blank?
 | 
				
			||||||
 | 
					          content += "将优先级由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					      when 'status_id'
 | 
				
			||||||
 | 
					        old_value = IssueStatus.find_by_id(detail.old_value)&.name 
 | 
				
			||||||
 | 
					        new_value = IssueStatus.find_by_id(detail.value)&.name 
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "将状态设置为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        else 
 | 
				
			||||||
 | 
					          new_value = "未设置" if detail.value.blank?
 | 
				
			||||||
 | 
					          content += "将状态由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        case self.issue.pm_issue_type.to_i
 | 
				
			||||||
 | 
					        when 1
 | 
				
			||||||
 | 
					          content.gsub!('新增', '待评审')
 | 
				
			||||||
 | 
					          content.gsub!('正在解决', '进行中')
 | 
				
			||||||
 | 
					          content.gsub!('已解决', '已完成')
 | 
				
			||||||
 | 
					          content.gsub!('关闭', '已关闭')
 | 
				
			||||||
 | 
					          content.gsub!('拒绝', '已拒绝')
 | 
				
			||||||
 | 
					        when 2
 | 
				
			||||||
 | 
					          content.gsub!('新增', '待处理')
 | 
				
			||||||
 | 
					          content.gsub!('正在解决', '进行中')
 | 
				
			||||||
 | 
					          content.gsub!('已解决', '已完成')
 | 
				
			||||||
 | 
					          content.gsub!('关闭', '已关闭')
 | 
				
			||||||
 | 
					          content.gsub!('拒绝', '已拒绝')
 | 
				
			||||||
 | 
					        when 3
 | 
				
			||||||
 | 
					          content.gsub!('新增', '待修复')
 | 
				
			||||||
 | 
					          content.gsub!('正在解决', '修复中')
 | 
				
			||||||
 | 
					          content.gsub!('已解决', '已修复')
 | 
				
			||||||
 | 
					          content.gsub!('关闭', '已关闭')
 | 
				
			||||||
 | 
					          content.gsub!('拒绝', '已拒绝')
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					      when 'pm_issue_type'
 | 
				
			||||||
 | 
					        old_value = detail.old_value
 | 
				
			||||||
 | 
					        new_value = detail.value
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "将工作项类型设置为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        else 
 | 
				
			||||||
 | 
					          new_value = "未设置" if detail.value.blank?
 | 
				
			||||||
 | 
					          content += "将工作项类型由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        content.gsub!('1', '需求')
 | 
				
			||||||
 | 
					        content.gsub!('2', '任务')
 | 
				
			||||||
 | 
					        content.gsub!('3', '缺陷')
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					      when 'pm_sprint_id'
 | 
				
			||||||
 | 
					        old_value = detail.old_value
 | 
				
			||||||
 | 
					        new_value = detail.value
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "添加了关联迭代"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					            content += "将关联迭代更改为<b>未设置</b>"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            content += "变更了关联迭代"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					      when 'project_id'
 | 
				
			||||||
 | 
					        old_value = Project.find_by_id(detail.old_value)&.name
 | 
				
			||||||
 | 
					        new_value = Project.find_by_id(detail.value)&.name
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "添加关联代码库<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					            content += "将关联代码库更改为<b>未设置</b>"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            content += "将关联代码库由<b>#{old_value}</b>改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					      when 'branch_name'
 | 
				
			||||||
 | 
					        old_value = detail.old_value 
 | 
				
			||||||
 | 
					        new_value = detail.value
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "添加关联分支<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					            content += "将关联分支更改为<b>未设置</b>"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            content += "将关联分支由<b>#{old_value}</b>改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when 'start_date'
 | 
				
			||||||
 | 
					        old_value = detail.old_value 
 | 
				
			||||||
 | 
					        new_value = detail.value
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "添加开始时间<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					            content += "将开始时间更改为<b>未设置</b>"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            content += "将开始时间由<b>#{old_value}</b>改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      when 'due_date'
 | 
				
			||||||
 | 
					        old_value = detail.old_value 
 | 
				
			||||||
 | 
					        new_value = detail.value
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "添加结束时间<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					            content += "将结束时间更改为<b>未设置</b>"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            content += "将结束时间由<b>#{old_value}</b>改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return content
 | 
				
			||||||
 | 
					      when 'time_scale'
 | 
				
			||||||
 | 
					        old_value = detail.old_value 
 | 
				
			||||||
 | 
					        new_value = detail.value
 | 
				
			||||||
 | 
					        if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					          content += "添加预估工时<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					            content += "将预估工时更改为<b>未设置</b>"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            content += "将预估工时由<b>#{old_value}</b>改为<b>#{new_value}</b>"
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return content        
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    when 'attachment'
 | 
				
			||||||
 | 
					      old_value = detail.old_value.to_s
 | 
				
			||||||
 | 
					      new_value = detail.value.to_s
 | 
				
			||||||
 | 
					      if detail.old_value.nil? || detail.old_value.blank?
 | 
				
			||||||
 | 
					        content += "上传了附件"
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					          content += "删除了附件"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          content += "上传了附件"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      return content 
 | 
				
			||||||
 | 
					    when 'assigner'
 | 
				
			||||||
 | 
					      old_value = User.where(id: detail.old_value.split(",")).map{|u| "<b>#{u.real_name}</b>"}.join("、")
 | 
				
			||||||
 | 
					      new_value = User.where(id: detail.value.split(",")).map{|u| "<b>#{u.real_name}</b>"}.join("、")
 | 
				
			||||||
 | 
					      if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					        content += "添加负责人#{new_value}"
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					          content += "将负责人更改为<b>未设置</b>"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          content += "将负责人由#{old_value}更改为#{new_value}"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      return content
 | 
				
			||||||
 | 
					    when 'issue_tag'
 | 
				
			||||||
 | 
					      old_value = IssueTag.where(id: detail.old_value.split(",")).map{|t| "<b>#{t.name}</b>"}.join("、")
 | 
				
			||||||
 | 
					      new_value = IssueTag.where(id: detail.value.split(",")).map{|t| "<b>#{t.name}</b>"}.join("、")
 | 
				
			||||||
 | 
					      if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					        content += "添加标记#{new_value}"
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					          content += "将标记更改为<b>未设置</b>"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          content += "将标记由#{old_value}更改为#{new_value}"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      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("、")
 | 
				
			||||||
 | 
					      if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					        content += "新建了关联的工作项#{new_value}"
 | 
				
			||||||
 | 
					      else 
 | 
				
			||||||
 | 
					        if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					          content += "删除了关联的工作项#{old_value}"
 | 
				
			||||||
 | 
					        else 
 | 
				
			||||||
 | 
					          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("、")
 | 
				
			||||||
 | 
					      if detail.old_value.nil? || detail.old_value.blank? 
 | 
				
			||||||
 | 
					        content += "关联了工作项#{new_value}"
 | 
				
			||||||
 | 
					      else 
 | 
				
			||||||
 | 
					        if detail.value.nil? || detail.value.blank? 
 | 
				
			||||||
 | 
					          content += "取消了关联的工作项#{old_value}"
 | 
				
			||||||
 | 
					        else 
 | 
				
			||||||
 | 
					          content += "关联了工作项#{new_value}"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      content.gsub!('1', "需求")
 | 
				
			||||||
 | 
					      content.gsub!('2', "任务")
 | 
				
			||||||
 | 
					      content.gsub!('3', "缺陷")
 | 
				
			||||||
 | 
					      return content
 | 
				
			||||||
 | 
					    when 'issue'
 | 
				
			||||||
 | 
					      issue = self.issue
 | 
				
			||||||
 | 
					      case issue.pm_issue_type
 | 
				
			||||||
 | 
					      when 1
 | 
				
			||||||
 | 
					        return "创建了需求"
 | 
				
			||||||
 | 
					      when 2 
 | 
				
			||||||
 | 
					        return "创建了任务"
 | 
				
			||||||
 | 
					      when 3
 | 
				
			||||||
 | 
					        return "创建了缺陷"
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def operate_content 
 | 
				
			||||||
    content = ""
 | 
					    content = ""
 | 
				
			||||||
    detail = self.journal_details.take
 | 
					    detail = self.journal_details.take
 | 
				
			||||||
    case detail.property
 | 
					    case detail.property
 | 
				
			||||||
    when 'issue'
 | 
					    when 'issue'
 | 
				
			||||||
      return "创建了<b>疑修</b>"
 | 
					      return "创建了<b>疑修</b>"
 | 
				
			||||||
    when 'attachment'
 | 
					    when 'attachment'
 | 
				
			||||||
      old_value = Attachment.where("id in (?) or uuid in (?)", detail.old_value.to_s.split(","), detail.old_value.to_s.split(",")).pluck(:filename).join("、")
 | 
					      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("id in (?) or uuid in (?)", detail.value.to_s.split(","), detail.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("、")
 | 
				
			||||||
      if old_value.nil? || old_value.blank?
 | 
					      if old_value.nil? || old_value.blank?
 | 
				
			||||||
        content += "添加了<b>#{new_value}</b>附件"
 | 
					        content += "添加了<b>#{new_value}</b>附件"
 | 
				
			||||||
      else 
 | 
					      else 
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,6 @@
 | 
				
			||||||
#  course_group_id     :integer          default("0")
 | 
					#  course_group_id     :integer          default("0")
 | 
				
			||||||
#  is_collect          :integer          default("1")
 | 
					#  is_collect          :integer          default("1")
 | 
				
			||||||
#  graduation_group_id :integer          default("0")
 | 
					#  graduation_group_id :integer          default("0")
 | 
				
			||||||
#  is_apply_signature  :boolean          default("0")
 | 
					 | 
				
			||||||
#  team_user_id        :integer
 | 
					#  team_user_id        :integer
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,6 @@
 | 
				
			||||||
#  id              :integer          not null, primary key
 | 
					#  id              :integer          not null, primary key
 | 
				
			||||||
#  user_id         :integer
 | 
					#  user_id         :integer
 | 
				
			||||||
#  organization_id :integer
 | 
					#  organization_id :integer
 | 
				
			||||||
#  is_creator      :boolean          default("0")
 | 
					 | 
				
			||||||
#  created_at      :datetime         not null
 | 
					#  created_at      :datetime         not null
 | 
				
			||||||
#  updated_at      :datetime         not null
 | 
					#  updated_at      :datetime         not null
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					# == Schema Information
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Table name: pm_links
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  id               :integer          not null, primary key
 | 
				
			||||||
 | 
					#  be_linkable_type :string(255)      not null
 | 
				
			||||||
 | 
					#  be_linkable_id   :integer          not null
 | 
				
			||||||
 | 
					#  linkable_type    :string(255)      not null
 | 
				
			||||||
 | 
					#  linkable_id      :integer          not null
 | 
				
			||||||
 | 
					#  created_at       :datetime         not null
 | 
				
			||||||
 | 
					#  updated_at       :datetime         not null
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Indexes
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  index_pm_links_on_linkable_id    (linkable_id)
 | 
				
			||||||
 | 
					#  index_pm_links_on_linkable_type  (linkable_type)
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PmLink < ApplicationRecord
 | 
				
			||||||
 | 
					  belongs_to :linkable, polymorphic: true
 | 
				
			||||||
 | 
					  belongs_to :be_linkable, polymorphic: true
 | 
				
			||||||
 | 
					  # belongs_to :linkable_issue, -> {where(pm_links: {linkable_type: 'Issue'})}, foreign_key: 'linkable_id', class_name: 'Issue'
 | 
				
			||||||
 | 
					  # belongs_to :be_linkable_issue, -> {where(pm_links: {be_linkable_type: 'Issue'})}, foreign_key: 'be_linkable_id', class_name: 'Issue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def be_linkable
 | 
				
			||||||
 | 
					    be_linkable_type.constantize.find be_linkable_id
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -59,6 +59,7 @@
 | 
				
			||||||
#  is_pinned              :boolean          default("0")
 | 
					#  is_pinned              :boolean          default("0")
 | 
				
			||||||
#  recommend_index        :integer          default("0")
 | 
					#  recommend_index        :integer          default("0")
 | 
				
			||||||
#  pr_view_admin          :boolean          default("0")
 | 
					#  pr_view_admin          :boolean          default("0")
 | 
				
			||||||
 | 
					#  has_actions            :boolean          default("0")
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -130,9 +131,9 @@ class Project < ApplicationRecord
 | 
				
			||||||
  has_many :pinned_projects, dependent: :destroy 
 | 
					  has_many :pinned_projects, dependent: :destroy 
 | 
				
			||||||
  has_many :has_pinned_users, through: :pinned_projects, source: :owner
 | 
					  has_many :has_pinned_users, through: :pinned_projects, source: :owner
 | 
				
			||||||
  has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id
 | 
					  has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id
 | 
				
			||||||
  has_many :user_trace_tasks, dependent: :destroy 
 | 
					  has_many :user_trace_tasks, dependent: :destroy
 | 
				
			||||||
  has_many :project_invite_links, dependent: :destroy
 | 
					  has_many :project_invite_links, dependent: :destroy
 | 
				
			||||||
  has_many :project_topic_ralates, dependent: :destroy 
 | 
					  has_many :project_topic_ralates, dependent: :destroy
 | 
				
			||||||
  has_many :project_topics, through: :project_topic_ralates
 | 
					  has_many :project_topics, through: :project_topic_ralates
 | 
				
			||||||
  has_many :commit_logs, dependent: :destroy
 | 
					  has_many :commit_logs, dependent: :destroy
 | 
				
			||||||
  has_many :daily_project_statistics, dependent: :destroy
 | 
					  has_many :daily_project_statistics, dependent: :destroy
 | 
				
			||||||
| 
						 | 
					@ -149,6 +150,12 @@ class Project < ApplicationRecord
 | 
				
			||||||
  scope :recommend,           -> { visible.project_statics_select.where(recommend: true) }
 | 
					  scope :recommend,           -> { visible.project_statics_select.where(recommend: true) }
 | 
				
			||||||
  scope :pinned, -> {where(is_pinned: true)}
 | 
					  scope :pinned, -> {where(is_pinned: true)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  scope :like, lambda { |keywords|
 | 
				
			||||||
 | 
					    # 表情处理
 | 
				
			||||||
 | 
					    keywords = keywords.to_s.each_char.select { |c| c.bytes.first < 240 }.join('')
 | 
				
			||||||
 | 
					    sql = "name LIKE :search OR identifier LIKE :search"
 | 
				
			||||||
 | 
					    where(sql, :search => "%#{keywords.strip}%") unless keywords.blank?
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  delegate :content, to: :project_detail, allow_nil: true
 | 
					  delegate :content, to: :project_detail, allow_nil: true
 | 
				
			||||||
  delegate :name, to: :license, prefix: true, allow_nil: true
 | 
					  delegate :name, to: :license, prefix: true, allow_nil: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -245,11 +252,11 @@ class Project < ApplicationRecord
 | 
				
			||||||
  def set_recommend_and_is_pinned
 | 
					  def set_recommend_and_is_pinned
 | 
				
			||||||
    self.recommend = self.recommend_index.zero? ? false : true
 | 
					    self.recommend = self.recommend_index.zero? ? false : true
 | 
				
			||||||
    # 私有项目不允许设置精选和推荐
 | 
					    # 私有项目不允许设置精选和推荐
 | 
				
			||||||
    unless self.is_public
 | 
					    # unless self.is_public
 | 
				
			||||||
      self.recommend = false
 | 
					    #   self.recommend = false
 | 
				
			||||||
      self.recommend_index = 0
 | 
					    #   self.recommend_index = 0
 | 
				
			||||||
      self.is_pinned = false 
 | 
					    #   self.is_pinned = false
 | 
				
			||||||
    end
 | 
					    # end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def self.search_project(search)
 | 
					  def self.search_project(search)
 | 
				
			||||||
| 
						 | 
					@ -393,11 +400,13 @@ class Project < ApplicationRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    user = Owner.find_by_login namespace_path
 | 
					    user = Owner.find_by_login namespace_path
 | 
				
			||||||
    user = User.new(login: namespace_path) if user.nil?
 | 
					    user = User.new(login: namespace_path) if user.nil?
 | 
				
			||||||
 | 
					    project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
 | 
				
			||||||
 | 
					    user = User.new(login: namespace_path) if user.nil?
 | 
				
			||||||
    if identifier.end_with?('.json')
 | 
					    if identifier.end_with?('.json')
 | 
				
			||||||
      project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
 | 
					      project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
 | 
				
			||||||
      identifier = identifier.sub(/.*\K.json/, '')
 | 
					      identifier = identifier.sub(/.*\K.json/, '')
 | 
				
			||||||
      project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
 | 
					      project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
 | 
				
			||||||
    else 
 | 
					    else
 | 
				
			||||||
      project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
 | 
					      project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    return nil if project.blank?
 | 
					    return nil if project.blank?
 | 
				
			||||||
| 
						 | 
					@ -444,36 +453,46 @@ class Project < ApplicationRecord
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def get_last_project_issues_index
 | 
					  def get_last_project_issues_index
 | 
				
			||||||
    last_issue = self.issues.issue_issue.last 
 | 
					    last_issue = self.issues.issue_issue.last
 | 
				
			||||||
    deleted_issue_count = ($redis_cache.hget("issue_cache_delete_count", self.id) || 0).to_i
 | 
					    deleted_issue_count = ($redis_cache.hget("issue_cache_delete_count", self.id) || 0).to_i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    last_issue&.project_issues_index.present? ? last_issue.project_issues_index + deleted_issue_count : 0
 | 
					    last_issue&.project_issues_index.present? ? last_issue.project_issues_index + deleted_issue_count : 0
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def incre_project_issue_cache_delete_count(count=1)
 | 
					  def incre_project_issue_cache_delete_count(count=1)
 | 
				
			||||||
    $redis_cache.hincrby("issue_cache_delete_count", self.id, count) 
 | 
					    $redis_cache.hincrby("issue_cache_delete_count", self.id, count)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def del_project_issue_cache_delete_count
 | 
					  def del_project_issue_cache_delete_count
 | 
				
			||||||
    $redis_cache.hdel("issue_cache_delete_count", self.id) 
 | 
					    $redis_cache.hdel("issue_cache_delete_count", self.id)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def open_portrait
 | 
					  def open_portrait
 | 
				
			||||||
    EduSetting.get("open_portrait_projects").present? ? EduSetting.get("open_portrait_projects").split(",").include?(self.id.to_s) : false
 | 
					    EduSetting.get("open_portrait_projects").present? ? EduSetting.get("open_portrait_projects").split(",").include?(self.id.to_s) : false
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def actionable
 | 
				
			||||||
 | 
					    return false if EduSetting.get("project_user_actionable").nil?
 | 
				
			||||||
 | 
					    return EduSetting.get("project_user_actionable").split(",").include?(self.owner&.login)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def has_pull_request(branch_name)
 | 
					  def has_pull_request(branch_name)
 | 
				
			||||||
    return true if self.pull_requests.opening.where(head: branch_name).present? || self.pull_requests.opening.where(base: branch_name).present? 
 | 
					    return true if self.pull_requests.opening.where(head: branch_name).present? || self.pull_requests.opening.where(base: branch_name).present? 
 | 
				
			||||||
    if self.forked_from_project_id.present? 
 | 
					    if self.forked_from_project_id.present? 
 | 
				
			||||||
      return true if self.fork_project.pull_requests.opening.where(head: branch_name).present? || self.fork_project.pull_requests.opening.where(base: branch_name).present?
 | 
					      return true if self.fork_project.pull_requests.opening.where(head: branch_name).present? || self.fork_project.pull_requests.opening.where(base: branch_name).present?
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return false
 | 
					    return false
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def self.mindspore_contributors 
 | 
					  def is_need_apply
 | 
				
			||||||
 | 
					    return false if EduSetting.get("project_need_apply").nil?
 | 
				
			||||||
 | 
					    return EduSetting.get("project_need_apply").split(",").include?(self.id.to_s)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def self.mindspore_contributors
 | 
				
			||||||
    cache_result = $redis_cache.get("ProjectMindsporeContributors")
 | 
					    cache_result = $redis_cache.get("ProjectMindsporeContributors")
 | 
				
			||||||
    if cache_result.nil? 
 | 
					    if cache_result.nil?
 | 
				
			||||||
      contributors = []
 | 
					      contributors = []
 | 
				
			||||||
      file = File.open('public/mindspore_authors', 'r')
 | 
					      file = File.open('public/mindspore_authors', 'r')
 | 
				
			||||||
      file.each_line do |l|
 | 
					      file.each_line do |l|
 | 
				
			||||||
| 
						 | 
					@ -481,12 +500,12 @@ class Project < ApplicationRecord
 | 
				
			||||||
        email = itemArray[0]
 | 
					        email = itemArray[0]
 | 
				
			||||||
        username = itemArray[1]
 | 
					        username = itemArray[1]
 | 
				
			||||||
        commits = itemArray[2].to_i
 | 
					        commits = itemArray[2].to_i
 | 
				
			||||||
        user = User.find_by(login: username, mail: email) 
 | 
					        user = User.find_by(login: username, mail: email)
 | 
				
			||||||
        user = User.find_by(login: username) if user.nil? 
 | 
					        user = User.find_by(login: username) if user.nil?
 | 
				
			||||||
        user = User.find_by(mail: email) if user.nil?
 | 
					        user = User.find_by(mail: email) if user.nil?
 | 
				
			||||||
        # next if user.nil?
 | 
					        # next if user.nil?
 | 
				
			||||||
        search_contributor = contributors.select{|con| con["id"]==user.id}[0]
 | 
					        search_contributor = contributors.select{|con| con["id"]==user.id}[0]
 | 
				
			||||||
        if search_contributor.present? 
 | 
					        if search_contributor.present?
 | 
				
			||||||
          search_contributor["contributions"] += commits
 | 
					          search_contributor["contributions"] += commits
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
          contributors << {contributions: commits, name: username, login: username, email: email, id: user&.id}.stringify_keys
 | 
					          contributors << {contributions: commits, name: username, login: username, email: email, id: user&.id}.stringify_keys
 | 
				
			||||||
| 
						 | 
					@ -497,10 +516,15 @@ class Project < ApplicationRecord
 | 
				
			||||||
      $redis_cache.set("ProjectMindsporeContributors", contributors.to_json)
 | 
					      $redis_cache.set("ProjectMindsporeContributors", contributors.to_json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return contributors
 | 
					      return contributors
 | 
				
			||||||
    else 
 | 
					    else
 | 
				
			||||||
      return JSON.parse(cache_result)
 | 
					      return JSON.parse(cache_result)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def full_url
 | 
				
			||||||
 | 
					    Rails.application.config_for(:configuration)['platform_url'].to_s + '/' + self.owner&.try(:login).to_s + '/' + self.identifier.to_s
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def to_builder
 | 
					  def to_builder
 | 
				
			||||||
    Jbuilder.new do |project|
 | 
					    Jbuilder.new do |project|
 | 
				
			||||||
      project.id self.id
 | 
					      project.id self.id
 | 
				
			||||||
| 
						 | 
					@ -509,9 +533,9 @@ class Project < ApplicationRecord
 | 
				
			||||||
      project.description Nokogiri::HTML(self.description).text
 | 
					      project.description Nokogiri::HTML(self.description).text
 | 
				
			||||||
      project.visits self.visits
 | 
					      project.visits self.visits
 | 
				
			||||||
      project.praises_count self.praises_count.to_i
 | 
					      project.praises_count self.praises_count.to_i
 | 
				
			||||||
      project.watchers_count self.watchers_count.to_i 
 | 
					      project.watchers_count self.watchers_count.to_i
 | 
				
			||||||
      project.issues_count self.issues_count.to_i  
 | 
					      project.issues_count self.issues_count.to_i
 | 
				
			||||||
      project.pull_requests_count self.pull_requests_count.to_i  
 | 
					      project.pull_requests_count self.pull_requests_count.to_i
 | 
				
			||||||
      project.forked_count self.forked_count.to_i
 | 
					      project.forked_count self.forked_count.to_i
 | 
				
			||||||
      project.is_public self.is_public
 | 
					      project.is_public self.is_public
 | 
				
			||||||
      project.mirror_url self.repository&.mirror_url
 | 
					      project.mirror_url self.repository&.mirror_url
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,6 @@
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  index_project_categories_on_ancestry  (ancestry)
 | 
					#  index_project_categories_on_ancestry  (ancestry)
 | 
				
			||||||
#  index_project_categories_on_id        (id)
 | 
					 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProjectCategory < ApplicationRecord
 | 
					class ProjectCategory < ApplicationRecord
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,10 +9,6 @@
 | 
				
			||||||
#  created_at     :datetime         not null
 | 
					#  created_at     :datetime         not null
 | 
				
			||||||
#  updated_at     :datetime         not null
 | 
					#  updated_at     :datetime         not null
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#  index_project_languages_on_id  (id)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProjectLanguage < ApplicationRecord
 | 
					class ProjectLanguage < ApplicationRecord
 | 
				
			||||||
  include Projectable
 | 
					  include Projectable
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,6 @@
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  index_name                        (project_id)
 | 
					 | 
				
			||||||
#  index_repositories_on_identifier  (identifier)
 | 
					#  index_repositories_on_identifier  (identifier)
 | 
				
			||||||
#  index_repositories_on_project_id  (project_id)
 | 
					#  index_repositories_on_project_id  (project_id)
 | 
				
			||||||
#  index_repositories_on_user_id     (user_id)
 | 
					#  index_repositories_on_user_id     (user_id)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,8 @@
 | 
				
			||||||
#  sync_direction        :integer
 | 
					#  sync_direction        :integer
 | 
				
			||||||
#  created_at            :datetime         not null
 | 
					#  created_at            :datetime         not null
 | 
				
			||||||
#  updated_at            :datetime         not null
 | 
					#  updated_at            :datetime         not null
 | 
				
			||||||
 | 
					#  external_token        :string(255)
 | 
				
			||||||
 | 
					#  webhook_gid           :integer
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,8 @@
 | 
				
			||||||
#  sync_direction        :integer
 | 
					#  sync_direction        :integer
 | 
				
			||||||
#  created_at            :datetime         not null
 | 
					#  created_at            :datetime         not null
 | 
				
			||||||
#  updated_at            :datetime         not null
 | 
					#  updated_at            :datetime         not null
 | 
				
			||||||
 | 
					#  external_token        :string(255)
 | 
				
			||||||
 | 
					#  webhook_gid           :integer
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Indexes
 | 
					# Indexes
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# This program is free software; you can redistribute it and/or
 | 
					# This program is free software; you can redistribute it and/or
 | 
				
			||||||
# modify it under the terms of the GNU General Public License
 | 
					# modify it under the terms of the GNU General Public License
 | 
				
			||||||
| 
						 | 
					@ -44,7 +45,7 @@ class Token < ActiveRecord::Base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def self.get_or_create_permanent_login_token(user, type)
 | 
					  def self.get_or_create_permanent_login_token(user, type)
 | 
				
			||||||
    token = Token.get_token_from_user(user, type)
 | 
					    token = Token.get_token_from_user(user, type)
 | 
				
			||||||
    Rails.logger.info "###### Token.get_token_from_user result: #{token&.value}"
 | 
					    Rails.logger.info "###### Token.get_token_from_user time:#{Time.new.to_i}, result: #{token&.value}"
 | 
				
			||||||
    unless token
 | 
					    unless token
 | 
				
			||||||
      token = Token.create(:user => user, :action => type)
 | 
					      token = Token.create(:user => user, :action => type)
 | 
				
			||||||
      Rails.logger.info "###### Token.get_token_from_user is nul and agine create token: #{token&.value}"
 | 
					      Rails.logger.info "###### Token.get_token_from_user is nul and agine create token: #{token&.value}"
 | 
				
			||||||
| 
						 | 
					@ -117,8 +118,8 @@ class Token < ActiveRecord::Base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Removes obsolete tokens (same user and action)
 | 
					  # Removes obsolete tokens (same user and action)
 | 
				
			||||||
  def delete_previous_tokens
 | 
					  def delete_previous_tokens
 | 
				
			||||||
    if user
 | 
					    # if user
 | 
				
			||||||
      Token.where(['user_id = ? AND action = ?', user.id, action]).delete_all
 | 
					      # Token.where(['user_id = ? AND action = ?', user.id, action]).delete_all
 | 
				
			||||||
    end
 | 
					    # end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,9 +22,9 @@
 | 
				
			||||||
#  school_id              :integer
 | 
					#  school_id              :integer
 | 
				
			||||||
#  description            :string(255)
 | 
					#  description            :string(255)
 | 
				
			||||||
#  department_id          :integer
 | 
					#  department_id          :integer
 | 
				
			||||||
#  province               :text(65535)
 | 
					#  province               :string(255)
 | 
				
			||||||
#  custom_department      :string(255)
 | 
					 | 
				
			||||||
#  city                   :string(255)
 | 
					#  city                   :string(255)
 | 
				
			||||||
 | 
					#  custom_department      :string(255)
 | 
				
			||||||
#  show_email             :boolean          default("0")
 | 
					#  show_email             :boolean          default("0")
 | 
				
			||||||
#  show_location          :boolean          default("0")
 | 
					#  show_location          :boolean          default("0")
 | 
				
			||||||
#  show_department        :boolean          default("0")
 | 
					#  show_department        :boolean          default("0")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,24 +9,39 @@ class Blockchain::BalanceQuery < ApplicationQuery
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call
 | 
					  def call
 | 
				
			||||||
    if is_current_admin_user
 | 
					    if is_current_admin_user
 | 
				
			||||||
      token_list, total_count = find_repo_with_token(params["user_id"], (params["page"] || 1), (params["limit"] || 10))
 | 
					 | 
				
			||||||
      result_list = []
 | 
					      result_list = []
 | 
				
			||||||
      token_list.each do |t|
 | 
					      if params[:project_id].present? or params[:keyword].present?
 | 
				
			||||||
        project = Project.find_by(id: t['token_name'].to_i)
 | 
					        project_ids = params[:project_id].present? ? [params[:project_id]] : Project.where(user_id: params["user_id"]).like(params[:keyword]).ids
 | 
				
			||||||
        if project.nil?
 | 
					        project_ids.each do |project_id|
 | 
				
			||||||
          result_list << {project: nil, balance: t['balance']}
 | 
					          project_balance  = find_one_balance(params["user_id"], project_id)
 | 
				
			||||||
          next
 | 
					          project = Project.find_by(id: project_balance['token_name'].to_i)
 | 
				
			||||||
 | 
					          if project.present?
 | 
				
			||||||
 | 
					            owner = User.find_by(id: project.user_id)
 | 
				
			||||||
 | 
					            if owner.present?
 | 
				
			||||||
 | 
					              result_list << {project: project, balance: project_balance['balance']}
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
        owner = User.find_by(id: project.user_id)
 | 
					        {"status": 0, "projects": result_list, "total_count": result_list.size}
 | 
				
			||||||
        if owner.nil? || project.nil?
 | 
					      else
 | 
				
			||||||
        else
 | 
					        token_list, total_count = find_repo_with_token(params["user_id"], (params["page"] || 1), (params["limit"] || 10))
 | 
				
			||||||
          balance = t['balance']
 | 
					        token_list.each do |t|
 | 
				
			||||||
          result_list << {project: project, balance: balance}
 | 
					          project = Project.find_by(id: t['token_name'].to_i)
 | 
				
			||||||
 | 
					          if project.nil?
 | 
				
			||||||
 | 
					            result_list << {project: nil, balance: t['balance']}
 | 
				
			||||||
 | 
					            next
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          owner = User.find_by(id: project.user_id)
 | 
				
			||||||
 | 
					          if owner.nil? || project.nil?
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            balance = t['balance']
 | 
				
			||||||
 | 
					            result_list << {project: project, balance: balance}
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					        {"status": 0, "projects": result_list, "total_count": total_count}
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
      results = {"status": 0, "projects": result_list, "total_count": total_count}
 | 
					 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      results = {"status": 1} # query failed
 | 
					      {"status": 1} # query failed
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					class Api::Pm::Issues::BatchDeleteService < ApplicationService
 | 
				
			||||||
 | 
					  include ActiveModel::Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  attr_reader :project, :issues, :current_user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :project, :issues, :current_user, presence: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def initialize(project, issues, current_user = nil)
 | 
				
			||||||
 | 
					    @project = project 
 | 
				
			||||||
 | 
					    @issues = issues.includes(:assigners)
 | 
				
			||||||
 | 
					    @current_user = current_user
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def call 
 | 
				
			||||||
 | 
					    raise Error, errors.full_messages.join(", ") unless valid? 
 | 
				
			||||||
 | 
					    try_lock("Api::V1::Issues::DeleteService:#{project.id}") # 开始写数据,加锁
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delete_issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    project.incre_project_issue_cache_delete_count(@issues.size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if Site.has_notice_menu? && !project.id.zero?
 | 
				
			||||||
 | 
					      @issues.each do |issue|
 | 
				
			||||||
 | 
					        SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id) 
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    unlock("Api::V1::Issues::DeleteService:#{project.id}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def delete_issues
 | 
				
			||||||
 | 
					    raise Error, "批量删除疑修失败!" unless @issues.destroy_all
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					class Api::Pm::Issues::BatchUpdateService < ApplicationService
 | 
				
			||||||
 | 
					  include ActiveModel::Model
 | 
				
			||||||
 | 
					  include Api::V1::Issues::Concerns::Checkable 
 | 
				
			||||||
 | 
					  include Api::V1::Issues::Concerns::Loadable 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  attr_reader :project, :issues, :params, :current_user
 | 
				
			||||||
 | 
					  attr_reader :status_id, :priority_id, :milestone_id, :project_id
 | 
				
			||||||
 | 
					  attr_reader :issue_tag_ids, :assigner_ids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :project, :issues, :current_user, presence: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def initialize(project, issues, params, current_user = nil)
 | 
				
			||||||
 | 
					    @project = project 
 | 
				
			||||||
 | 
					    @issues = issues
 | 
				
			||||||
 | 
					    @params = params
 | 
				
			||||||
 | 
					    @current_user = current_user
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def call 
 | 
				
			||||||
 | 
					    raise Error, errors.full_messages.join(", ") unless valid? 
 | 
				
			||||||
 | 
					    ActiveRecord::Base.transaction do 
 | 
				
			||||||
 | 
					      @issues.each do |issue| 
 | 
				
			||||||
 | 
					        if issue.issue_classify == "issue"
 | 
				
			||||||
 | 
					          Api::Pm::Issues::UpdateService.call(project, issue, params, current_user)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,181 @@
 | 
				
			||||||
 | 
					class Api::Pm::Issues::CreateService < ApplicationService
 | 
				
			||||||
 | 
					  include ActiveModel::Model
 | 
				
			||||||
 | 
					  include Api::V1::Issues::Concerns::Checkable 
 | 
				
			||||||
 | 
					  include Api::V1::Issues::Concerns::Loadable 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  attr_reader :project, :current_user
 | 
				
			||||||
 | 
					  attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num, :root_subject
 | 
				
			||||||
 | 
					  attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login
 | 
				
			||||||
 | 
					  attr_accessor :created_issue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :subject, presence: true
 | 
				
			||||||
 | 
					  validates :status_id, :priority_id, presence: true
 | 
				
			||||||
 | 
					  validates :project, :current_user, presence: true
 | 
				
			||||||
 | 
					  validates :blockchain_token_num, numericality: {greater_than: 0}, allow_blank: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def initialize(project, params, current_user = nil)
 | 
				
			||||||
 | 
					    @project = project 
 | 
				
			||||||
 | 
					    @current_user = current_user 
 | 
				
			||||||
 | 
					    @status_id = params[:status_id]
 | 
				
			||||||
 | 
					    @priority_id = params[:priority_id]
 | 
				
			||||||
 | 
					    @milestone_id = params[:milestone_id]
 | 
				
			||||||
 | 
					    @branch_name = params[:branch_name]
 | 
				
			||||||
 | 
					    @start_date = params[:start_date]
 | 
				
			||||||
 | 
					    @due_date = params[:due_date]
 | 
				
			||||||
 | 
					    @subject = params[:subject]
 | 
				
			||||||
 | 
					    @description = params[:description]
 | 
				
			||||||
 | 
					    @blockchain_token_num = params[:blockchain_token_num]
 | 
				
			||||||
 | 
					    @issue_tag_ids = params[:issue_tag_ids]
 | 
				
			||||||
 | 
					    @assigner_ids = params[:assigner_ids]
 | 
				
			||||||
 | 
					    @attachment_ids = params[:attachment_ids]
 | 
				
			||||||
 | 
					    @receivers_login = params[:receivers_login]
 | 
				
			||||||
 | 
					    @pm_project_id = params[:pm_project_id]
 | 
				
			||||||
 | 
					    @pm_sprint_id = params[:pm_sprint_id]
 | 
				
			||||||
 | 
					    @pm_issue_type = params[:pm_issue_type]
 | 
				
			||||||
 | 
					    @root_id = params[:root_id]
 | 
				
			||||||
 | 
					    @time_scale = params[:time_scale]
 | 
				
			||||||
 | 
					    @linkable_id = params[:link_able_id]
 | 
				
			||||||
 | 
					    @root_subject = params[:root_subject]
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def call
 | 
				
			||||||
 | 
					    raise Error, errors.full_messages.join(', ') unless valid? 
 | 
				
			||||||
 | 
					    ActiveRecord::Base.transaction do 
 | 
				
			||||||
 | 
					      check_issue_status(status_id)
 | 
				
			||||||
 | 
					      check_issue_priority(priority_id)
 | 
				
			||||||
 | 
					      check_milestone(milestone_id) if milestone_id.present?
 | 
				
			||||||
 | 
					      check_issue_tags(issue_tag_ids) unless issue_tag_ids.blank?
 | 
				
			||||||
 | 
					      check_assigners(assigner_ids) unless assigner_ids.blank?
 | 
				
			||||||
 | 
					      check_attachments(attachment_ids) unless attachment_ids.blank?
 | 
				
			||||||
 | 
					      check_atme_receivers(receivers_login) unless receivers_login.blank?
 | 
				
			||||||
 | 
					      check_blockchain_token_num(current_user.id, project.id, blockchain_token_num) if blockchain_token_num.present?
 | 
				
			||||||
 | 
					      load_assigners(assigner_ids) unless assigner_ids.blank?
 | 
				
			||||||
 | 
					      load_attachments(attachment_ids) unless attachment_ids.blank?
 | 
				
			||||||
 | 
					      load_issue_tags(issue_tag_ids) unless issue_tag_ids.blank?
 | 
				
			||||||
 | 
					      load_atme_receivers(receivers_login) unless receivers_login.blank?
 | 
				
			||||||
 | 
					      try_lock("Api::Pm::Issues::CreateService:#{project.id}") # 开始写数据,加锁
 | 
				
			||||||
 | 
					      @created_issue = Issue.new(issue_attributes)
 | 
				
			||||||
 | 
					      @created_issue.pm_issue_type = @pm_issue_type
 | 
				
			||||||
 | 
					      if @root_subject.present? && @pm_issue_type.to_i == 4
 | 
				
			||||||
 | 
					        @root_issue = Issue.find_by(subject: @root_subject, pm_issue_type: 4, pm_project_id: @pm_project_id) 
 | 
				
			||||||
 | 
					        unless @root_issue.present? 
 | 
				
			||||||
 | 
					          @root_issue = Issue.create(subject: @root_subject, pm_issue_type: 4, pm_project_id: @pm_project_id, status_id: 1, priority_id: 1, tracker_id: Tracker.first.id, project_id: @project.id, author_id: current_user.id)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        @created_issue.root_id = @root_issue.id
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        @created_issue.root_id = @root_id
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      build_author_participants
 | 
				
			||||||
 | 
					      build_assigner_participants unless assigner_ids.blank?
 | 
				
			||||||
 | 
					      build_atme_participants if @atme_receivers.present?
 | 
				
			||||||
 | 
					      build_issue_journal_details 
 | 
				
			||||||
 | 
					      build_issue_project_trends 
 | 
				
			||||||
 | 
					      @created_issue.assigners = @assigners unless assigner_ids.blank?
 | 
				
			||||||
 | 
					      @created_issue.attachments = @attachments unless attachment_ids.blank?
 | 
				
			||||||
 | 
					      @created_issue.issue_tags = @issue_tags unless issue_tag_ids.blank?
 | 
				
			||||||
 | 
					      @created_issue.pm_project_id = @pm_project_id
 | 
				
			||||||
 | 
					      @created_issue.pm_sprint_id = @pm_sprint_id
 | 
				
			||||||
 | 
					      @created_issue.time_scale = @time_scale
 | 
				
			||||||
 | 
					      @created_issue.issue_tags_value = @issue_tags.order('id asc').pluck(:id).join(',') unless issue_tag_ids.blank?
 | 
				
			||||||
 | 
					      @created_issue.changer_id = @current_user.id
 | 
				
			||||||
 | 
					      @created_issue.save!
 | 
				
			||||||
 | 
					      if @created_issue.parent_issue.present? 
 | 
				
			||||||
 | 
					        parent_issue = @created_issue.parent_issue
 | 
				
			||||||
 | 
					        if @created_issue.root_id.present? && parent_issue.present?
 | 
				
			||||||
 | 
					          journal = parent_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					          journal.journal_details.create!({property: @created_issue.pm_issue_type_string, prop_key: 'leaf_issue', value: @created_issue.id.to_s})
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      if @linkable_id.present?
 | 
				
			||||||
 | 
					        PmLink.create(be_linkable_type: 'Issue', be_linkable_id: @created_issue.id, linkable_type: 'Issue', linkable_id: @linkable_id) 
 | 
				
			||||||
 | 
					        another_issue = Issue.find_by_id(@linkable_id)
 | 
				
			||||||
 | 
					        if another_issue.present? 
 | 
				
			||||||
 | 
					          journal = another_issue.journals.create!({user_id: @current_user.id})
 | 
				
			||||||
 | 
					          journal.journal_details.create!({property: 'link_issue', prop_key: "1", value: @created_issue.id.to_s})
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      if Site.has_blockchain? && @project.use_blockchain
 | 
				
			||||||
 | 
					        if @created_issue.blockchain_token_num.present? && @created_issue.blockchain_token_num > 0 
 | 
				
			||||||
 | 
					          Blockchain::CreateIssue.call({user_id: current_user.id, project_id: @created_issue.project_id, token_num: @created_issue.blockchain_token_num})
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        push_activity_2_blockchain('issue_create', @created_issue) 
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      project.del_project_issue_cache_delete_count # 把缓存里存储项目删除issue的个数清除掉
 | 
				
			||||||
 | 
					      unless @project.id.zero?
 | 
				
			||||||
 | 
					        # 新增时向grimoirelab推送事件
 | 
				
			||||||
 | 
					        IssueWebhookJob.set(wait: 5.seconds).perform_later(@created_issue.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # @信息发送
 | 
				
			||||||
 | 
					        AtmeService.call(current_user, @atme_receivers, @created_issue) unless receivers_login.blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 发消息
 | 
				
			||||||
 | 
					        if Site.has_notice_menu?
 | 
				
			||||||
 | 
					          SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @created_issue&.id, assigner_ids) unless assigner_ids.blank?
 | 
				
			||||||
 | 
					          SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @created_issue&.id)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 触发webhook
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueCreate', @created_issue&.id, current_user.id)
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @created_issue&.id, current_user.id, {issue_tag_ids: [[], issue_tag_ids]}) unless issue_tag_ids.blank?
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @created_issue&.id, current_user.id, {assigner_ids: [[], assigner_ids]}) unless assigner_ids.blank?
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      unlock("Api::Pm::Issues::CreateService:#{project.id}") # 结束写数据,解锁
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return @created_issue
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issue_attributes 
 | 
				
			||||||
 | 
					    issue_attributes = {
 | 
				
			||||||
 | 
					      subject: subject,
 | 
				
			||||||
 | 
					      project_id: project.id,
 | 
				
			||||||
 | 
					      author_id: current_user.id,
 | 
				
			||||||
 | 
					      tracker_id: Tracker.first.id,
 | 
				
			||||||
 | 
					      status_id: status_id, 
 | 
				
			||||||
 | 
					      priority_id: priority_id,
 | 
				
			||||||
 | 
					      project_issues_index: (project.get_last_project_issues_index + 1),
 | 
				
			||||||
 | 
					      issue_type: '1',
 | 
				
			||||||
 | 
					      issue_classify: 'issue'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    issue_attributes.merge!({description: description}) if description.present?
 | 
				
			||||||
 | 
					    issue_attributes.merge!({fixed_version_id: milestone_id}) if milestone_id.present?
 | 
				
			||||||
 | 
					    issue_attributes.merge!({start_date: start_date}) if start_date.present?
 | 
				
			||||||
 | 
					    issue_attributes.merge!({due_date: due_date}) if due_date.present?
 | 
				
			||||||
 | 
					    issue_attributes.merge!({branch_name: branch_name}) if branch_name.present?
 | 
				
			||||||
 | 
					    issue_attributes.merge!({blockchain_token_num: blockchain_token_num}) if blockchain_token_num.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    issue_attributes
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_author_participants 
 | 
				
			||||||
 | 
					    @created_issue.issue_participants.new({participant_type: 'authored', participant_id: current_user.id})
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_assigner_participants
 | 
				
			||||||
 | 
					    assigner_ids.each do |aid|
 | 
				
			||||||
 | 
					      @created_issue.issue_participants.new({participant_type: 'assigned', participant_id: aid})
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_atme_participants
 | 
				
			||||||
 | 
					    @atme_receivers.each do |receiver|
 | 
				
			||||||
 | 
					      @created_issue.issue_participants.new({participant_type: 'atme', participant_id: receiver.id})
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_issue_project_trends
 | 
				
			||||||
 | 
					    return if @project.id == 0
 | 
				
			||||||
 | 
					    @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: 'create'})
 | 
				
			||||||
 | 
					    @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE}) if status_id.to_i == 5
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_issue_journal_details
 | 
				
			||||||
 | 
					    journal = @created_issue.journals.new({user_id: current_user.id})
 | 
				
			||||||
 | 
					    journal.journal_details.new({property: @created_issue.pm_issue_type_string, prop_key: 1, old_value: '', value: ''})
 | 
				
			||||||
 | 
					  end 
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,48 @@
 | 
				
			||||||
 | 
					class Api::Pm::Issues::DeleteService < ApplicationService
 | 
				
			||||||
 | 
					  include ActiveModel::Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  attr_reader :project, :issue, :current_user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :project, :issue, :current_user, presence: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def initialize(project, issue, current_user = nil)
 | 
				
			||||||
 | 
					    @project = project 
 | 
				
			||||||
 | 
					    @issue = issue
 | 
				
			||||||
 | 
					    @current_user = current_user
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def call 
 | 
				
			||||||
 | 
					    raise Error, errors.full_messages.join(", ") unless valid? 
 | 
				
			||||||
 | 
					    try_lock("Api::V1::Issues::DeleteService:#{project.id}") # 开始写数据,加锁
 | 
				
			||||||
 | 
					    ActiveRecord::Base.transaction do 
 | 
				
			||||||
 | 
					      parent_issue = @issue.parent_issue
 | 
				
			||||||
 | 
					      if @issue.root_id.present? && parent_issue.present?
 | 
				
			||||||
 | 
					        journal = parent_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: @issue.pm_issue_type_string, prop_key: 'leaf_issue', old_value: @issue.id.to_s})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      delete_issue
 | 
				
			||||||
 | 
					      #删除双向关联
 | 
				
			||||||
 | 
					      PmLink.where(be_linkable_id: @issue.id, be_linkable_type: 'Issue').or(PmLink.where(linkable_id: @issue.id, linkable_type: 'Issue')).map(&:destroy)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      project.incre_project_issue_cache_delete_count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if Site.has_blockchain? && @project.use_blockchain && !project.id.zero?
 | 
				
			||||||
 | 
					        unlock_balance_on_blockchain(@issue.author_id.to_s, @project.id.to_s, @issue.blockchain_token_num.to_i) if @issue.blockchain_token_num.present?
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if Site.has_notice_menu? && !project.id.zero?
 | 
				
			||||||
 | 
					        SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id) 
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    unlock("Api::V1::Issues::DeleteService:#{project.id}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def delete_issue
 | 
				
			||||||
 | 
					    raise Error, "删除疑修失败!" unless issue.destroy!
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,326 @@
 | 
				
			||||||
 | 
					class Api::Pm::Issues::UpdateService < ApplicationService
 | 
				
			||||||
 | 
					  include ActiveModel::Model 
 | 
				
			||||||
 | 
					  include Api::V1::Issues::Concerns::Checkable 
 | 
				
			||||||
 | 
					  include Api::V1::Issues::Concerns::Loadable 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  attr_reader :project, :issue, :current_user
 | 
				
			||||||
 | 
					  attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num
 | 
				
			||||||
 | 
					  attr_reader :target_pm_project_id, :pm_sprint_id, :pm_issue_type, :root_id, :time_scale
 | 
				
			||||||
 | 
					  attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login, :before_issue_tag_ids, :before_assigner_ids, :project_id
 | 
				
			||||||
 | 
					  attr_accessor :add_assigner_ids, :previous_issue_changes, :updated_issue, :atme_receivers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :project, :issue, :current_user, presence: true
 | 
				
			||||||
 | 
					  validates :blockchain_token_num, numericality: {greater_than: 0}, allow_blank: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def initialize(project, issue, params, current_user = nil) 
 | 
				
			||||||
 | 
					    @project = project 
 | 
				
			||||||
 | 
					    @issue = issue
 | 
				
			||||||
 | 
					    @current_user = current_user
 | 
				
			||||||
 | 
					    @status_id = params[:status_id]
 | 
				
			||||||
 | 
					    @priority_id = params[:priority_id]
 | 
				
			||||||
 | 
					    @milestone_id = params[:milestone_id]
 | 
				
			||||||
 | 
					    @branch_name = params[:branch_name]
 | 
				
			||||||
 | 
					    @start_date = params[:start_date]
 | 
				
			||||||
 | 
					    @due_date = params[:due_date]
 | 
				
			||||||
 | 
					    @subject = params[:subject]
 | 
				
			||||||
 | 
					    @description = params[:description]
 | 
				
			||||||
 | 
					    @blockchain_token_num = params[:blockchain_token_num]
 | 
				
			||||||
 | 
					    @issue_tag_ids = params[:issue_tag_ids]
 | 
				
			||||||
 | 
					    @assigner_ids = params[:assigner_ids]
 | 
				
			||||||
 | 
					    @before_issue_tag_ids = issue.issue_tags.pluck(:id)
 | 
				
			||||||
 | 
					    @before_assigner_ids = issue.assigners.pluck(:id)
 | 
				
			||||||
 | 
					    @attachment_ids = params[:attachment_ids]
 | 
				
			||||||
 | 
					    @receivers_login = params[:receivers_login]
 | 
				
			||||||
 | 
					    @target_pm_project_id = params[:target_pm_project_id]
 | 
				
			||||||
 | 
					    @pm_sprint_id = params[:pm_sprint_id]
 | 
				
			||||||
 | 
					    @pm_issue_type = params[:pm_issue_type]
 | 
				
			||||||
 | 
					    @root_id = params[:root_id]
 | 
				
			||||||
 | 
					    @time_scale = params[:time_scale]
 | 
				
			||||||
 | 
					    @project_id = params[:project_id]
 | 
				
			||||||
 | 
					    @add_assigner_ids = []
 | 
				
			||||||
 | 
					    @previous_issue_changes = {}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def call 
 | 
				
			||||||
 | 
					    raise Error, errors.full_messages.join(", ") unless valid? 
 | 
				
			||||||
 | 
					    ActiveRecord::Base.transaction do 
 | 
				
			||||||
 | 
					      check_issue_status(status_id) if status_id.present?
 | 
				
			||||||
 | 
					      check_issue_priority(priority_id) if priority_id.present?
 | 
				
			||||||
 | 
					      check_milestone(milestone_id) if milestone_id.present?
 | 
				
			||||||
 | 
					      check_root_issue(issue, root_id) if root_id.present?
 | 
				
			||||||
 | 
					      check_issue_tags(issue_tag_ids) unless issue_tag_ids.nil?
 | 
				
			||||||
 | 
					      check_assigners(assigner_ids) unless assigner_ids.nil?
 | 
				
			||||||
 | 
					      check_attachments(attachment_ids) unless attachment_ids.nil?
 | 
				
			||||||
 | 
					      check_atme_receivers(receivers_login) unless receivers_login.nil?
 | 
				
			||||||
 | 
					      check_blockchain_token_num(issue.author_id, project.id, blockchain_token_num, (@issue.blockchain_token_num || 0)) if blockchain_token_num.present? && current_user.id == @issue.author_id && !PullAttachedIssue.exists?(issue_id: @issue, fixed: true)
 | 
				
			||||||
 | 
					      load_assigners(assigner_ids)
 | 
				
			||||||
 | 
					      load_attachments(attachment_ids)
 | 
				
			||||||
 | 
					      load_issue_tags(issue_tag_ids)
 | 
				
			||||||
 | 
					      load_atme_receivers(receivers_login) unless receivers_login.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try_lock("Api::Pm::Issues::UpdateService:#{project.id}:#{issue.id}")
 | 
				
			||||||
 | 
					      @updated_issue = @issue
 | 
				
			||||||
 | 
					      issue_load_attributes
 | 
				
			||||||
 | 
					      build_assigner_issue_journal_details unless assigner_ids.nil?# 操作记录
 | 
				
			||||||
 | 
					      build_attachment_issue_journal_details unless attachment_ids.nil?
 | 
				
			||||||
 | 
					      build_issue_tag_issue_journal_details unless issue_tag_ids.nil?
 | 
				
			||||||
 | 
					      build_issue_project_trends if status_id.present? # 开关时间记录
 | 
				
			||||||
 | 
					      build_assigner_participants unless assigner_ids.nil? # 负责人
 | 
				
			||||||
 | 
					      build_edit_participants 
 | 
				
			||||||
 | 
					      build_atme_participants if @atme_receivers.present?
 | 
				
			||||||
 | 
					      unless assigner_ids.nil?
 | 
				
			||||||
 | 
					        @previous_issue_changes.merge!(assigned_to_id: [@updated_issue.assigners.pluck(:id),  @assigners.pluck(:id)])
 | 
				
			||||||
 | 
					        @updated_issue.assigners = @assigners || User.none 
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      @updated_issue.attachments = @attachments || Attachment.none unless attachment_ids.nil?
 | 
				
			||||||
 | 
					      @updated_issue.issue_tags_relates.destroy_all & @updated_issue.issue_tags = @issue_tags || IssueTag.none unless issue_tag_ids.nil?
 | 
				
			||||||
 | 
					      @updated_issue.issue_tags_value = @issue_tags.order("id asc").pluck(:id).join(",") unless issue_tag_ids.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #Pm相关
 | 
				
			||||||
 | 
					      @updated_issue.pm_project_id = @target_pm_project_id unless @target_pm_project_id.nil?
 | 
				
			||||||
 | 
					      @updated_issue.pm_sprint_id = @pm_sprint_id unless @pm_sprint_id.nil?
 | 
				
			||||||
 | 
					      if @updated_issue.children_issues.count == 0 && @updated_issue.parent_id.nil?
 | 
				
			||||||
 | 
					        @updated_issue.pm_issue_type = @pm_issue_type unless @pm_issue_type.nil?
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      @updated_issue.root_id = @root_id unless @root_id.nil? #不为 nil的时候更新
 | 
				
			||||||
 | 
					      @updated_issue.root_id = nil if @root_id.try(:zero?) #为 0 的时候设置为 nil
 | 
				
			||||||
 | 
					      @updated_issue.time_scale = @time_scale unless @time_scale.nil?
 | 
				
			||||||
 | 
					      @updated_issue.project_id = @project_id unless @project_id.nil?
 | 
				
			||||||
 | 
					      @updated_issue.updated_on = Time.now
 | 
				
			||||||
 | 
					      @updated_issue.changer_id = @current_user.id
 | 
				
			||||||
 | 
					      @updated_issue.save!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      build_after_issue_journal_details if @updated_issue.previous_changes.present? # 操作记录
 | 
				
			||||||
 | 
					      build_previous_issue_changes
 | 
				
			||||||
 | 
					      build_cirle_blockchain_token if blockchain_token_num.present?
 | 
				
			||||||
 | 
					      unless @project.id.zero?
 | 
				
			||||||
 | 
					        # @信息发送
 | 
				
			||||||
 | 
					        AtmeService.call(current_user, @atme_receivers, @issue) unless receivers_login.blank?
 | 
				
			||||||
 | 
					        # 消息发送
 | 
				
			||||||
 | 
					        if Site.has_notice_menu?
 | 
				
			||||||
 | 
					          SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, @issue&.id, previous_issue_changes) unless previous_issue_changes.blank?
 | 
				
			||||||
 | 
					          SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id, add_assigner_ids) unless add_assigner_ids.blank?
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        # 触发webhook
 | 
				
			||||||
 | 
					        Rails.logger.info "################### 触发webhook"
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueUpdate', @updated_issue&.id, current_user.id, previous_issue_changes.except(:issue_tags_value, :assigned_to_id))
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @issue&.id, current_user.id, {issue_tag_ids: [before_issue_tag_ids, issue_tag_ids]}) unless issue_tag_ids.nil?
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @issue&.id, current_user.id, {assigner_ids: [before_assigner_ids, assigner_ids]}) unless assigner_ids.nil?
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      unlock("Api::Pm::Issues::UpdateService:#{project.id}:#{issue.id}")
 | 
				
			||||||
 | 
					      return @updated_issue
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def issue_load_attributes 
 | 
				
			||||||
 | 
					    if current_user.id == @updated_issue.author_id && !PullAttachedIssue.exists?(issue_id: @updated_issue, fixed: true)
 | 
				
			||||||
 | 
					      @updated_issue.blockchain_token_num = blockchain_token_num unless blockchain_token_num.nil?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    @updated_issue.status_id = status_id if status_id.present?
 | 
				
			||||||
 | 
					    @updated_issue.priority_id = priority_id if priority_id.present?
 | 
				
			||||||
 | 
					    @updated_issue.fixed_version_id = milestone_id unless milestone_id.nil? 
 | 
				
			||||||
 | 
					    @updated_issue.branch_name = branch_name unless branch_name.nil?
 | 
				
			||||||
 | 
					    @updated_issue.start_date = start_date unless start_date.nil? 
 | 
				
			||||||
 | 
					    @updated_issue.due_date = due_date unless due_date.nil? 
 | 
				
			||||||
 | 
					    @updated_issue.subject = subject if subject.present? 
 | 
				
			||||||
 | 
					    @updated_issue.description = description unless description.nil?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_assigner_participants
 | 
				
			||||||
 | 
					    if assigner_ids.blank? 
 | 
				
			||||||
 | 
					      @updated_issue.issue_participants.where(participant_type: "assigned").each(&:destroy!)
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      @updated_issue.issue_participants.where(participant_type: "assigned").where.not(participant_id: assigner_ids).each(&:destroy!)
 | 
				
			||||||
 | 
					      assigner_ids.each do |aid|
 | 
				
			||||||
 | 
					        next if @updated_issue.issue_participants.exists?(participant_type: "assigned", participant_id: aid)
 | 
				
			||||||
 | 
					        @updated_issue.issue_participants.new({participant_type: "assigned", participant_id: aid})
 | 
				
			||||||
 | 
					        @add_assigner_ids << aid
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_edit_participants 
 | 
				
			||||||
 | 
					    @updated_issue.issue_participants.new({participant_type: "edited", participant_id: current_user.id}) unless @updated_issue.issue_participants.exists?(participant_type: "edited", participant_id: current_user.id)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_atme_participants
 | 
				
			||||||
 | 
					    @atme_receivers.each do |receiver|
 | 
				
			||||||
 | 
					      next if @updated_issue.issue_participants.exists?(participant_type: "atme", participant_id: receiver.id)
 | 
				
			||||||
 | 
					      @updated_issue.issue_participants.new({participant_type: "atme", participant_id: receiver.id})
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_previous_issue_changes
 | 
				
			||||||
 | 
					    @previous_issue_changes.merge!(@updated_issue.previous_changes.slice("status_id", "priority_id", "fixed_version_id", "issue_tags_value", "branch_name", "subject").symbolize_keys)
 | 
				
			||||||
 | 
					    if @updated_issue.previous_changes[:start_date].present? 
 | 
				
			||||||
 | 
					      @previous_issue_changes.merge!(start_date: [@updated_issue.previous_changes[:start_date][0].to_s,  @updated_issue.previous_changes[:start_date][1].to_s])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    if @updated_issue.previous_changes[:due_date].present? 
 | 
				
			||||||
 | 
					      @previous_issue_changes.merge!(due_date: [@updated_issue.previous_changes[:due_date][0].to_s,  @updated_issue.previous_changes[:due_date][1].to_s])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_cirle_blockchain_token
 | 
				
			||||||
 | 
					    if @updated_issue.previous_changes["blockchain_token_num"].present? 
 | 
				
			||||||
 | 
					      unlock_balance_on_blockchain(@updated_issue&.author_id.to_s, @updated_issue.project_id.to_s, @updated_issue.previous_changes["blockchain_token_num"][0].to_i) if @updated_issue.previous_changes["blockchain_token_num"][0].present?
 | 
				
			||||||
 | 
					      lock_balance_on_blockchain(@updated_issue&.author_id.to_s, @updated_issue.project_id.to_s, @updated_issue.previous_changes["blockchain_token_num"][1].to_i) if @updated_issue.previous_changes["blockchain_token_num"][1].present?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_issue_project_trends
 | 
				
			||||||
 | 
					    if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][1] == 5
 | 
				
			||||||
 | 
					      @updated_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE})
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][0] == 5
 | 
				
			||||||
 | 
					      @updated_issue.project_trends.where(action_type: ProjectTrend::CLOSE).each(&:destroy!)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_after_issue_journal_details
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
 | 
					      # 更改标题
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["subject"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "subject", old_value: @updated_issue.previous_changes["subject"][0], value: @updated_issue.previous_changes["subject"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 更改描述
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["description"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "description", old_value: @updated_issue.previous_changes["description"][0], value: @updated_issue.previous_changes["description"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 修改状态
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["status_id"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: @updated_issue.pm_issue_type_string, prop_key: "status_id", old_value: @updated_issue.previous_changes["status_id"][0], value: @updated_issue.previous_changes["status_id"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 修改优先级
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["priority_id"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "priority_id", old_value: @updated_issue.previous_changes["priority_id"][0], value: @updated_issue.previous_changes["priority_id"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 修改工作项类型
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["pm_issue_type"].present? 
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "pm_issue_type", old_value: @updated_issue.previous_changes["pm_issue_type"][0], value: @updated_issue.previous_changes["pm_issue_type"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 修改迭代
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["pm_sprint_id"].present? 
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "pm_sprint_id", old_value: @updated_issue.previous_changes["pm_sprint_id"][0], value: @updated_issue.previous_changes["pm_sprint_id"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 修改代码库
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["project_id"].present? 
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "project_id", old_value: @updated_issue.previous_changes["project_id"][0], value: @updated_issue.previous_changes["project_id"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 修改里程碑
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["fixed_version_id"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "fixed_version_id", old_value: @updated_issue.previous_changes["fixed_version_id"][0], value: @updated_issue.previous_changes["fixed_version_id"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 更改分支
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["branch_name"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "branch_name", old_value: @updated_issue.previous_changes["branch_name"][0], value: @updated_issue.previous_changes["branch_name"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 更改开始时间
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["start_date"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "start_date", old_value: @updated_issue.previous_changes["start_date"][0], value: @updated_issue.previous_changes["start_date"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 更改结束时间
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["due_date"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "due_date", old_value: @updated_issue.previous_changes["due_date"][0], value: @updated_issue.previous_changes["due_date"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 更改预估工时
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["time_scale"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attr", prop_key: "time_scale", old_value: @updated_issue.previous_changes["time_scale"][0], value: @updated_issue.previous_changes["time_scale"][1]})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # 更改父工作项
 | 
				
			||||||
 | 
					      if @updated_issue.previous_changes["root_id"].present?
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: @updated_issue.pm_issue_type_string, prop_key: "root_id", old_value: @updated_issue.previous_changes["root_id"][0], value: @updated_issue.previous_changes["root_id"][1]})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 更改子工作项
 | 
				
			||||||
 | 
					        before_parent_issue = Issue.find_by_id(@updated_issue.previous_changes["root_id"][0])
 | 
				
			||||||
 | 
					        if before_parent_issue.present? 
 | 
				
			||||||
 | 
					          journal = before_parent_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					          journal.journal_details.create!({property: @updated_issue.pm_issue_type_string, prop_key: "tag_leaf_issue", old_value: @updated_issue.id.to_s})
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        after_parent_issue = Issue.find_by_id(@updated_issue.previous_changes["root_id"][1])
 | 
				
			||||||
 | 
					        if after_parent_issue.present? 
 | 
				
			||||||
 | 
					          journal = after_parent_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					          journal.journal_details.create!({property: @updated_issue.pm_issue_type_string, prop_key: "tag_leaf_issue", value: @updated_issue.id.to_s})
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    rescue
 | 
				
			||||||
 | 
					      raise Error, "创建操作记录失败!"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_assigner_issue_journal_details
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
 | 
					      # 更改负责人
 | 
				
			||||||
 | 
					      new_assigner_ids = @assigner_ids
 | 
				
			||||||
 | 
					      new_assigner_ids = [] if @assigner_ids.nil?
 | 
				
			||||||
 | 
					      now_assigner_ids = @updated_issue.assigners.pluck(:id)
 | 
				
			||||||
 | 
					      if !(now_assigner_ids.sort == new_assigner_ids.sort)
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "assigner", prop_key: "#{new_assigner_ids.size}", old_value: now_assigner_ids.join(","), value: new_assigner_ids.join(",")})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rescue
 | 
				
			||||||
 | 
					      raise Error, "创建操作记录失败!"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_issue_tag_issue_journal_details
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
 | 
					      # 更改标记
 | 
				
			||||||
 | 
					      new_issue_tag_ids = @issue_tag_ids
 | 
				
			||||||
 | 
					      new_issue_tag_ids = [] if @issue_tag_ids.nil?
 | 
				
			||||||
 | 
					      now_issue_tag_ids = @updated_issue.issue_tags.pluck(:id)
 | 
				
			||||||
 | 
					      if !(now_issue_tag_ids.sort == new_issue_tag_ids.sort)
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "issue_tag", prop_key: "#{new_issue_tag_ids.size}", old_value: now_issue_tag_ids.join(","), value: new_issue_tag_ids.join(",")})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    rescue
 | 
				
			||||||
 | 
					      raise Error, "创建操作记录失败!"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_attachment_issue_journal_details
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
 | 
					      # 更改附件
 | 
				
			||||||
 | 
					      new_attachment_ids = @attachment_ids
 | 
				
			||||||
 | 
					      new_attachment_ids = [] if @attachment_ids.nil?
 | 
				
			||||||
 | 
					      now_attachment_ids = @updated_issue.attachments.pluck(:id)
 | 
				
			||||||
 | 
					      if !(now_attachment_ids.sort == new_attachment_ids.sort)
 | 
				
			||||||
 | 
					        journal = @updated_issue.journals.create!({user_id: current_user.id})
 | 
				
			||||||
 | 
					        journal.journal_details.create!({property: "attachment", prop_key: "#{new_attachment_ids.size}", old_value: now_attachment_ids.join(","), value: new_attachment_ids.join(",")})
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    rescue
 | 
				
			||||||
 | 
					      raise Error, "创建操作记录失败!"
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					class Api::Pm::SprintIssues::ListService < ApplicationService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  include ActiveModel::Model 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  attr_reader :category, :pm_project_id, :pm_issue_type, :assigner_id, :priority_id, :status_id, :keyword, :current_user
 | 
				
			||||||
 | 
					  attr_reader :status_ids, :pm_issue_types
 | 
				
			||||||
 | 
					  attr_reader :sort_by, :sort_direction
 | 
				
			||||||
 | 
					  attr_accessor :queried_issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :category, inclusion: { in: %w[linked unlink], message: '请输入正确的Category'}
 | 
				
			||||||
 | 
					  validates :sort_by, inclusion: { in: %w[issues.status_id issues.created_on issues.updated_on issue_priorities.position] , message: '请输入正确的SortBy'}, allow_blank: true
 | 
				
			||||||
 | 
					  validates :sort_direction, inclusion: { in: %w[asc desc], message: '请输入正确的SortDirection'}, allow_blank: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :pm_project_id, :current_user, presence: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def initialize(params, current_user = nil)
 | 
				
			||||||
 | 
					    @category = params[:category] || "unlink"
 | 
				
			||||||
 | 
					    @pm_project_id = params[:pm_project_id]
 | 
				
			||||||
 | 
					    @pm_issue_type = params[:pm_issue_type]
 | 
				
			||||||
 | 
					    @assigner_id = params[:assigner_id]
 | 
				
			||||||
 | 
					    @priority_id = params[:priority_id]
 | 
				
			||||||
 | 
					    @status_id = params[:status_id]
 | 
				
			||||||
 | 
					    @keyword = params[:keyword]
 | 
				
			||||||
 | 
					    @status_ids = params[:status_ids].present? ? params[:status_ids].split(',') : []
 | 
				
			||||||
 | 
					    @pm_issue_types = params[:pm_issue_types].present? ? params[:pm_issue_types].split(',') : []
 | 
				
			||||||
 | 
					    @sort_by = params[:sort_by].present? ?  params[:sort_by] : 'issues.updated_on'
 | 
				
			||||||
 | 
					    @sort_direction = (params[:sort_direction].present? ? params[:sort_direction] : 'desc').downcase
 | 
				
			||||||
 | 
					    @current_user = current_user
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def call 
 | 
				
			||||||
 | 
					    raise Error, errors.full_messages.join(', ') unless valid? 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    issue_query_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @queried_issues
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					  def issue_query_data
 | 
				
			||||||
 | 
					     issues = @category == "unlink" ? Issue.where(pm_project_id: @pm_project_id, pm_sprint_id: [nil, 0]) : Issue.where(pm_project_id: @pm_project_id).where.not(pm_sprint_id: [nil, 0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     issues = issues.where(pm_issue_type: @pm_issue_type) if @pm_issue_type.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     issues = issues.joins(:assigners).where(users: {id: @assigner_id}) if @assigner_id.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     issues = issues.where(priority_id: @priority_id) if @priority_id.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     issues = issues.where(status_id: @status_id) if @status_id.present? 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     # status_ids 
 | 
				
			||||||
 | 
					     issues = issues.where(status_id: @status_ids) unless @status_ids.blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     # pm_issue_types
 | 
				
			||||||
 | 
					     issues = issues.where(pm_issue_type: @pm_issue_types) unless @pm_issue_types.blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     issues = issues.ransack(subject_cont: @keyword).result if @keyword.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     scope = issues.includes(:priority, :issue_status, :user, :show_assigners, :show_issue_tags, :version, :comment_journals)
 | 
				
			||||||
 | 
					     scope = scope.reorder("#{sort_by} #{sort_direction}").distinct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     @queried_issues = scope
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ class Api::V1::Issues::BatchDeleteService < ApplicationService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    project.incre_project_issue_cache_delete_count(@issues.size)
 | 
					    project.incre_project_issue_cache_delete_count(@issues.size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if Site.has_notice_menu?
 | 
					    if Site.has_notice_menu? && !project.id.zero?
 | 
				
			||||||
      @issues.each do |issue|
 | 
					      @issues.each do |issue|
 | 
				
			||||||
        SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id) 
 | 
					        SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id) 
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ class Api::V1::Issues::BatchUpdateService < ApplicationService
 | 
				
			||||||
  include Api::V1::Issues::Concerns::Loadable 
 | 
					  include Api::V1::Issues::Concerns::Loadable 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  attr_reader :project, :issues, :params, :current_user
 | 
					  attr_reader :project, :issues, :params, :current_user
 | 
				
			||||||
  attr_reader :status_id, :priority_id, :milestone_id
 | 
					  attr_reader :status_id, :priority_id, :milestone_id, :project_id
 | 
				
			||||||
  attr_reader :issue_tag_ids, :assigner_ids
 | 
					  attr_reader :issue_tag_ids, :assigner_ids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :project, :issues, :current_user, presence: true
 | 
					  validates :project, :issues, :current_user, presence: true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,10 @@ module Api::V1::Issues::Concerns::Checkable
 | 
				
			||||||
    raise ApplicationService::Error, "Milestone不存在!" unless Version.find_by_id(milestone_id).present?
 | 
					    raise ApplicationService::Error, "Milestone不存在!" unless Version.find_by_id(milestone_id).present?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def check_root_issue(issue, root_id)
 | 
				
			||||||
 | 
					    raise ApplicationService::Error, "父工作项与当前工作项已存在父子关系!" if Issue.full_children_issues(issue).map{|i| i.id}.include?(root_id)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def check_issue_tags(issue_tag_ids)
 | 
					  def check_issue_tags(issue_tag_ids)
 | 
				
			||||||
    raise ApplicationService::Error, "请输入正确的标记ID数组!" unless issue_tag_ids.is_a?(Array)
 | 
					    raise ApplicationService::Error, "请输入正确的标记ID数组!" unless issue_tag_ids.is_a?(Array)
 | 
				
			||||||
    raise ApplicationService::Error, "最多可选择3个标记" if issue_tag_ids.size > 3
 | 
					    raise ApplicationService::Error, "最多可选择3个标记" if issue_tag_ids.size > 3
 | 
				
			||||||
| 
						 | 
					@ -47,6 +51,7 @@ module Api::V1::Issues::Concerns::Checkable
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def check_blockchain_token_num(user_id, project_id, blockchain_token_num, now_blockchain_token_num=0)
 | 
					  def check_blockchain_token_num(user_id, project_id, blockchain_token_num, now_blockchain_token_num=0)
 | 
				
			||||||
 | 
					    return if project_id.zero?
 | 
				
			||||||
    left_blockchain_token_num = Blockchain::BalanceQueryOneProject.call({"user_id": user_id, "project_id": project_id}) rescue 0
 | 
					    left_blockchain_token_num = Blockchain::BalanceQueryOneProject.call({"user_id": user_id, "project_id": project_id}) rescue 0
 | 
				
			||||||
    raise ApplicationService::Error, "用户Token不足。" if blockchain_token_num.to_i > (left_blockchain_token_num+now_blockchain_token_num).to_i
 | 
					    raise ApplicationService::Error, "用户Token不足。" if blockchain_token_num.to_i > (left_blockchain_token_num+now_blockchain_token_num).to_i
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@ module Api::V1::Issues::Concerns::Loadable
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def load_attachments(attachment_ids)
 | 
					  def load_attachments(attachment_ids)
 | 
				
			||||||
    @attachments = Attachment.where("id in (?) or uuid in (?)", attachment_ids, attachment_ids)
 | 
					    @attachments = Attachment.where("BINARY id in (?) or uuid in (?)", attachment_ids, attachment_ids)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def load_atme_receivers(receivers_login)
 | 
					  def load_atme_receivers(receivers_login)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ class Api::V1::Issues::CreateService < ApplicationService
 | 
				
			||||||
  include Api::V1::Issues::Concerns::Loadable 
 | 
					  include Api::V1::Issues::Concerns::Loadable 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  attr_reader :project, :current_user
 | 
					  attr_reader :project, :current_user
 | 
				
			||||||
  attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num
 | 
					  attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num, :root_subject
 | 
				
			||||||
  attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login
 | 
					  attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login
 | 
				
			||||||
  attr_accessor :created_issue
 | 
					  attr_accessor :created_issue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,10 +29,17 @@ class Api::V1::Issues::CreateService < ApplicationService
 | 
				
			||||||
    @assigner_ids = params[:assigner_ids]
 | 
					    @assigner_ids = params[:assigner_ids]
 | 
				
			||||||
    @attachment_ids = params[:attachment_ids]
 | 
					    @attachment_ids = params[:attachment_ids]
 | 
				
			||||||
    @receivers_login = params[:receivers_login]
 | 
					    @receivers_login = params[:receivers_login]
 | 
				
			||||||
 | 
					    @pm_project_id = params[:pm_project_id]
 | 
				
			||||||
 | 
					    @pm_sprint_id = params[:pm_sprint_id]
 | 
				
			||||||
 | 
					    @pm_issue_type = params[:pm_issue_type]
 | 
				
			||||||
 | 
					    @root_id = params[:root_id]
 | 
				
			||||||
 | 
					    @time_scale = params[:time_scale]
 | 
				
			||||||
 | 
					    @linkable_id = params[:link_able_id]
 | 
				
			||||||
 | 
					    @root_subject = params[:root_subject]
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call 
 | 
					  def call
 | 
				
			||||||
    raise Error, errors.full_messages.join(", ") unless valid? 
 | 
					    raise Error, errors.full_messages.join(', ') unless valid? 
 | 
				
			||||||
    ActiveRecord::Base.transaction do 
 | 
					    ActiveRecord::Base.transaction do 
 | 
				
			||||||
      check_issue_status(status_id)
 | 
					      check_issue_status(status_id)
 | 
				
			||||||
      check_issue_priority(priority_id)
 | 
					      check_issue_priority(priority_id)
 | 
				
			||||||
| 
						 | 
					@ -46,7 +53,6 @@ class Api::V1::Issues::CreateService < ApplicationService
 | 
				
			||||||
      load_attachments(attachment_ids) unless attachment_ids.blank?
 | 
					      load_attachments(attachment_ids) unless attachment_ids.blank?
 | 
				
			||||||
      load_issue_tags(issue_tag_ids) unless issue_tag_ids.blank?
 | 
					      load_issue_tags(issue_tag_ids) unless issue_tag_ids.blank?
 | 
				
			||||||
      load_atme_receivers(receivers_login) unless receivers_login.blank?
 | 
					      load_atme_receivers(receivers_login) unless receivers_login.blank?
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
      try_lock("Api::V1::Issues::CreateService:#{project.id}") # 开始写数据,加锁
 | 
					      try_lock("Api::V1::Issues::CreateService:#{project.id}") # 开始写数据,加锁
 | 
				
			||||||
      @created_issue = Issue.new(issue_attributes)
 | 
					      @created_issue = Issue.new(issue_attributes)
 | 
				
			||||||
      build_author_participants
 | 
					      build_author_participants
 | 
				
			||||||
| 
						 | 
					@ -57,39 +63,54 @@ class Api::V1::Issues::CreateService < ApplicationService
 | 
				
			||||||
      @created_issue.assigners = @assigners unless assigner_ids.blank?
 | 
					      @created_issue.assigners = @assigners unless assigner_ids.blank?
 | 
				
			||||||
      @created_issue.attachments = @attachments unless attachment_ids.blank?
 | 
					      @created_issue.attachments = @attachments unless attachment_ids.blank?
 | 
				
			||||||
      @created_issue.issue_tags = @issue_tags unless issue_tag_ids.blank?
 | 
					      @created_issue.issue_tags = @issue_tags unless issue_tag_ids.blank?
 | 
				
			||||||
      
 | 
					      @created_issue.pm_project_id = @pm_project_id
 | 
				
			||||||
      @created_issue.issue_tags_value = @issue_tags.order("id asc").pluck(:id).join(",") unless issue_tag_ids.blank?
 | 
					      @created_issue.pm_sprint_id = @pm_sprint_id
 | 
				
			||||||
 | 
					      @created_issue.pm_issue_type = @pm_issue_type
 | 
				
			||||||
 | 
					      if @root_subject.present? && @pm_issue_type.to_i == 4
 | 
				
			||||||
 | 
					        @root_issue = Issue.find_by(subject: @root_subject, pm_issue_type: 4, pm_project_id: @pm_project_id) 
 | 
				
			||||||
 | 
					        unless @root_issue.present? 
 | 
				
			||||||
 | 
					          @root_issue = Issue.create(subject: @root_subject, pm_issue_type: 4, pm_project_id: @pm_project_id, status_id: 1, priority_id: 1, tracker_id: Tracker.first.id, project_id: @project.id, author_id: current_user.id)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        @created_issue.root_id = @root_issue.id
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        @created_issue.root_id = @root_id
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      @created_issue.time_scale = @time_scale
 | 
				
			||||||
 | 
					      @created_issue.issue_tags_value = @issue_tags.order('id asc').pluck(:id).join(',') unless issue_tag_ids.blank?
 | 
				
			||||||
 | 
					      @created_issue.changer_id = @current_user.id
 | 
				
			||||||
      @created_issue.save!
 | 
					      @created_issue.save!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if Site.has_blockchain? && @project.use_blockchain 
 | 
					      PmLink.create(be_linkable_type: 'Issue', be_linkable_id: @created_issue.id, linkable_type: 'Issue', linkable_id: @linkable_id) if @linkable_id.present?
 | 
				
			||||||
 | 
					      if Site.has_blockchain? && @project.use_blockchain
 | 
				
			||||||
        if @created_issue.blockchain_token_num.present? && @created_issue.blockchain_token_num > 0 
 | 
					        if @created_issue.blockchain_token_num.present? && @created_issue.blockchain_token_num > 0 
 | 
				
			||||||
          Blockchain::CreateIssue.call({user_id: current_user.id, project_id: @created_issue.project_id, token_num: @created_issue.blockchain_token_num})
 | 
					          Blockchain::CreateIssue.call({user_id: current_user.id, project_id: @created_issue.project_id, token_num: @created_issue.blockchain_token_num})
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
  
 | 
					
 | 
				
			||||||
        push_activity_2_blockchain("issue_create", @created_issue) 
 | 
					        push_activity_2_blockchain('issue_create', @created_issue) 
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      project.del_project_issue_cache_delete_count # 把缓存里存储项目删除issue的个数清除掉
 | 
					      project.del_project_issue_cache_delete_count # 把缓存里存储项目删除issue的个数清除掉
 | 
				
			||||||
 | 
					      unless @project.id.zero?
 | 
				
			||||||
 | 
					        # 新增时向grimoirelab推送事件
 | 
				
			||||||
 | 
					        IssueWebhookJob.set(wait: 5.seconds).perform_later(@created_issue.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # 新增时向grimoirelab推送事件
 | 
					        # @信息发送
 | 
				
			||||||
      IssueWebhookJob.set(wait: 5.seconds).perform_later(@created_issue.id)
 | 
					        AtmeService.call(current_user, @atme_receivers, @created_issue) unless receivers_login.blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # @信息发送
 | 
					        # 发消息
 | 
				
			||||||
      AtmeService.call(current_user, @atme_receivers, @created_issue) unless receivers_login.blank?
 | 
					        if Site.has_notice_menu?
 | 
				
			||||||
 | 
					          SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @created_issue&.id, assigner_ids) unless assigner_ids.blank?
 | 
				
			||||||
 | 
					          SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @created_issue&.id)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # 发消息
 | 
					        # 触发webhook
 | 
				
			||||||
      if Site.has_notice_menu?
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueCreate', @created_issue&.id, current_user.id)
 | 
				
			||||||
        SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @created_issue&.id, assigner_ids) unless assigner_ids.blank?
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @created_issue&.id, current_user.id, {issue_tag_ids: [[], issue_tag_ids]}) unless issue_tag_ids.blank?
 | 
				
			||||||
        SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @created_issue&.id)
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @created_issue&.id, current_user.id, {assigner_ids: [[], assigner_ids]}) unless assigner_ids.blank?
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					 | 
				
			||||||
      # 触发webhook
 | 
					 | 
				
			||||||
      TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueCreate', @created_issue&.id, current_user.id)
 | 
					 | 
				
			||||||
      TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @created_issue&.id, current_user.id, {issue_tag_ids: [[], issue_tag_ids]}) unless issue_tag_ids.blank?
 | 
					 | 
				
			||||||
      TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @created_issue&.id, current_user.id, {assigner_ids: [[], assigner_ids]}) unless assigner_ids.blank?
 | 
					 | 
				
			||||||
      unlock("Api::V1::Issues::CreateService:#{project.id}") # 结束写数据,解锁
 | 
					      unlock("Api::V1::Issues::CreateService:#{project.id}") # 结束写数据,解锁
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    
 | 
					
 | 
				
			||||||
    return @created_issue
 | 
					    return @created_issue
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,8 +125,8 @@ class Api::V1::Issues::CreateService < ApplicationService
 | 
				
			||||||
      status_id: status_id, 
 | 
					      status_id: status_id, 
 | 
				
			||||||
      priority_id: priority_id,
 | 
					      priority_id: priority_id,
 | 
				
			||||||
      project_issues_index: (project.get_last_project_issues_index + 1),
 | 
					      project_issues_index: (project.get_last_project_issues_index + 1),
 | 
				
			||||||
      issue_type: "1",
 | 
					      issue_type: '1',
 | 
				
			||||||
      issue_classify: "issue"
 | 
					      issue_classify: 'issue'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    issue_attributes.merge!({description: description}) if description.present?
 | 
					    issue_attributes.merge!({description: description}) if description.present?
 | 
				
			||||||
| 
						 | 
					@ -119,28 +140,29 @@ class Api::V1::Issues::CreateService < ApplicationService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def build_author_participants 
 | 
					  def build_author_participants 
 | 
				
			||||||
    @created_issue.issue_participants.new({participant_type: "authored", participant_id: current_user.id})
 | 
					    @created_issue.issue_participants.new({participant_type: 'authored', participant_id: current_user.id})
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def build_assigner_participants
 | 
					  def build_assigner_participants
 | 
				
			||||||
    assigner_ids.each do |aid|
 | 
					    assigner_ids.each do |aid|
 | 
				
			||||||
      @created_issue.issue_participants.new({participant_type: "assigned", participant_id: aid})
 | 
					      @created_issue.issue_participants.new({participant_type: 'assigned', participant_id: aid})
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def build_atme_participants
 | 
					  def build_atme_participants
 | 
				
			||||||
    @atme_receivers.each do |receiver|
 | 
					    @atme_receivers.each do |receiver|
 | 
				
			||||||
      @created_issue.issue_participants.new({participant_type: "atme", participant_id: receiver.id})
 | 
					      @created_issue.issue_participants.new({participant_type: 'atme', participant_id: receiver.id})
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def build_issue_project_trends
 | 
					  def build_issue_project_trends
 | 
				
			||||||
    @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: "create"})
 | 
					    return if @project.id == 0
 | 
				
			||||||
 | 
					    @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: 'create'})
 | 
				
			||||||
    @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE}) if status_id.to_i == 5
 | 
					    @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE}) if status_id.to_i == 5
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def build_issue_journal_details
 | 
					  def build_issue_journal_details
 | 
				
			||||||
    journal = @created_issue.journals.new({user_id: current_user.id})
 | 
					    journal = @created_issue.journals.new({user_id: current_user.id})
 | 
				
			||||||
    journal.journal_details.new({property: "issue", prop_key: 1, old_value: '', value: ''})
 | 
					    journal.journal_details.new({property: 'issue', prop_key: 1, old_value: '', value: ''})
 | 
				
			||||||
  end 
 | 
					  end 
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -16,14 +16,16 @@ class Api::V1::Issues::DeleteService < ApplicationService
 | 
				
			||||||
    try_lock("Api::V1::Issues::DeleteService:#{project.id}") # 开始写数据,加锁
 | 
					    try_lock("Api::V1::Issues::DeleteService:#{project.id}") # 开始写数据,加锁
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    delete_issue
 | 
					    delete_issue
 | 
				
			||||||
 | 
					    #删除双向关联
 | 
				
			||||||
 | 
					    PmLink.where(be_linkable_id: @issue.id, be_linkable_type: 'Issue').or(PmLink.where(linkable_id: @issue.id, linkable_type: 'Issue')).map(&:destroy)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    project.incre_project_issue_cache_delete_count
 | 
					    project.incre_project_issue_cache_delete_count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if Site.has_blockchain? && @project.use_blockchain
 | 
					    if Site.has_blockchain? && @project.use_blockchain && !project.id.zero?
 | 
				
			||||||
      unlock_balance_on_blockchain(@issue.author_id.to_s, @project.id.to_s, @issue.blockchain_token_num.to_i) if @issue.blockchain_token_num.present?
 | 
					      unlock_balance_on_blockchain(@issue.author_id.to_s, @project.id.to_s, @issue.blockchain_token_num.to_i) if @issue.blockchain_token_num.present?
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if Site.has_notice_menu?
 | 
					    if Site.has_notice_menu? && !project.id.zero?
 | 
				
			||||||
      SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id) 
 | 
					      SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id) 
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
| 
						 | 
					@ -37,5 +39,4 @@ class Api::V1::Issues::DeleteService < ApplicationService
 | 
				
			||||||
  def delete_issue
 | 
					  def delete_issue
 | 
				
			||||||
    raise Error, "删除疑修失败!" unless issue.destroy!
 | 
					    raise Error, "删除疑修失败!" unless issue.destroy!
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -2,17 +2,18 @@ class Api::V1::Issues::ListService < ApplicationService
 | 
				
			||||||
  include ActiveModel::Model
 | 
					  include ActiveModel::Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  attr_reader :project, :only_name, :category, :participant_category, :keyword, :author_id, :issue_tag_ids
 | 
					  attr_reader :project, :only_name, :category, :participant_category, :keyword, :author_id, :issue_tag_ids
 | 
				
			||||||
  attr_reader :begin_date, :end_date
 | 
					  attr_reader :begin_date, :end_date, :update_begin_date, :update_end_date
 | 
				
			||||||
  attr_reader :milestone_id, :assigner_id, :status_id, :sort_by, :sort_direction, :current_user
 | 
					  attr_reader :milestone_id, :assigner_id, :status_id, :priority_id, :sort_by, :sort_direction, :current_user
 | 
				
			||||||
  attr_accessor :queried_issues, :total_issues_count, :closed_issues_count, :opened_issues_count
 | 
					  attr_reader :pm_project_id, :pm_project_ids, :pm_sprint_id, :root_id, :pm_issue_type, :status_ids, :ids, :exclude_ids, :pm_issue_types
 | 
				
			||||||
 | 
					  attr_accessor :queried_issues, :total_issues_count, :closed_issues_count, :opened_issues_count, :complete_issues_count, :participator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :category, inclusion: {in: %w(all opened closed), message: '请输入正确的Category'}
 | 
					  validates :category, inclusion: { in: %w[all opened closed], message: '请输入正确的Category'}
 | 
				
			||||||
  validates :participant_category, inclusion: {in: %w(all aboutme authoredme assignedme atme), message: '请输入正确的ParticipantCategory'}
 | 
					  validates :participant_category, inclusion: { in: %w[all aboutme authoredme assignedme atme], message: '请输入正确的ParticipantCategory'}
 | 
				
			||||||
  validates :sort_by, inclusion: {in: ['issues.created_on', 'issues.updated_on', 'issues.blockchain_token_num', 'issue_priorities.position'], message: '请输入正确的SortBy'}, allow_blank: true
 | 
					  validates :sort_by, inclusion: { in: %w[issues.created_on issues.updated_on issues.blockchain_token_num issue_priorities.position issues.start_date issues.due_date issues.status_id] , message: '请输入正确的SortBy'}, allow_blank: true
 | 
				
			||||||
  validates :sort_direction, inclusion: {in: %w(asc desc), message: '请输入正确的SortDirection'}, allow_blank: true
 | 
					  validates :sort_direction, inclusion: { in: %w[asc desc], message: '请输入正确的SortDirection'}, allow_blank: true
 | 
				
			||||||
  validates :current_user, presence: true
 | 
					  validates :current_user, presence: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def initialize(project, params, current_user=nil)
 | 
					  def initialize(project, params, current_user = nil)
 | 
				
			||||||
    @project = project
 | 
					    @project = project
 | 
				
			||||||
    @only_name = params[:only_name]
 | 
					    @only_name = params[:only_name]
 | 
				
			||||||
    @category = params[:category] || 'all'
 | 
					    @category = params[:category] || 'all'
 | 
				
			||||||
| 
						 | 
					@ -22,20 +23,39 @@ class Api::V1::Issues::ListService < ApplicationService
 | 
				
			||||||
    @issue_tag_ids = params[:issue_tag_ids].present? ? params[:issue_tag_ids].split(',') : []
 | 
					    @issue_tag_ids = params[:issue_tag_ids].present? ? params[:issue_tag_ids].split(',') : []
 | 
				
			||||||
    @milestone_id = params[:milestone_id]
 | 
					    @milestone_id = params[:milestone_id]
 | 
				
			||||||
    @assigner_id = params[:assigner_id]
 | 
					    @assigner_id = params[:assigner_id]
 | 
				
			||||||
 | 
					    @priority_id = params[:priority_id]
 | 
				
			||||||
    @status_id = params[:status_id]
 | 
					    @status_id = params[:status_id]
 | 
				
			||||||
    @begin_date = params[:begin_date]
 | 
					    @begin_date = params[:begin_date]
 | 
				
			||||||
    @end_date = params[:end_date]
 | 
					    @end_date = params[:end_date]
 | 
				
			||||||
 | 
					    @update_begin_date = params[:update_begin_date]
 | 
				
			||||||
 | 
					    @update_end_date = params[:update_end_date]
 | 
				
			||||||
    @sort_by = params[:sort_by].present? ?  params[:sort_by] : 'issues.updated_on'
 | 
					    @sort_by = params[:sort_by].present? ?  params[:sort_by] : 'issues.updated_on'
 | 
				
			||||||
 | 
					    @pm_project_id = params[:pm_project_id]
 | 
				
			||||||
 | 
					    @pm_project_ids = params[:pm_project_ids]
 | 
				
			||||||
 | 
					    @pm_sprint_id = params[:pm_sprint_id]
 | 
				
			||||||
 | 
					    @root_id = params[:root_id]
 | 
				
			||||||
 | 
					    @pm_issue_type = params[:pm_issue_type]
 | 
				
			||||||
 | 
					    @ids = params[:ids]
 | 
				
			||||||
 | 
					    @exclude_ids = params[:exclude_ids]
 | 
				
			||||||
 | 
					    @status_ids = params[:status_ids].present? ? params[:status_ids].split(',') : []
 | 
				
			||||||
 | 
					    @pm_issue_types = params[:pm_issue_types].present? ? params[:pm_issue_types].split(',') : []
 | 
				
			||||||
    @sort_direction = (params[:sort_direction].present? ? params[:sort_direction] : 'desc').downcase
 | 
					    @sort_direction = (params[:sort_direction].present? ? params[:sort_direction] : 'desc').downcase
 | 
				
			||||||
 | 
					    @participator = params[:participator_id].present? ? User.find_by_id(params[:participator_id]) : current_user
 | 
				
			||||||
    @current_user = current_user
 | 
					    @current_user = current_user
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call 
 | 
					  def call 
 | 
				
			||||||
    raise Error, errors.full_messages.join(', ') unless valid?
 | 
					    raise Error, errors.full_messages.join(', ') unless valid?
 | 
				
			||||||
    # begin
 | 
					    # begin
 | 
				
			||||||
      issue_query_data
 | 
					    issue_query_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {data: queried_issues, total_issues_count: @total_issues_count, closed_issues_count: @closed_issues_count, opened_issues_count: @opened_issues_count}
 | 
					    {
 | 
				
			||||||
 | 
					      data: queried_issues, 
 | 
				
			||||||
 | 
					      total_issues_count: @total_issues_count, 
 | 
				
			||||||
 | 
					      closed_issues_count: @closed_issues_count, 
 | 
				
			||||||
 | 
					      opened_issues_count: @opened_issues_count,
 | 
				
			||||||
 | 
					      complete_issues_count: @complete_issues_count
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    # rescue 
 | 
					    # rescue 
 | 
				
			||||||
    #   raise Error, "服务器错误,请联系系统管理员!"
 | 
					    #   raise Error, "服务器错误,请联系系统管理员!"
 | 
				
			||||||
    # end
 | 
					    # end
 | 
				
			||||||
| 
						 | 
					@ -43,19 +63,18 @@ class Api::V1::Issues::ListService < ApplicationService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private 
 | 
					  private 
 | 
				
			||||||
  def issue_query_data 
 | 
					  def issue_query_data 
 | 
				
			||||||
    issues = @project.issues.issue_issue
 | 
					    issues = @project&.id.zero? ? Issue.issue_issue : @project.issues.issue_issue
 | 
				
			||||||
 | 
					    @total_issues_count = issues.where(pm_issue_type:[1, 2, 3]).distinct.size
 | 
				
			||||||
    case participant_category
 | 
					    case participant_category
 | 
				
			||||||
    when 'aboutme' # 关于我的
 | 
					    when 'aboutme' # 关于我的
 | 
				
			||||||
      issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: %w(authored assigned atme), participant_id: current_user&.id})
 | 
					      issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: %w[authored assigned atme], participant_id: participator&.id})
 | 
				
			||||||
    when 'authoredme' # 我创建的
 | 
					    when 'authoredme' # 我创建的
 | 
				
			||||||
      issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'authored', participant_id: current_user&.id})
 | 
					      issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'authored', participant_id: participator&.id})
 | 
				
			||||||
    when 'assignedme' # 我负责的
 | 
					    when 'assignedme' # 我负责的
 | 
				
			||||||
      issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'assigned', participant_id: current_user&.id})
 | 
					      issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'assigned', participant_id: participator&.id})
 | 
				
			||||||
    when 'atme' # @我的
 | 
					    when 'atme' # @我的
 | 
				
			||||||
      issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'atme', participant_id: current_user&.id})
 | 
					      issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'atme', participant_id: participator&.id})
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # author_id
 | 
					    # author_id
 | 
				
			||||||
    issues = issues.where(author_id: author_id) if author_id.present?
 | 
					    issues = issues.where(author_id: author_id) if author_id.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,6 +96,26 @@ class Api::V1::Issues::ListService < ApplicationService
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #pm相关
 | 
				
			||||||
 | 
					    # root_id# -1 查一级目录
 | 
				
			||||||
 | 
					    issues = if root_id.to_i == -1
 | 
				
			||||||
 | 
					               issues.where(root_id: nil)
 | 
				
			||||||
 | 
					             elsif root_id.to_i.positive?
 | 
				
			||||||
 | 
					               issues.where(root_id: root_id)
 | 
				
			||||||
 | 
					             else
 | 
				
			||||||
 | 
					               issues
 | 
				
			||||||
 | 
					             end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # pm_issue_type
 | 
				
			||||||
 | 
					    issues = issues.where(pm_issue_type: pm_issue_type) if pm_issue_type.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # pm_project_id
 | 
				
			||||||
 | 
					    issues = issues.where(pm_project_id: pm_project_id) if pm_project_id.present?
 | 
				
			||||||
 | 
					    issues = issues.where(pm_project_id: pm_project_ids.to_s.split(",")) if pm_project_ids.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # pm_sprint_id
 | 
				
			||||||
 | 
					    issues = issues.where(pm_sprint_id: pm_sprint_id) if pm_sprint_id.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # assigner_id
 | 
					    # assigner_id
 | 
				
			||||||
    if assigner_id.present?
 | 
					    if assigner_id.present?
 | 
				
			||||||
      if assigner_id.to_i == -1 
 | 
					      if assigner_id.to_i == -1 
 | 
				
			||||||
| 
						 | 
					@ -89,16 +128,36 @@ class Api::V1::Issues::ListService < ApplicationService
 | 
				
			||||||
    # status_id
 | 
					    # status_id
 | 
				
			||||||
    issues = issues.where(status_id: status_id) if status_id.present? && category != 'closed'
 | 
					    issues = issues.where(status_id: status_id) if status_id.present? && category != 'closed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # priority_id
 | 
				
			||||||
 | 
					    issues = issues.where(priority_id: priority_id) if priority_id.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # status_ids 
 | 
				
			||||||
 | 
					    issues = issues.where(status_id: status_ids) unless status_ids.blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # pm_issue_types
 | 
				
			||||||
 | 
					    issues = issues.where(pm_issue_type: pm_issue_types) unless pm_issue_types.blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # ids
 | 
				
			||||||
 | 
					    issues = issues.where(id: ids.to_s.split(",")) if ids.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # exclude_ids
 | 
				
			||||||
 | 
					    issues = issues.where.not(id: exclude_ids.to_s.split(",")) if exclude_ids.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if begin_date&.present? || end_date&.present?
 | 
					    if begin_date&.present? || end_date&.present?
 | 
				
			||||||
      issues = issues.where('issues.created_on between ? and ?', begin_date&.present? ? begin_date.to_time : Time.now.beginning_of_day, end_date&.present? ? end_date.to_time.end_of_day : Time.now.end_of_day)
 | 
					      issues = issues.where('issues.created_on between ? and ?', begin_date&.present? ? begin_date.to_time : Time.now.beginning_of_day, end_date&.present? ? end_date.to_time.end_of_day : Time.now.end_of_day)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if update_begin_date&.present? || update_end_date&.present?
 | 
				
			||||||
 | 
					      issues = issues.where('issues.updated_on between ? and ?', update_begin_date&.present? ? update_begin_date.to_time : Time.now.beginning_of_day, update_end_date&.present? ? update_end_date.to_time.end_of_day : Time.now.end_of_day)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # keyword
 | 
					    # keyword
 | 
				
			||||||
    issues = issues.ransack(id_or_project_issues_index_eq: keyword).result.or(issues.ransack(subject_or_description_cont: keyword).result) if keyword.present?
 | 
					    issues = issues.ransack(id_or_project_issues_index_eq: keyword).result.or(issues.ransack(subject_or_description_cont: keyword).result) if keyword.present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @total_issues_count = issues.distinct.size
 | 
					
 | 
				
			||||||
    @closed_issues_count = issues.closed.distinct.size
 | 
					    @closed_issues_count = issues.closed.distinct.size
 | 
				
			||||||
    @opened_issues_count = issues.opened.distinct.size
 | 
					    @opened_issues_count = issues.opened.distinct.size
 | 
				
			||||||
 | 
					    @complete_issues_count = issues.closed.distinct.size + issues.where(status_id: 3).distinct.size - issues.where(pm_issue_type: 3, status_id: 3).distinct.size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    case category 
 | 
					    case category 
 | 
				
			||||||
    when 'closed'
 | 
					    when 'closed'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,8 @@ class Api::V1::Issues::UpdateService < ApplicationService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  attr_reader :project, :issue, :current_user
 | 
					  attr_reader :project, :issue, :current_user
 | 
				
			||||||
  attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num
 | 
					  attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num
 | 
				
			||||||
  attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login, :before_issue_tag_ids, :before_assigner_ids
 | 
					  attr_reader :target_pm_project_id, :pm_sprint_id, :pm_issue_type, :root_id, :time_scale
 | 
				
			||||||
 | 
					  attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login, :before_issue_tag_ids, :before_assigner_ids, :project_id
 | 
				
			||||||
  attr_accessor :add_assigner_ids, :previous_issue_changes, :updated_issue, :atme_receivers
 | 
					  attr_accessor :add_assigner_ids, :previous_issue_changes, :updated_issue, :atme_receivers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :project, :issue, :current_user, presence: true
 | 
					  validates :project, :issue, :current_user, presence: true
 | 
				
			||||||
| 
						 | 
					@ -30,6 +31,12 @@ class Api::V1::Issues::UpdateService < ApplicationService
 | 
				
			||||||
    @before_assigner_ids = issue.assigners.pluck(:id)
 | 
					    @before_assigner_ids = issue.assigners.pluck(:id)
 | 
				
			||||||
    @attachment_ids = params[:attachment_ids]
 | 
					    @attachment_ids = params[:attachment_ids]
 | 
				
			||||||
    @receivers_login = params[:receivers_login]
 | 
					    @receivers_login = params[:receivers_login]
 | 
				
			||||||
 | 
					    @target_pm_project_id = params[:target_pm_project_id]
 | 
				
			||||||
 | 
					    @pm_sprint_id = params[:pm_sprint_id]
 | 
				
			||||||
 | 
					    @pm_issue_type = params[:pm_issue_type]
 | 
				
			||||||
 | 
					    @root_id = params[:root_id]
 | 
				
			||||||
 | 
					    @time_scale = params[:time_scale]
 | 
				
			||||||
 | 
					    @project_id = params[:project_id]
 | 
				
			||||||
    @add_assigner_ids = []
 | 
					    @add_assigner_ids = []
 | 
				
			||||||
    @previous_issue_changes = {}
 | 
					    @previous_issue_changes = {}
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					@ -40,6 +47,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
 | 
				
			||||||
      check_issue_status(status_id) if status_id.present?
 | 
					      check_issue_status(status_id) if status_id.present?
 | 
				
			||||||
      check_issue_priority(priority_id) if priority_id.present?
 | 
					      check_issue_priority(priority_id) if priority_id.present?
 | 
				
			||||||
      check_milestone(milestone_id) if milestone_id.present?
 | 
					      check_milestone(milestone_id) if milestone_id.present?
 | 
				
			||||||
 | 
					      check_root_issue(issue, root_id) if root_id.present?
 | 
				
			||||||
      check_issue_tags(issue_tag_ids) unless issue_tag_ids.nil?
 | 
					      check_issue_tags(issue_tag_ids) unless issue_tag_ids.nil?
 | 
				
			||||||
      check_assigners(assigner_ids) unless assigner_ids.nil?
 | 
					      check_assigners(assigner_ids) unless assigner_ids.nil?
 | 
				
			||||||
      check_attachments(attachment_ids) unless attachment_ids.nil?
 | 
					      check_attachments(attachment_ids) unless attachment_ids.nil?
 | 
				
			||||||
| 
						 | 
					@ -68,28 +76,39 @@ class Api::V1::Issues::UpdateService < ApplicationService
 | 
				
			||||||
      @updated_issue.issue_tags_relates.destroy_all & @updated_issue.issue_tags = @issue_tags || IssueTag.none unless issue_tag_ids.nil?
 | 
					      @updated_issue.issue_tags_relates.destroy_all & @updated_issue.issue_tags = @issue_tags || IssueTag.none unless issue_tag_ids.nil?
 | 
				
			||||||
      @updated_issue.issue_tags_value = @issue_tags.order("id asc").pluck(:id).join(",") unless issue_tag_ids.nil?
 | 
					      @updated_issue.issue_tags_value = @issue_tags.order("id asc").pluck(:id).join(",") unless issue_tag_ids.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #Pm相关
 | 
				
			||||||
 | 
					      @updated_issue.pm_project_id = @target_pm_project_id unless @target_pm_project_id.nil?
 | 
				
			||||||
 | 
					      @updated_issue.pm_sprint_id = @pm_sprint_id unless @pm_sprint_id.nil?
 | 
				
			||||||
 | 
					      if @updated_issue.children_issues.count == 0 && @updated_issue.parent_id.nil?
 | 
				
			||||||
 | 
					        @updated_issue.pm_issue_type = @pm_issue_type unless @pm_issue_type.nil?
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      @updated_issue.root_id = @root_id unless @root_id.nil? #不为 nil的时候更新
 | 
				
			||||||
 | 
					      @updated_issue.root_id = nil if @root_id.try(:zero?) #为 0 的时候设置为 nil
 | 
				
			||||||
 | 
					      @updated_issue.time_scale = @time_scale unless @time_scale.nil?
 | 
				
			||||||
 | 
					      @updated_issue.project_id = @project_id unless @project_id.nil?
 | 
				
			||||||
      @updated_issue.updated_on = Time.now
 | 
					      @updated_issue.updated_on = Time.now
 | 
				
			||||||
 | 
					      @updated_issue.changer_id = @current_user.id
 | 
				
			||||||
      @updated_issue.save!
 | 
					      @updated_issue.save!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      build_after_issue_journal_details if @updated_issue.previous_changes.present? # 操作记录
 | 
					      build_after_issue_journal_details if @updated_issue.previous_changes.present? # 操作记录
 | 
				
			||||||
      build_previous_issue_changes
 | 
					      build_previous_issue_changes
 | 
				
			||||||
      build_cirle_blockchain_token if blockchain_token_num.present?
 | 
					      build_cirle_blockchain_token if blockchain_token_num.present?
 | 
				
			||||||
 | 
					      unless @project.id.zero?
 | 
				
			||||||
      # @信息发送
 | 
					        # @信息发送
 | 
				
			||||||
      AtmeService.call(current_user, @atme_receivers, @issue) unless receivers_login.blank?
 | 
					        AtmeService.call(current_user, @atme_receivers, @issue) unless receivers_login.blank?
 | 
				
			||||||
      # 消息发送
 | 
					        # 消息发送
 | 
				
			||||||
      if Site.has_notice_menu?
 | 
					        if Site.has_notice_menu?
 | 
				
			||||||
        SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, @issue&.id, previous_issue_changes) unless previous_issue_changes.blank?
 | 
					          SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, @issue&.id, previous_issue_changes) unless previous_issue_changes.blank?
 | 
				
			||||||
        SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id, add_assigner_ids) unless add_assigner_ids.blank?
 | 
					          SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id, add_assigner_ids) unless add_assigner_ids.blank?
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        # 触发webhook
 | 
				
			||||||
 | 
					        Rails.logger.info "################### 触发webhook"
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueUpdate', @updated_issue&.id, current_user.id, previous_issue_changes.except(:issue_tags_value, :assigned_to_id))
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @issue&.id, current_user.id, {issue_tag_ids: [before_issue_tag_ids, issue_tag_ids]}) unless issue_tag_ids.nil?
 | 
				
			||||||
 | 
					        TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @issue&.id, current_user.id, {assigner_ids: [before_assigner_ids, assigner_ids]}) unless assigner_ids.nil?
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      unlock("Api::V1::Issues::UpdateService:#{project.id}:#{issue.id}")
 | 
					      unlock("Api::V1::Issues::UpdateService:#{project.id}:#{issue.id}")
 | 
				
			||||||
      # 触发webhook
 | 
					 | 
				
			||||||
      Rails.logger.info "################### 触发webhook"
 | 
					 | 
				
			||||||
      TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueUpdate', @updated_issue&.id, current_user.id, previous_issue_changes.except(:issue_tags_value, :assigned_to_id))
 | 
					 | 
				
			||||||
      TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @issue&.id, current_user.id, {issue_tag_ids: [before_issue_tag_ids, issue_tag_ids]}) unless issue_tag_ids.nil?
 | 
					 | 
				
			||||||
      TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @issue&.id, current_user.id, {assigner_ids: [before_assigner_ids, assigner_ids]}) unless assigner_ids.nil?
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      return @updated_issue
 | 
					      return @updated_issue
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					class Gitea::Repository::ActionSecretsService < Gitea::ClientService
 | 
				
			||||||
 | 
					  attr_reader :owner, :repo, :secret_name, :secret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def initialize(owner, repo, secret_name, secret)
 | 
				
			||||||
 | 
					    @owner   = owner
 | 
				
			||||||
 | 
					    @repo    = repo
 | 
				
			||||||
 | 
					    @secret_name = secret_name
 | 
				
			||||||
 | 
					    @secret = secret
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def call
 | 
				
			||||||
 | 
					    response = put(url, request_params)
 | 
				
			||||||
 | 
					    render_201_response(response)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy
 | 
				
			||||||
 | 
					    response = delete(url, request_params)
 | 
				
			||||||
 | 
					    render_201_response(response)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def request_params
 | 
				
			||||||
 | 
					    Hash.new.merge(token: owner.gitea_token, data: { data: secret } )
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def url
 | 
				
			||||||
 | 
					    "/repos/#{owner.login}/#{repo}/actions/secrets/#{secret_name}".freeze
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ class Projects::TransferService < ApplicationService
 | 
				
			||||||
  def call
 | 
					  def call
 | 
				
			||||||
    Rails.logger.info("###### Project transfer_service begin ######")
 | 
					    Rails.logger.info("###### Project transfer_service begin ######")
 | 
				
			||||||
    ActiveRecord::Base.transaction do
 | 
					    ActiveRecord::Base.transaction do
 | 
				
			||||||
 | 
					      update_actions
 | 
				
			||||||
      gitea_update_owner
 | 
					      gitea_update_owner
 | 
				
			||||||
      update_owner
 | 
					      update_owner
 | 
				
			||||||
      update_repo_url
 | 
					      update_repo_url
 | 
				
			||||||
| 
						 | 
					@ -32,6 +33,16 @@ class Projects::TransferService < ApplicationService
 | 
				
			||||||
    project.set_owner_permission(new_owner)
 | 
					    project.set_owner_permission(new_owner)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update_actions
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
 | 
					      action_params = { has_actions: false }
 | 
				
			||||||
 | 
					      Gitea::Repository::UpdateService.call(owner, project.identifier, action_params)
 | 
				
			||||||
 | 
					      project.update action_params
 | 
				
			||||||
 | 
					    rescue Exception => e
 | 
				
			||||||
 | 
					      Rails.logger.info("##### Project transfer_service, gitea transfer error #{e}")
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def update_repo_url
 | 
					  def update_repo_url
 | 
				
			||||||
    project.repository.update!(user_id: new_owner.id, url: @gitea_repo["clone_url"])
 | 
					    project.repository.update!(user_id: new_owner.id, url: @gitea_repo["clone_url"])
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="actions" style="margin-top:10px;">
 | 
					  <div class="actions" style="margin-top:10px;">
 | 
				
			||||||
    <!--    <a href="/managements/competition/customize" style="font-size: 28px;">>>前往节点管理页面</a>-->
 | 
					    <!--    <a href="/managements/competition/customize" style="font-size: 28px;">>>前往节点管理页面</a>-->
 | 
				
			||||||
    <%= form.submit("保存赛事") %>
 | 
					    <%= form.submit("保存") %>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
<% end %>
 | 
					<% end %>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ json.types @node_types.each do |node_type|
 | 
				
			||||||
  if node_type.name.to_s == "未分类"
 | 
					  if node_type.name.to_s == "未分类"
 | 
				
			||||||
    json.extract! node_type, :id, :name
 | 
					    json.extract! node_type, :id, :name
 | 
				
			||||||
    json.nodes @no_type_nodes do |node|
 | 
					    json.nodes @no_type_nodes do |node|
 | 
				
			||||||
      json.extract! node, :id, :name, :full_name, :description, :action_node_types_id, :yaml, :sort_no, :use_count
 | 
					      json.extract! node, :id, :label, :name, :full_name, :description, :action_node_types_id, :yaml, :sort_no, :use_count
 | 
				
			||||||
      json.inputs node.action_node_inputs do |node_input|
 | 
					      json.inputs node.action_node_inputs do |node_input|
 | 
				
			||||||
        json.partial! "node_input", locals: { node_input: node_input, node: node }
 | 
					        json.partial! "node_input", locals: { node_input: node_input, node: node }
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ json.types @node_types.each do |node_type|
 | 
				
			||||||
  else
 | 
					  else
 | 
				
			||||||
    json.extract! node_type, :id, :name
 | 
					    json.extract! node_type, :id, :name
 | 
				
			||||||
    json.nodes node_type.action_nodes do |node|
 | 
					    json.nodes node_type.action_nodes do |node|
 | 
				
			||||||
      json.extract! node, :id, :name, :full_name, :description, :action_node_types_id, :yaml, :sort_no, :use_count
 | 
					      json.extract! node, :id, :label, :name, :full_name, :description, :action_node_types_id, :yaml, :sort_no, :use_count
 | 
				
			||||||
      json.inputs node.action_node_inputs do |node_input|
 | 
					      json.inputs node.action_node_inputs do |node_input|
 | 
				
			||||||
        json.partial! "node_input", locals: { node_input: node_input, node: node }
 | 
					        json.partial! "node_input", locals: { node_input: node_input, node: node }
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,13 +14,16 @@
 | 
				
			||||||
    <label for="status">类型:</label>
 | 
					    <label for="status">类型:</label>
 | 
				
			||||||
    <%= form.select :action_node_types_id, options_for_select(Action::NodeType.all.map { |key| [key.name, key.id]}, node.action_node_types_id), {}, class: "form-control" %>
 | 
					    <%= form.select :action_node_types_id, options_for_select(Action::NodeType.all.map { |key| [key.name, key.id]}, node.action_node_types_id), {}, class: "form-control" %>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
  <div class="field">
 | 
					  <div class="field">
 | 
				
			||||||
    <%= form.label :name, "节点名称" %>
 | 
					    <%= form.label :label, "节点名称" %>
 | 
				
			||||||
 | 
					    <%= form.text_field :label %>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  <div class="field">
 | 
				
			||||||
 | 
					    <%= form.label :name, "节点标识" %>
 | 
				
			||||||
    <%= form.text_field :name %>
 | 
					    <%= form.text_field :name %>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div class="field">
 | 
					  <div class="field">
 | 
				
			||||||
    <%= form.label :full_name, "节点全称" %>
 | 
					    <%= form.label :full_name, "节点全名" %>
 | 
				
			||||||
    <%= form.text_field :full_name %>
 | 
					    <%= form.text_field :full_name %>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@
 | 
				
			||||||
  <tr>
 | 
					  <tr>
 | 
				
			||||||
    <th>ID</th>
 | 
					    <th>ID</th>
 | 
				
			||||||
    <th width="15%">节点名称</th>
 | 
					    <th width="15%">节点名称</th>
 | 
				
			||||||
 | 
					    <th width="15%">节点标识</th>
 | 
				
			||||||
    <th width="20%">节点全称</th>
 | 
					    <th width="20%">节点全称</th>
 | 
				
			||||||
    <th width="20%">节点描述</th>
 | 
					    <th width="20%">节点描述</th>
 | 
				
			||||||
    <th width="10%">分类</th>
 | 
					    <th width="10%">分类</th>
 | 
				
			||||||
| 
						 | 
					@ -27,6 +28,7 @@
 | 
				
			||||||
  <% @nodes.each do |info| %>
 | 
					  <% @nodes.each do |info| %>
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
      <td><%= info.id %></td>
 | 
					      <td><%= info.id %></td>
 | 
				
			||||||
 | 
					      <td><%= info.label %></td>
 | 
				
			||||||
      <td><%= info.name %></td>
 | 
					      <td><%= info.name %></td>
 | 
				
			||||||
      <td><%= info.full_name %></td>
 | 
					      <td><%= info.full_name %></td>
 | 
				
			||||||
      <td><%= info.description %></td>
 | 
					      <td><%= info.description %></td>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ json.types @node_types.each do |node_type|
 | 
				
			||||||
  if node_type.name.to_s == "未分类"
 | 
					  if node_type.name.to_s == "未分类"
 | 
				
			||||||
    json.extract! node_type, :id, :name
 | 
					    json.extract! node_type, :id, :name
 | 
				
			||||||
    json.nodes @no_type_nodes do |node|
 | 
					    json.nodes @no_type_nodes do |node|
 | 
				
			||||||
      json.extract! node, :id, :name, :full_name, :description, :action_node_types_id, :yaml, :sort_no, :use_count
 | 
					      json.extract! node, :id, :label, :name, :full_name, :description, :icon, :action_node_types_id, :yaml, :sort_no, :use_count
 | 
				
			||||||
      json.inputs node.action_node_inputs do |node_input|
 | 
					      json.inputs node.action_node_inputs do |node_input|
 | 
				
			||||||
        json.partial! "node_input", locals: { node_input: node_input, node: node }
 | 
					        json.partial! "node_input", locals: { node_input: node_input, node: node }
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ json.types @node_types.each do |node_type|
 | 
				
			||||||
  else
 | 
					  else
 | 
				
			||||||
    json.extract! node_type, :id, :name
 | 
					    json.extract! node_type, :id, :name
 | 
				
			||||||
    json.nodes node_type.action_nodes do |node|
 | 
					    json.nodes node_type.action_nodes do |node|
 | 
				
			||||||
      json.extract! node, :id, :name, :full_name, :description, :action_node_types_id, :yaml, :sort_no, :use_count
 | 
					      json.extract! node, :id, :label, :name, :full_name, :description, :icon, :action_node_types_id, :yaml, :sort_no, :use_count
 | 
				
			||||||
      json.inputs node.action_node_inputs do |node_input|
 | 
					      json.inputs node.action_node_inputs do |node_input|
 | 
				
			||||||
        json.partial! "node_input", locals: { node_input: node_input, node: node }
 | 
					        json.partial! "node_input", locals: { node_input: node_input, node: node }
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,7 +85,9 @@
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    </tbody>
 | 
					    </tbody>
 | 
				
			||||||
  </table>
 | 
					  </table>
 | 
				
			||||||
 | 
					  <% if EduSetting.get("open_baidu_tongji").to_s == "true" %>
 | 
				
			||||||
  <%= render partial: 'admins/dashboards/baidu_tongji' %>
 | 
					  <%= render partial: 'admins/dashboards/baidu_tongji' %>
 | 
				
			||||||
 | 
					  <% end %>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
<div id="project-language-modals">
 | 
					<div id="project-language-modals">
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					json.issues @links.each do |link|
 | 
				
			||||||
 | 
					  if @issue.id == link.be_linkable_id
 | 
				
			||||||
 | 
					    json.partial! "api/v1/issues/simple_detail", locals: { issue: link.linkable }
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    json.partial! "api/v1/issues/simple_detail", locals: { issue: link.be_linkable }
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					json.id journal.id
 | 
				
			||||||
 | 
					json.is_journal_detail journal.is_journal_detail? 
 | 
				
			||||||
 | 
					json.created_at journal.created_on.strftime("%Y-%m-%d %H:%M")
 | 
				
			||||||
 | 
					json.updated_at journal.updated_on.strftime("%Y-%m-%d %H:%M")
 | 
				
			||||||
 | 
					json.user do 
 | 
				
			||||||
 | 
					  if journal.user.present? 
 | 
				
			||||||
 | 
					    json.partial! "api/v1/users/simple_user", user: journal.user
 | 
				
			||||||
 | 
					  else 
 | 
				
			||||||
 | 
					    json.nil! 
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					if journal.is_journal_detail? 
 | 
				
			||||||
 | 
					  detail = journal.journal_details.take
 | 
				
			||||||
 | 
					  json.operate_category journal.pm_operate_category
 | 
				
			||||||
 | 
					  json.operate_content journal.is_journal_detail? ? journal.pm_operate_content : nil
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					  json.notes journal.notes 
 | 
				
			||||||
 | 
					  json.comments_count journal.comments_count
 | 
				
			||||||
 | 
					  json.children_journals journal.first_ten_children_journals.each do |journal| 
 | 
				
			||||||
 | 
					    json.partial! "api/v1/issues/journals/children_detail", journal: journal
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  json.attachments journal.attachments do |attachment| 
 | 
				
			||||||
 | 
					    json.partial! "api/v1/attachments/simple_detail", locals: {attachment: attachment}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					json.total_journals_count @total_journals_count
 | 
				
			||||||
 | 
					json.total_operate_journals_count @total_operate_journals_count
 | 
				
			||||||
 | 
					json.total_comment_journals_count @total_comment_journals_count
 | 
				
			||||||
 | 
					json.total_count @journals.total_count 
 | 
				
			||||||
 | 
					json.journals @journals do |journal|
 | 
				
			||||||
 | 
					  journal.associate_attachment_container
 | 
				
			||||||
 | 
					  json.partial! "api/pm/issues/journals/detail", journal: journal
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					json.total_count @issues.total_count 
 | 
				
			||||||
 | 
					json.issues @issues.each do |issue| 
 | 
				
			||||||
 | 
					  if params[:only_name].present? 
 | 
				
			||||||
 | 
					    json.(issue, :id, :subject, :project_issues_index)
 | 
				
			||||||
 | 
					  else 
 | 
				
			||||||
 | 
					    json.partial! "api/v1/issues/simple_detail", locals: {issue: issue}
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,6 @@ json.title attachment.title
 | 
				
			||||||
json.description attachment.description
 | 
					json.description attachment.description
 | 
				
			||||||
json.filesize number_to_human_size(attachment.filesize)
 | 
					json.filesize number_to_human_size(attachment.filesize)
 | 
				
			||||||
json.is_pdf attachment.is_pdf?
 | 
					json.is_pdf attachment.is_pdf?
 | 
				
			||||||
json.url attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)
 | 
					json.url Rails.application.config_for(:configuration)['platform_url'] + (attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)).to_s
 | 
				
			||||||
json.created_on attachment.created_on.strftime("%Y-%m-%d %H:%M:%S")
 | 
					json.created_on attachment.created_on.strftime("%Y-%m-%d %H:%M")
 | 
				
			||||||
json.content_type attachment.content_type
 | 
					json.content_type attachment.content_type
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +33,13 @@ json.author do
 | 
				
			||||||
    json.nil! 
 | 
					    json.nil! 
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					json.changer do 
 | 
				
			||||||
 | 
					  if issue.changer.present? 
 | 
				
			||||||
 | 
					    json.partial! "api/v1/users/simple_user", locals: {user: issue.changer}
 | 
				
			||||||
 | 
					  else 
 | 
				
			||||||
 | 
					    json.nil! 
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
json.assigners issue.show_assigners.each do |assigner| 
 | 
					json.assigners issue.show_assigners.each do |assigner| 
 | 
				
			||||||
  json.partial! "api/v1/users/simple_user", locals: {user: assigner}
 | 
					  json.partial! "api/v1/users/simple_user", locals: {user: assigner}
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -44,4 +51,17 @@ json.operate_journals_count issue.operate_journals.size
 | 
				
			||||||
json.attachments issue.attachments.each do |attachment|
 | 
					json.attachments issue.attachments.each do |attachment|
 | 
				
			||||||
  json.partial! "api/v1/attachments/simple_detail", locals: {attachment: attachment}
 | 
					  json.partial! "api/v1/attachments/simple_detail", locals: {attachment: attachment}
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
json.pull_fixed issue.pull_attached_issues.where(fixed: true).present?
 | 
					json.pull_fixed issue.pull_attached_issues.where(fixed: true).present?
 | 
				
			||||||
 | 
					json.root_id issue.root_id
 | 
				
			||||||
 | 
					json.pm_issue_type issue.pm_issue_type
 | 
				
			||||||
 | 
					json.pm_sprint_id issue.pm_sprint_id
 | 
				
			||||||
 | 
					json.pm_project_id issue.pm_project_id
 | 
				
			||||||
 | 
					json.time_scale issue.time_scale
 | 
				
			||||||
 | 
					json.child_count issue.child_count
 | 
				
			||||||
 | 
					json.project do 
 | 
				
			||||||
 | 
					  if issue.project.present? && issue.project&.owner.present?
 | 
				
			||||||
 | 
					    json.partial! "api/v1/projects/detail", locals: {project: issue.project}
 | 
				
			||||||
 | 
					  else 
 | 
				
			||||||
 | 
					    json.nil! 
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -6,9 +6,32 @@ json.tags issue.show_issue_tags.each do |tag|
 | 
				
			||||||
  json.partial! "api/v1/issues/issue_tags/simple_detail", locals: {tag: tag}
 | 
					  json.partial! "api/v1/issues/issue_tags/simple_detail", locals: {tag: tag}
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
json.status_name issue.issue_status&.name 
 | 
					json.status_name issue.issue_status&.name 
 | 
				
			||||||
 | 
					json.status_id issue.status_id 
 | 
				
			||||||
 | 
					json.status do 
 | 
				
			||||||
 | 
					  if issue.issue_status.present?
 | 
				
			||||||
 | 
					    json.partial! "api/v1/issues/statues/simple_detail", locals: {status: issue.issue_status}
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    json.nil! 
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
json.priority_name issue.priority&.name 
 | 
					json.priority_name issue.priority&.name 
 | 
				
			||||||
 | 
					json.priority_id issue.priority_id
 | 
				
			||||||
 | 
					json.priority do 
 | 
				
			||||||
 | 
					  if issue.priority.present?
 | 
				
			||||||
 | 
					    json.partial! "api/v1/issues/issue_priorities/simple_detail", locals: {priority: issue.priority}
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    json.nil! 
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
json.milestone_name issue.version&.name
 | 
					json.milestone_name issue.version&.name
 | 
				
			||||||
json.milestone_id issue.fixed_version_id
 | 
					json.milestone_id issue.fixed_version_id
 | 
				
			||||||
 | 
					json.root_id issue.root_id
 | 
				
			||||||
 | 
					json.pm_issue_type issue.pm_issue_type
 | 
				
			||||||
 | 
					json.pm_sprint_id issue.pm_sprint_id
 | 
				
			||||||
 | 
					json.pm_project_id issue.pm_project_id
 | 
				
			||||||
 | 
					json.time_scale issue.time_scale
 | 
				
			||||||
 | 
					json.child_count issue.child_count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
json.author do
 | 
					json.author do
 | 
				
			||||||
  if issue.user.present? 
 | 
					  if issue.user.present? 
 | 
				
			||||||
    json.partial! "api/v1/users/simple_user", locals: {user: issue.user}
 | 
					    json.partial! "api/v1/users/simple_user", locals: {user: issue.user}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,13 @@
 | 
				
			||||||
json.total_issues_count @total_issues_count
 | 
					json.total_issues_count @total_issues_count
 | 
				
			||||||
json.opened_count @opened_issues_count
 | 
					json.opened_count @opened_issues_count
 | 
				
			||||||
json.closed_count @closed_issues_count
 | 
					json.closed_count @closed_issues_count
 | 
				
			||||||
 | 
					json.complete_count @complete_issues_count
 | 
				
			||||||
json.total_count @issues.total_count 
 | 
					json.total_count @issues.total_count 
 | 
				
			||||||
json.has_created_issues @project.issues.size > 0
 | 
					json.has_created_issues @project.present? ? @project.issues.size > 0 : 0
 | 
				
			||||||
json.issues @issues.each do |issue| 
 | 
					json.issues @issues.each do |issue| 
 | 
				
			||||||
  if params[:only_name].present? 
 | 
					  if params[:only_name].present? 
 | 
				
			||||||
    json.(issue, :id, :subject, :project_issues_index)
 | 
					    json.(issue, :id, :subject, :project_issues_index)
 | 
				
			||||||
  else 
 | 
					  else 
 | 
				
			||||||
    json.partial! "simple_detail", locals: {issue: issue}
 | 
					    json.partial! "api/v1/issues/simple_detail", locals: {issue: issue}
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1 +1 @@
 | 
				
			||||||
json.(priority, :id, :name)
 | 
					json.(priority, :id, :name,:pm_color)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
json.total_count @priorities.total_count 
 | 
					json.total_count @priorities.total_count 
 | 
				
			||||||
json.priorities @priorities.each do |priority|
 | 
					json.priorities @priorities.each do |priority|
 | 
				
			||||||
  json.partial! "simple_detail", locals: {priority: priority}
 | 
					  json.partial! "api/v1/issues/issue_priorities/simple_detail", locals: {priority: priority}
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
json.total_count @issue_tags.total_count
 | 
					json.total_count @issue_tags.total_count
 | 
				
			||||||
json.issue_tags @issue_tags.each do |tag| 
 | 
					json.issue_tags @issue_tags.each do |tag| 
 | 
				
			||||||
  if params[:only_name]
 | 
					  if params[:only_name]
 | 
				
			||||||
    json.partial! "simple_detail", locals: {tag: tag}
 | 
					    json.partial! "api/v1/issues/issue_tags/simple_detail", locals: {tag: tag}
 | 
				
			||||||
  else 
 | 
					  else 
 | 
				
			||||||
    json.partial! "detail", locals: {tag: tag}
 | 
					    json.partial! "api/v1/issues/issue_tags/detail", locals: {tag: tag}
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ else
 | 
				
			||||||
  json.notes journal.notes 
 | 
					  json.notes journal.notes 
 | 
				
			||||||
  json.comments_count journal.comments_count
 | 
					  json.comments_count journal.comments_count
 | 
				
			||||||
  json.children_journals journal.first_ten_children_journals.each do |journal| 
 | 
					  json.children_journals journal.first_ten_children_journals.each do |journal| 
 | 
				
			||||||
    json.partial! "children_detail", journal: journal
 | 
					    json.partial! "api/v1/issues/journals/children_detail", journal: journal
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
  json.attachments journal.attachments do |attachment| 
 | 
					  json.attachments journal.attachments do |attachment| 
 | 
				
			||||||
    json.partial! "api/v1/attachments/simple_detail", locals: {attachment: attachment}
 | 
					    json.partial! "api/v1/attachments/simple_detail", locals: {attachment: attachment}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue