Merge branch 'rep_quality' into develop

Conflicts:
	db/schema.rb
	public/stylesheets/project.css
	public/stylesheets/public.css
This commit is contained in:
huang 2016-06-24 19:17:26 +08:00
commit 46cde061a6
33 changed files with 1117 additions and 12 deletions

View File

@ -6,7 +6,11 @@ unless RUBY_PLATFORM =~ /w32/
gem 'iconv'
end
gem 'certified'
gem 'net-ssh'
gem 'jenkins_api_client'
gem 'nokogiri'
# gem 'certified'
gem 'wechat',path: 'lib/wechat'
gem 'grack', path:'lib/grack'

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@ -0,0 +1,3 @@
// Place all the styles related to the QualityAnalyses controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@ -393,7 +393,7 @@ class ProjectsController < ApplicationController
unless @project.gpid.nil?
g = Gitlab.client
@gitlab_branches = g.branches(@project.gpid)
@branch_names = g.branches(@project.gpid).map{|b| b.name}
@branch_names = @gitlab_branches.map{|b| b.name}
@gitlab_default_branch = g.project(@project.gpid).default_branch
end
end
@ -645,7 +645,7 @@ class ProjectsController < ApplicationController
params[:project][:is_public] ? @project.is_public = 1 : @project.is_public = 0
params[:project][:hidden_repo] ? @project.hidden_repo = 1 : @project.hidden_repo = 0
# 更新公开私有时同步gitlab公开私有
unless @project.gpid.nil?
if !@project.gpid.nil? && @project.is_public != (params[:project][:is_public] == "on" ? true : false)
g = Gitlab.client
params[:project][:is_public] ? g.edit_project(@project.gpid, 20, params[:branch]) : g.edit_project(@project.gpid, 0, params[:branch])
end

View File

@ -0,0 +1,99 @@
class QualityAnalysisController < ApplicationController
before_filter :find_project_by_project_id#, :except => [:getattachtype]
before_filter :authorize
layout "base_projects"
include ApplicationHelper
require 'jenkins_api_client'
require 'nokogiri'
require 'json'
require 'open-uri'
def show
end
def create
gitlab_address = Redmine::Configuration['gitlab_address']
jenkins_address = Redmine::Configuration['jenkins_address']
@client = JenkinsApi::Client.new(:server_url => jenkins_address,
:username => "temp",
:password => '123123')
#@client.exists?(job_name)
@g = Gitlab.client
user_name = User.find(params[:user_id]).try(:login)
branch = params[:branch].nil? ? "master" : params[:branch]
language = params[:language]
path = params[:path]
identifier = params[:identifier]
qa = QualityAnalysis.where(:project_id => @project.id, :author_login => user_name).first
version = qa.nil? ? 1 : qa.sonar_version + 1
properties = "sonar.projectKey=#{user_name}:#{identifier}
sonar.projectName=#{user_name}:#{identifier}
sonar.projectVersion=#{version}
sonar.sources=#{path}
sonar.language=#{language.downcase}
sonar.sourceEncoding=utf-8"
git_url = gitlab_address.to_s+"/"+@project.owner.to_s+"/"+ identifier + "."+"git"
# # # modify config
@doc = Nokogiri::XML(File.open(File.join(Rails.root, 'tmp', 'config.xml')))
@doc.at_xpath("//hudson.plugins.git.UserRemoteConfig/url").content = git_url
@doc.at_xpath("//hudson.plugins.git.BranchSpec/name").content = "*/#{branch}"
@doc.at_xpath("//hudson.plugins.sonar.SonarRunnerBuilder/properties").content = properties #sonar-properties
#
# replace config.xml of jenkins
@client = @client.job.create("#{user_name}_#{identifier}", @doc.to_xml)
# relace gitlab hook
# genkins address
@g.add_project_hook(@project.gpid, jenkins_address + "/project/#{user_name}_#{identifier}")
if qa.nil?
QualityAnalysis.create(:project_id => @project.id, :author_login => user_name, :rep_identifier => identifier, :sonar_version => version, :path => path, :branch => branch, :language => language)
else
qa.update_attribute(:sonar_version, version)
end
end
def index
@sonar_address = Redmine::Configuration['sonar_address']
# if params[:resource_id].nil?
# @name_flag = true
# @quality_analyses = QualityAnalysis.where(:project_id => @project.id)
# # @quality_analyses.map {|qa| qa.}
# # if @quality_analyses.count > 0
# # @quality_analyses.each do |qa|
# # ["Hjqreturn:cc_rep", "Hjqreturn:putong", "Hjqreturn:sonar_rep2", "shitou:sonar_rep"]
# #
# # end
# # end
# # projects_date = open(@sonar_address + "/api/projects/index").read
# # arr = JSON.parse(projects_date).map {|m| m["nm"]}
# # arr.map
# else
qa = QualityAnalysis.where(:project_id => @project.id).first
@resource_id = qa.author_login+":"+qa.rep_identifier
@name_flag = false
complexity_date = open(@sonar_address + "/api/resources/index?resource=#{@resource_id}&depth=0&metrics=sqale_rating,function_complexity,duplicated_lines_density,comment_lines_density,sqale_index,lines,file_line,files,functions,classes,directories").read
@complexity =JSON.parse(complexity_date).first
issue_date = open(@sonar_address + "/api/resources/index?resource=#{@resource_id}&depth=0&metrics=blocker_violations,critical_violations,major_violations,minor_violations,info_violations,violations").read
@sonar_issues = JSON.parse(issue_date).first
# end
end
# Find project of id params[:project_id]
def find_project_by_project_id
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
# Authorize the user for the requested action
def authorize(ctrl = params[:controller], action = params[:action], global = false)
unless @project.archived? && @project.gpid.nil?
true
else
render_403 :message => :notice_not_authorized_archived_project
end
end
end

View File

@ -30,11 +30,13 @@ class RepositoriesController < ApplicationController
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
default_search_scope :changesets
before_filter :find_project_by_project_id, :only => [:new, :create, :newrepo, :stats]
before_filter :find_project_by_project_id, :only => [:new, :create, :newrepo, :stats, :quality_analysis]
before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
before_filter :find_project_repository, :except => [:new, :create, :newcreate, :edit, :update, :destroy, :committers, :newrepo, :to_gitlab, :forked, :project_archive]
before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
before_filter :authorize , :except => [:newrepo,:newcreate,:fork, :to_gitlab, :forked, :commit_diff, :project_archive]
before_filter :authorize , :except => [:newrepo,:newcreate,:fork, :to_gitlab, :forked, :commit_diff, :project_archive, :quality_analysis]
# 链接gitlab
before_filter :connect_gitlab, :only => [:quality_analysis]
accept_rss_auth :revisions
# hidden repositories filter // 隐藏代码过滤器
before_filter :check_hidden_repo, :only => [:show, :stats, :revisions, :revision, :diff ]
@ -43,6 +45,7 @@ class RepositoriesController < ApplicationController
helper :project_score
#@root_path = RepositoriesHelper::ROOT_PATH
$g=Gitlab.client
require 'net/ssh'
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
def new
@ -306,6 +309,37 @@ update
end
end
def quality_analysis
gitlab_branches = @g.branches(@project.gpid)
@branch_names = gitlab_branches.map{|b| b.name}
@gitlab_default_branch = @g.project(@project.gpid).default_branch
# language = params[:language]
# branch = params[:branch]
# path = params[:path]
# user_name = User.find(@project.user_id).try(:login)
# rep_name = params[:repository_id]
# host = "192.168.0.200"
# port = "1125"
# username = "git"
# password = "123123"
# server_cmd1 = "sh gitclone.sh" + " " + user_name + " " + rep_name
# # 连接到远程主机 foobar
# ssh = Net::SSH.start(host, username, :port => port, :password => password) do |ssh|
# result = ssh.exec!(server_cmd1)
# path = "/home/git/repo/" + user_name + "/" + rep_name
# # sonar 分析
# # server_cmd2
# # 删除clone的版本库
# # server_cmd3
# end
respond_to do |format|
format.js
format.html{
render :layout => "base_projects"
}
end
end
def destroy
DestroyRepositoryTask.new.destroy(User.current.id, @repository.id)
@repository.hidden = true
@ -615,6 +649,14 @@ update
project.project_score.update_attribute(:commit_time, date.created_at)
end
# 链接gitlab
def connect_gitlab
@g = Gitlab.client
unless @project.gpid.nil?
@g_project = @g.project(@project.gpid)
end
end
def find_repository
@repository = Repository.find(params[:id])
@project = @repository.project

View File

@ -0,0 +1,96 @@
module QualityAnalysisHelper
def sqale_rating_status val
arr = []
if val.to_i > 0 && val.to_i < 5
arr << "很好"
arr << "b_green2"
elsif val.to_i > 5 && val.to_i < 10
arr << "较好"
arr << "b_slow_yellow"
elsif val.to_i > 10 && val.to_i < 20
arr << "中等"
arr << "b_yellow"
elsif val.to_i > 20 && val.to_i < 50
arr << "较差"
arr << "b_slow_red"
elsif val.to_i > 20
arr << "很差"
arr << "b_red"
end
end
def complexity_status val
arr = []
if val.to_i < 10
arr << "良好"
arr << "b_green2"
elsif val.to_i > 10 && val.to_i < 15
arr << "较高"
arr << "b_yellow"
elsif val.to_i > 15
arr << "很高"
arr << "b_red"
end
end
def duplicated_lines_density_status val
arr = []
if val.to_i < 30
arr << "良好"
arr << "b_green2"
elsif val.to_i > 30 && val.to_i < 50
arr << "较高"
arr << "b_yellow"
elsif val.to_i > 50
arr << "很高"
arr << "b_red"
end
end
def comment_lines_density_status val
arr = []
if val.to_i < 20
arr << "较低"
arr << "b_yellow"
elsif val.to_i > 20 && val.to_i < 50
arr << "正常"
arr << "b_green2"
elsif val.to_i > 50
arr << "较高"
arr << "b_red"
end
end
def score_sqale_rating val
if val.to_i > 0 && val.to_i < 5
"5"
elsif val.to_i > 5 && val.to_i < 10
"4"
elsif val.to_i > 10 && val.to_i < 20
"3"
elsif val.to_i > 20 && val.to_i < 50
"2"
elsif val.to_i > 20
"1"
end
end
def lines_scale val
if val.to_i < 5000
"小型"
elsif val.to_i >5000 && val.to_i < 50000
"中型"
else
"大型"
end
end
#统计答题百分比,统计结果保留两位小数
def statistics_result_percentage(e, t)
e = e.to_f
t = t.to_f
t == 0 ? 0 : format("%.2f", e*100/t)
end
end

View File

@ -155,9 +155,9 @@ class Project < ActiveRecord::Base
#ActiveModel::Dirty 这里有一个changed方法。对任何对象都可以用
after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
# 创建project之后默认创建一个board之后的board去掉了board的概念
after_create :create_board_sync,:acts_as_forge_activities,:create_project_ealasticsearch_index
after_create :create_board_sync,:acts_as_forge_activities
before_destroy :delete_all_members,:delete_project_ealasticsearch_index
after_update :update_project_ealasticsearch_index
# after_update :update_project_ealasticsearch_index
def remove_references_before_destroy
return if self.id.nil?
Watcher.delete_all ['watchable_id = ?', id]

View File

@ -0,0 +1,7 @@
class QualityAnalysis < ActiveRecord::Base
attr_accessible :author_login, :project_id, :rep_identifier, :sonar_version, :branch, :path, :rep_identifier, :language
def user_rep_name
self.author_login+":"+self.rep_identifier
end
end

View File

@ -0,0 +1,3 @@
class SonarAnalysis < ActiveRecord::Base
attr_accessible :author_login, :project_id, :rep_identifier
end

View File

@ -0,0 +1,483 @@
<script>
// sqale_rating
$(function () {
$('#container_sqale_rating').highcharts({
chart: {
type: 'gauge',
plotBackgroundColor: null,
plotBackgroundImage: null,
plotBorderWidth: 0,
plotShadow: false
},
title: false,
pane: {
startAngle: -150,
endAngle: 150,
background: [{
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#FFF'],
[1, '#333']
]
},
borderWidth: 0,
outerRadius: '109%'
}, {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#333'],
[1, '#FFF']
]
},
borderWidth: 1,
outerRadius: '107%'
}, {
// default background
}, {
backgroundColor: '#DDD',
borderWidth: 0,
outerRadius: '105%',
innerRadius: '103%'
}]
},
// the value axis
yAxis: {
min: 0,
max: 100,
minorTickInterval: 'auto',
minorTickWidth: 1,
minorTickLength: 10,
minorTickPosition: 'inside',
minorTickColor: '#666',
tickPixelInterval: 30,
tickWidth: 2,
tickPosition: 'inside',
tickLength: 10,
tickColor: '#666',
labels: {
step: 2,
rotation: 'auto'
},
// title: {
// text: 'km/h'
// },
plotBands: [{
from: 0,
to: 5,
color: '#55BF3B' // green
}, {
from: 6,
to: 10,
color: '#adde18' // yellow
}, {
from: 11,
to: 20,
color: '#DDDF0D' // red
}, {
from: 21,
to: 50,
color: '#df8538' // yellow
}, {
from: 51,
to: 100,
color: '#DF5353' // yellow
}]
},
// delete hightcharts.com
credits:{
enabled: false
},
series: [{
name: '代码质量',
data: [<%= @complexity["msr"][9]["val"] %>]
// tooltip: {
// valueSuffix: ' km/h'
// }
}]
},
// Add some life
function (chart) {
// if (!chart.renderer.forExport) {
// setInterval(function () {
// var point = chart.series[0].points[0],
// newVal,
// inc = Math.round((Math.random() - 0.5) * 20);
//
// newVal = point.y + inc;
// if (newVal < 0 || newVal > 200) {
// newVal = point.y - inc;
// }
//
// point.update(newVal);
//
// }, 3000);
// }
});
});
// function_complexity
$(function () {
$('#container_function_complexity').highcharts({
chart: {
type: 'gauge',
plotBackgroundColor: null,
plotBackgroundImage: null,
plotBorderWidth: 0,
plotShadow: false
},
title: false,
pane: {
startAngle: -150,
endAngle: 150,
background: [{
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#FFF'],
[1, '#333']
]
},
borderWidth: 0,
outerRadius: '109%'
}, {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#333'],
[1, '#FFF']
]
},
borderWidth: 1,
outerRadius: '107%'
}, {
// default background
}, {
backgroundColor: '#DDD',
borderWidth: 0,
outerRadius: '105%',
innerRadius: '103%'
}]
},
// the value axis
yAxis: {
min: 0,
max: 30,
minorTickInterval: 'auto',
minorTickWidth: 1,
minorTickLength: 10,
minorTickPosition: 'inside',
minorTickColor: '#666',
tickPixelInterval: 30,
tickWidth: 2,
tickPosition: 'inside',
tickLength: 10,
tickColor: '#666',
labels: {
step: 2,
rotation: 'auto'
},
// title: {
// text: 'km/h'
// },
plotBands: [{
from: 0,
to: 10,
color: '#55BF3B' // green
}, {
from: 11,
to: 15,
color: '#DDDF0D' // yellow
}, {
from: 16,
to: 30,
color: '#DF5353' // red
}]
},
// delete hightcharts.com
credits:{
enabled: false
},
series: [{
name: '复杂度',
data: [<%= @complexity["msr"][6]["val"] %>]
// tooltip: {
// valueSuffix: ' km/h'
// }
}]
},
// Add some life
function (chart) {
// if (!chart.renderer.forExport) {
// setInterval(function () {
// var point = chart.series[0].points[0],
//// newVal,
//// inc = Math.round((Math.random() - 0.5) * 20);
//
//// newVal = point.y + inc;
//// if (newVal < 0 || newVal > 200) {
//// newVal = point.y - inc;
//// }
//
//// point.update(newVal);
//
// }, 3000);
// }
});
});
// duplicated_lines_density
$(function () {
$('#container_duplicated_lines_density').highcharts({
chart: {
type: 'gauge',
plotBackgroundColor: null,
plotBackgroundImage: null,
plotBorderWidth: 0,
plotShadow: false
},
title: false,
pane: {
startAngle: -150,
endAngle: 150,
background: [{
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#FFF'],
[1, '#333']
]
},
borderWidth: 0,
outerRadius: '109%'
}, {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#333'],
[1, '#FFF']
]
},
borderWidth: 1,
outerRadius: '107%'
}, {
// default background
}, {
backgroundColor: '#DDD',
borderWidth: 0,
outerRadius: '105%',
innerRadius: '103%'
}]
},
xAxis: {
style:{
fontSize: '18px'
}
},
// the value axis
yAxis: {
min: 0,
max: 100,
minorTickInterval: 'auto',
minorTickWidth: 1,
minorTickLength: 10,
minorTickPosition: 'inside',
minorTickColor: '#666',
tickPixelInterval: 30,
tickWidth: 2,
tickPosition: 'inside',
tickLength: 10,
tickColor: '#666',
labels: {
step: 2,
rotation: 'auto'
},
// title: {
// text: 'km/h'
// },
plotBands: [{
from: 0,
to: 30,
color: '#55BF3B' // green
}, {
from: 31,
to: 50,
color: '#DDDF0D' // red
}, {
from: 51,
to: 100,
color: '#DF5353' // yellow
}]
},
// delete hightcharts.com
credits:{
enabled: false
},
series: [{
name: '重复率',
data: [<%= @complexity["msr"][7]["val"] %>]
// tooltip: {
// valueSuffix: ' km/h'
// }
}]
},
// Add some life
function (chart) {
// if (!chart.renderer.forExport) {
// setInterval(function () {
// var point = chart.series[0].points[0],
// newVal,
// inc = Math.round((Math.random() - 0.5) * 20);
//
// newVal = point.y + inc;
// if (newVal < 0 || newVal > 200) {
// newVal = point.y - inc;
// }
//
// point.update(newVal);
//
// }, 3000);
// }
});
});
// comment_lines_density
$(function () {
$('#container_comment_lines_density').highcharts({
chart: {
type: 'gauge',
plotBackgroundColor: null,
plotBackgroundImage: null,
plotBorderWidth: 0,
plotShadow: false
},
title: false,
pane: {
startAngle: -150,
endAngle: 150,
background: [{
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#FFF'],
[1, '#333']
]
},
borderWidth: 0,
outerRadius: '109%'
}, {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#333'],
[1, '#FFF']
]
},
borderWidth: 1,
outerRadius: '107%'
}, {
// default background
}, {
backgroundColor: '#DDD',
borderWidth: 0,
outerRadius: '105%',
innerRadius: '103%'
}]
},
xAxis: {
title: {
text: '复杂度',
x: -23, //center设置标题的位置
y: 6
},
style:{
fontSize: '18px'
}
},
// the value axis
yAxis: {
min: 0,
max: 100,
minorTickInterval: 'auto',
minorTickWidth: 1,
minorTickLength: 10,
minorTickPosition: 'inside',
minorTickColor: '#666',
tickPixelInterval: 30,
tickWidth: 2,
tickPosition: 'inside',
tickLength: 10,
tickColor: '#666',
labels: {
step: 2,
rotation: 'auto'
},
// title: {
// text: 'km/h'
// },
plotBands: [{
from: 0,
to: 20,
color: '#DDDF0D' // green
}, {
from: 21,
to: 50,
color: '#55BF3B' // red
}, {
from: 51,
to: 100,
color: '#DF5353' // yellow
}]
},
// delete hightcharts.com
credits:{
enabled: false
},
series: [{
name: '质量等级',
data: [<%= @complexity["msr"][5]["val"] %>]
// tooltip: {
// valueSuffix: ' km/h'
// }
}]
},
// Add some life
function (chart) {
// if (!chart.renderer.forExport) {
// setInterval(function () {
// var point = chart.series[0].points[0],
// newVal,
// inc = Math.round((Math.random() - 0.5) * 20);
//
// newVal = point.y + inc;
// if (newVal < 0 || newVal > 200) {
// newVal = point.y - inc;
// }
//
// point.update(newVal);
//
// }, 3000);
// }
});
});
</script>

View File

@ -0,0 +1,25 @@
<div class="project_r_h">
<h2 class="project_h2">分析结果</h2>
</div>
<ul class="analysis-result-list">
<li class="analysis-result-name fl fontBlue2" >名称</li>
<li class="analysis-result-version fl fontBlue2" >版本</li>
<li class="analysis-result-loc fl fontBlue2" >分支</li>
<li class="analysis-result-debt fl fontBlue2" >语言</li>
<li class="analysis-result-time fl fontBlue2" >时间</li>
<div class="cl"></div>
</ul>
<% if @quality_analyses.count >0 %>
<% @quality_analyses.each do |qa| %>
<ul class="analysis-result-list">
<li title="Name" title="名称"><%=link_to "#{qa.author_login}:#{qa.rep_identifier}", project_quality_analysis_path(:resource_id => qa.author_login+":"+qa.rep_identifier, :branch => qa.branch.nil? ? "master" : qa.branch), :class => "analysis-result-name fl fontBlue2" %></li>
<li class="analysis-result-version fl fontBlue2" title="版本">1.0</li>
<li class="analysis-result-loc fl fontBlue2" title="分支名"><%= qa.branch %></li>
<li class="analysis-result-debt fl fontBlue2" title="语言"><%= qa.language %></li>
<li class="analysis-result-time fl fontBlue2" title="时间"><%= format_time(qa.created_at) %></li>
<div class="cl"></div>
</ul>
<% end %>
<% end %>

View File

@ -0,0 +1,114 @@
<%= javascript_include_tag 'highcharts','highcharts-more' %>
<%= render :partial => "hightchars" %>
<div class="project_r_h">
<h2 class="project_h2" style="width:180px;">质量分析</h2>
</div>
<div class="button-rep">当前分支:<%= params[:branch] %></div>
<div class="cl"></div>
<div class="tac f20 fb mt35 mb30">项目代码质量分析报告</div>
<div class="analysis-tag-wrap f16"> <span class="analysis-tag fl mr15"></span> <span class="fb fl">概要信息</span></div>
<div class="analysis-block mt10 mb40 f14">
<div class="flex">
<div class="analysis-genral">
<p id="container_sqale_rating" style="max-width:200px;min-height:200px;width:200px; margin:0 auto;"></p>
<p class="fontGrey3">质量等级</p>
<p class="fontBlue2 pr"><%= @complexity["msr"][9]["frmt_val"] %><span class="f8 c_white analysis-genral-icon <%= sqale_rating_status(@complexity["msr"][9]["val"])[1] %> borderRadius"><%= sqale_rating_status(@complexity["msr"][9]["val"])[0] %></span></p>
</div>
<div class="analysis-genral" >
<p id="container_function_complexity" style="max-width:200px;min-height:200px;width:200px; margin:0 auto;"></p>
<p class="fontGrey3">复杂度</p>
<p class="fontBlue2 pr"><%= @complexity["msr"][6]["val"] %><span class="f8 c_white analysis-genral-icon <%= complexity_status(@complexity["msr"][6]["val"])[1] %> borderRadius"><%= complexity_status(@complexity["msr"][6]["val"])[0] %></span></p>
</div>
</div>
<div class="flex">
<div class="analysis-genral">
<p id="container_duplicated_lines_density" style="max-width:200px;min-height:200px;width:200px; margin:0 auto;"></p>
<p class="fontGrey3">代码重复度</p>
<p class="fontBlue2 pr"><%= @complexity["msr"][7]["frmt_val"] %><span class="f8 c_white analysis-genral-icon <%= duplicated_lines_density_status(@complexity["msr"][7]["val"])[1] %> borderRadius"><%= duplicated_lines_density_status(@complexity["msr"][7]["val"])[0] %></span></p>
</div>
<div class="analysis-genral">
<p id="container_comment_lines_density" style="max-width:200px;min-height:200px;width:200px; margin:0 auto;"></p>
<p class="fontGrey3">注释率</p>
<p class="fontBlue2 pr"><%= @complexity["msr"][5]["frmt_val"] %><span class="f8 c_white analysis-genral-icon <%= comment_lines_density_status(@complexity["msr"][5]["val"])[1] %> borderRadius"><%=comment_lines_density_status(@complexity["msr"][5]["val"])[0] %></span></p>
</div>
</div>
</div>
<div class="analysis-tag-wrap f16"> <span class="analysis-tag fl mr15"></span> <span class="fb fl mr10">质量等级</span><span class="mr10 fontGrey2"><span class="c_red f18" style="margin-top:-5px; display:inline-block;"><%= score_sqale_rating(@complexity["msr"][9]["val"]) %></span>/5分</span><span class="fontGrey2">可定性评价为:<span class="c_red">质量<%= sqale_rating_status(@complexity["msr"][9]["val"])[0] %></span></span></div>
<div class="analysis-block mt10 mb40 f14">
<div><span class="fontGrey3 mr30">技术债务</span><span class="fontBlue2 w70 pInline"><%= @complexity["msr"][8]["frmt_val"] %></span><span class="fontGrey2"><a target="_blank" href="<%= @sonar_address %>/drilldown/measures/<%= @resource_id %>?metric=sqale_index">查看详情</a></span></div>
<div><span class="fontGrey3 mr30">质量问题</span>
<span class="fontBlue2 w70 pInline"><a class="fontBlue2 w70 pInline" target="_blank" href="<%= @sonar_address %>/component_issues?id=<%= @resource_id %>#resolved=false"><%= @sonar_issues["msr"][0]["frmt_val"] %></a></span><span class="fontGrey2">问题分类如下:</span></div>
<div class="ml90 mt15">
<div class="mb10"><span class="analysis-block-icon mr5"></span><span class="fontGrey3 mr45">阻断</span><span class="fontBlue2 w70 pInline"><%= @sonar_issues["msr"][1]["frmt_val"] %></span><span class="quality-percentage"><span class="quality-percentage-rate" style="width:<%= statistics_result_percentage(@sonar_issues["msr"][1]["frmt_val"].to_i, 200) %>%;"></span></span></div>
<div class="mb10"><span class="analysis-serious-icon mr5"></span><span class="fontGrey3 mr45">严重</span><span class="fontBlue2 w70 pInline"><%= @sonar_issues["msr"][2]["frmt_val"] %></span><span class="quality-percentage"><span class="quality-percentage-rate" style="width:<%= statistics_result_percentage(@sonar_issues["msr"][2]["frmt_val"].to_i, 200) %>%;"></span></span></div>
<div class="mb10"><span class="analysis-main-icon mr5"></span><span class="fontGrey3 mr45">主要</span><span class="fontBlue2 w70 pInline"><%= @sonar_issues["msr"][3]["frmt_val"] %></span><span class="quality-percentage"><span class="quality-percentage-rate" style="width:<%= statistics_result_percentage(@sonar_issues["msr"][3]["frmt_val"].to_i, 200) %>%;"></span></span></div>
<div class="mb10"><span class="analysis-secondary-icon mr5"></span><span class="fontGrey3 mr45">次要</span><span class="fontBlue2 w70 pInline"><%= @sonar_issues["msr"][4]["frmt_val"] %></span><span class="quality-percentage"><span class="quality-percentage-rate" style="width:<%= statistics_result_percentage(@sonar_issues["msr"][4]["frmt_val"].to_i, 200) %>%;"></span></span></div>
<div><span class="analysis-info-icon mr5"></span><span class="fontGrey3 mr45">信息</span><span class="fontBlue2 w70 pInline"><%= @sonar_issues["msr"][5]["frmt_val"] %></span><span class="quality-percentage"><span class="quality-percentage-rate" style="width:<%= statistics_result_percentage(@sonar_issues["msr"][5]["frmt_val"].to_i, 200) %>%;"></span></span></div>
</div>
</div>
<div class="analysis-tag-wrap f16"> <span class="analysis-tag fl mr15"></span> <span class="fb fl mr10">代码规模</span><span class="fontGrey2">可定性评价为:<span class="c_red"><%= lines_scale(@complexity["msr"][0]["frmt_val"]) %></span></span></div>
<div class="analysis-block mt10 mb40 flex f14">
<div class="analysis-genral">
<p class="fontGrey3">代码行数</p>
<p class="fontBlue2"><%= @complexity["msr"][0]["frmt_val"] %></p>
</div>
<div class="analysis-genral">
<p class="fontGrey3">文件</p>
<p class="fontBlue2"><%= @complexity["msr"][2]["frmt_val"] %></p>
</div>
<div class="analysis-genral">
<p class="fontGrey3">目录</p>
<p class="fontBlue2"><%= @complexity["msr"][3]["frmt_val"] %></p>
</div>
<div class="analysis-genral">
<p class="fontGrey3">类</p>
<p class="fontBlue2"><%= @complexity["msr"][1]["frmt_val"] %></p>
</div>
<div class="analysis-genral">
<p class="fontGrey3">方法</p>
<p class="fontBlue2"><%= @complexity["msr"][4]["frmt_val"] %></p>
</div>
</div>
<!--<div class="analysis-tag-wrap f16"> <span class="analysis-tag fl mr15"></span> <span class="fb fl">贡献统计</span></div>-->
<!--<div class="analysis-block mt10 f12">-->
<!--<ul class="contribute-list">-->
<!--<li class="fl fontGrey2 contribute-list-avatar">&nbsp;</li>-->
<!--<li class="fl fontGrey2 contribute-list-code">代码行数</li>-->
<!--<li class="fl fontGrey2 contribute-list-problem">引入质量问题数</li>-->
<!--<li class="fl fontGrey2 contribute-list-rate">引入质量问题数/代码行数</li>-->
<!--<div class="cl"></div>-->
<!--</ul>-->
<!--<ul class="contribute-list">-->
<!--<li class="fl fontGrey2 contribute-list-avatar contribute-list-height">-->
<!--<div class="mt8"><img src="images/homepageImage.jpg" width="50" class="image-cir" />-->
<!--<p class="fontGrey2 hidden">小明</p>-->
<!--</div>-->
<!--</li>-->
<!--<li class="fl fontGrey2 contribute-list-code contribute-list-height contribute-list-line-height">18340</li>-->
<!--<li class="fl fontGrey2 contribute-list-problem contribute-list-height contribute-list-line-height">230</li>-->
<!--<li class="fl contribute-list-rate fontBlue2 contribute-list-height contribute-list-line-height">.012540</li>-->
<!--<div class="cl"></div>-->
<!--</ul>-->
<!--<ul class="contribute-list">-->
<!--<li class="fl fontGrey2 contribute-list-avatar contribute-list-height">-->
<!--<div class="mt8"><img src="images/homepageImage.jpg" width="50" class="image-cir" />-->
<!--<p class="fontGrey2 hidden">小王</p>-->
<!--</div>-->
<!--</li>-->
<!--<li class="fl fontGrey2 contribute-list-code contribute-list-height contribute-list-line-height">834</li>-->
<!--<li class="fl fontGrey2 contribute-list-problem contribute-list-height contribute-list-line-height">34</li>-->
<!--<li class="fl contribute-list-rate fontBlue2 contribute-list-height contribute-list-line-height">.04077</li>-->
<!--<div class="cl"></div>-->
<!--</ul>-->
<!--<ul class="contribute-list">-->
<!--<li class="fl fontGrey2 contribute-list-avatar contribute-list-height">-->
<!--<div class="mt8"><img src="images/homepageImage.jpg" width="50" class="image-cir" />-->
<!--<p class="fontGrey2 hidden">小亮</p>-->
<!--</div>-->
<!--</li>-->
<!--<li class="fl fontGrey2 contribute-list-code contribute-list-height contribute-list-line-height">134</li>-->
<!--<li class="fl fontGrey2 contribute-list-problem contribute-list-height contribute-list-line-height">10</li>-->
<!--<li class="fl contribute-list-rate fontBlue2 contribute-list-height contribute-list-line-height">.07462</li>-->
<!--<div class="cl"></div>-->
<!--</ul>-->
<!--</div>-->

View File

@ -0,0 +1,6 @@
<% if @name_flag %>
<%= render :partial => "result_list" %>
<% else %>
<%= render "show" %>
<% end %>

View File

View File

@ -0,0 +1,23 @@
<div class="f16 fb fontBlue mb10">代码质量分析</div>
<div>
<%= form_tag( url_for(:controller => 'quality_analysis', :action => 'create', :project_id => @project.id, :user_id => User.current.id, :identifier => @repository.identifier), :remote => true, :id => 'quality_analyses_form') do %>
<div class="ui form">
<div class="mb10" style="margin-right:13px;">
<textarea id="path_description" name="path" rows="8" placeholder="目录相对于根目录,用半角逗号隔开。如:src/main/java,libs,res/script" style="height: 87px; resize:vertical;" class="analysis-option-box"></textarea>
</div>
<div class="mb10">
<div>
<%= select_tag :branch, options_for_select(["#{@gitlab_default_branch}"]+ @branch_names, @rev), :id => 'branch', :class => "analysis-option-box" %>
</div>
</div>
<div class="mb10">
<div>
<%= select_tag :language, options_for_select(["java","python","ruby","c++","c#", "Web"]), :id => 'branch', :class => "analysis-option-box" %>
</div>
</div>
<div class="courseSendSubmit mr15"><a href="javascript:void(0);" class="sendSourceText" onclick="$('#quality_analyses_form').submit();hideModal()">提交</a></div>
<div class="courseSendCancel"><a href="javascript:void(0);" class="sendSourceText" onclick="hideModal()">取消</a></div>
<div class="cl"></div>
</div>
<% end %>
</div>

View File

@ -0,0 +1,76 @@
<%= javascript_include_tag 'highcharts','highcharts-more' %>
<div class="project_r_h">
<h2 class="project_h2"><%= l(:label_quality_analyses) %></h2>
</div>
<div class="repository_con " style="line-height:1.9;">
<%#= render :partial => 'navigation' %>
<div class="cl"></div>
</div>
<div id = "container">
</div>
<div id = "container_quality" class="mt30">
</div>
<script>
$(function () {
$('#container').highcharts({
chart: {
type: 'pie',
options3d: {
enabled: true,
alpha: 45,
beta: 0
}
},
title: {
text: 'Browser market shares at a specific website, 2014'
},
tooltip: {
pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
depth: 35,
dataLabels: {
enabled: true,
format: '{point.name}'
}
}
},
series: [{
type: 'pie',
name: 'Browser share',
data: [
['Firefox', 45.0],
['IE', 26.8],
{
name: 'Chrome',
y: 12.8,
sliced: true,
selected: true
},
['Safari', 8.5],
['Opera', 6.2],
['Others', 0.7]
],
dataLabels: {
enabled: true,
style: {
fontSize: '13px',
color: '#aaa',
fontFamily: 'Arial',
textShadow: '0px 0px 6px rgb(0, 0, 0), 0px 0px 3px rgb(f, f, f)',
fontWeight: 'normal'
}
}
}]
});
});
</script>
<p><%= link_to l(:button_back), :action => 'show', :id => @project %></p>
<% html_title(l(:label_repository), l(:label_statistics)) -%>

View File

@ -0,0 +1,8 @@
$('#ajax-modal').html('<%= escape_javascript( render :partial => 'repositories/quality_analysis', :locals => {}) %>');
showModal('ajax-modal', '615px');
$('#ajax-modal').siblings().remove();
$('#ajax-modal').before("<a href='javascript:void(0)' onclick='hideModal()' style='margin-left: 580px;'><img src='/images/bid/close.png' width='26px' height='26px' /></a>");
$('#ajax-modal').parent().css("top","20%").css("left","32%").css("border","3px solid #269ac9");
$('#ajax-modal').parent().addClass("popbox_polls");

View File

@ -2,7 +2,13 @@
<div class="project_r_h">
<div class="fl"><h2 class="project_h2_repository"><%= render :partial => 'breadcrumbs', :locals => {:path => @path, :kind => 'dir', :revision => @rev} %></h2></div>
<a href="<%= @zip_path %>" class="btn_zipdown fr" onclick="">ZIP下载</a>
<% if is_project_manager?(User.current, @project.id) && QualityAnalysis.where(:project_id => @project.id).first.nil? %>
<%# if User.current.member_of?(@project) %>
<%= link_to "质量分析", quality_analysis_path(:id => @project.id), :remote => true, :class => "btn_zipdown fr" %>
<%# end %>
<% else %>
<%= link_to "质量分析", project_quality_analysis_path(:project_id => @project.id, :resource_id => @proje), :class => "btn_zipdown fr" %>
<% end %>
</div>
<div class="repository_con" style="line-height:1.9;">
<% if @entries.nil? %>
@ -45,7 +51,7 @@
<div class="commit_content_dec fl" title="<%= @changesets_latest_coimmit.comments %>"><%= @changesets_latest_coimmit.message %></div>
</span>
<% else %>
<span class="fl"><div class="fb fontGrey3 mr5 fl hidden maxwidth150"><%=@changesets_latest_coimmit.author_email %></div>
<span class="fl"><div class="fb fontGrey3 mr5 fl hidden maxwidth150"><%=@changesets_latest_coimmit.author_email %></div>
<div class="fl">提交于<%= time_tag(@changesets_latest_coimmit.created_at) %></div>
<div class="commit_content_dec fl" title="<%= @changesets_latest_coimmit.comments %>"><%= @changesets_latest_coimmit.message %></div>
</span>
@ -57,7 +63,6 @@
<span class="fr mr5"><font class="fb ml2 mr2 vl_commit">
<%=link_to @changesets_all_count, {:action => 'changes', :path => to_path_param(@path), :id => @project, :repository_id => @repository.identifier_param, :rev => @rev,:page=>1 ,:commit_count =>"#{@changesets_all_count}"} %></font> 提交
</span>
</div>
<% end %>

View File

@ -602,6 +602,7 @@ en:
label_time_tracking: Time tracking
label_change_plural: Changes
label_statistics: Statistics
label_quality_analyses: Quality Analyses
label_commits_per_month: Commits per month
label_commits_per_author: Commits per author
label_diff: diff

View File

@ -732,6 +732,7 @@ zh:
label_time_tracking: 时间跟踪
label_change_plural: 变更
label_statistics: 统计
label_quality_analyses: 质量分析
label_commits_per_month: 每月提交次数
label_commits_per_author: 每用户提交次数

View File

@ -785,6 +785,13 @@ RedmineApp::Application.routes.draw do
end
end
resources :quality_analysis, :only => [:index, :new, :create] do
collection do
end
member do
end
end
# resources :files, :only => [:index, :new, :create] do
# member do
# match "quote_resource_show_project",:via => [:get]
@ -905,6 +912,7 @@ RedmineApp::Application.routes.draw do
# repositories routes
get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats', :as => "stats_repository_project"
get 'projects/:id/repository/:repository_id/quality_analysis', :to => 'repositories#quality_analysis', :as => "quality_analysis"
get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))', :to => 'repositories#changes'
@ -923,6 +931,7 @@ RedmineApp::Application.routes.draw do
}
get 'projects/:id/repository/statistics', :to => 'repositories#stats'
get 'projects/:id/repository/graph', :to => 'repositories#graph'
get 'projects/:id/repository/changes(/*path(.:ext))', :to => 'repositories#changes'

View File

@ -0,0 +1,11 @@
class CreateQualityAnalyses < ActiveRecord::Migration
def change
create_table :quality_analyses do |t|
t.integer :project_id
t.string :author_login
t.string :rep_identifier
t.timestamps
end
end
end

View File

@ -0,0 +1,5 @@
class AddJkVersionToQualityAnalysis < ActiveRecord::Migration
def change
add_column :quality_analyses, :sonar_version, :integer, :default => false
end
end

View File

@ -0,0 +1,6 @@
class AddColumnToQualityAnalyses < ActiveRecord::Migration
def change
add_column :quality_analyses, :path, :string
add_column :quality_analyses, :branch, :string
end
end

View File

@ -0,0 +1,5 @@
class AddLanguaeToQualityAnalyses < ActiveRecord::Migration
def change
add_column :quality_analyses, :language, :string
end
end

View File

@ -0,0 +1,5 @@
class AddNameToQualityAnalyses < ActiveRecord::Migration
def change
add_column :quality_analyses, :language, :string
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1247,6 +1247,39 @@ a.pages-big{ width:50px;}
.red-cir-btn{ background:#e74c3c; padding:1px 5px; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; color:#fff; font-weight:normal;font-size:12px;}
.green-cir-btn{ background:#28be6c; padding:1px 5px; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; color:#fff; font-weight:normal;font-size:12px;}
/*20160622质量分析*/
.analysis-tag-wrap {width:100%; color:#000; height:20px; line-height:20px; vertical-align:middle;}
.analysis-tag {width:10px; height:20px; background-color:#777;}
.analysis-block {padding:15px; border:1px solid #d9d9d9;}
.flex {display:flex;}
.analysis-genral {flex:1; display:block; text-align:center;}
.analysis-block-icon {background:url(../images/code-analysis-icon.png) -2px -8px no-repeat; width:14px; height:14px; display:inline-block; vertical-align:middle;}
.analysis-serious-icon {background:url(../images/code-analysis-icon.png) -2px -34px no-repeat; width:14px; height:14px; display:inline-block; vertical-align:middle;}
.analysis-main-icon {background:url(../images/code-analysis-icon.png) -2px -59px no-repeat; width:14px; height:14px; display:inline-block; vertical-align:middle;}
.analysis-secondary-icon {background:url(../images/code-analysis-icon.png) -2px -85px no-repeat; width:14px; height:14px; display:inline-block; vertical-align:middle;}
.analysis-info-icon {background:url(../images/code-analysis-icon.png) -2px -111px no-repeat; width:14px; height:14px; display:inline-block; vertical-align:middle;}
.quality-percentage {width:320px; height:14px; display:inline-block;}
.quality-percentage-rate {width:50%; height:14px; background-color:#0a6c99; display:inline-block;}
.image-cir {border-radius:50%;}
.analysis-genral-icon {position:absolute; padding:1px 5px; display:inline-block; top:5px;}
.contribute-list-avatar {width:80px; vertical-align:middle; text-align:center;}
.contribute-list-code {width:160px; vertical-align:middle; text-align:center;}
.contribute-list-problem {width:170px; vertical-align:middle; text-align:center;}
.contribute-list-rate {width:228px; vertical-align:middle; text-align:center;}
.contribute-list-height {height:80px;}
.contribute-list-line-height {line-height:80px;}
/*20160623分析结果*/
.analysis-result-list {padding:5px;}
.analysis-result-list:nth-of-type(odd){background:#fff;}/*奇数行*/
.analysis-result-list:nth-of-type(even){background:#f5f5f5;}/*偶数行*/
.analysis-result-name {width:200px; cursor:pointer;}
.analysis-result-version {width:90px; text-align:right; cursor:pointer;}
.analysis-result-loc {width:60px; text-align:right; cursor:pointer;}
.analysis-result-debt {width:160px; text-align:right; cursor:pointer;}
.analysis-result-time {width:150px; text-align:right; cursor:pointer;}
.analysis-name-icon {background:url(../images/code-analysis-icon.png) -2px -148px no-repeat; width:16px; height:16px; display:inline-block; vertical-align:middle;}
/*未登录回复提示*/
.visitor-box {width:620px; height:33px; line-height:33px; text-align:center; vertical-align: middle; border:1px solid #ccc; background-color: #fff;}
@ -1254,4 +1287,4 @@ a.pages-big{ width:50px;}
.H60 {height:60px !important;}
.W420 {width:420px;}
.W300 {width:300px !important;}
.W600{ width:600px;}
.W600{ width:600px;}

View File

@ -27,6 +27,7 @@ a.btn_message_free{ background:#ff5722; display:block; text-align:center; color
h2{ font-size:18px; }
h3{ font-size:14px; }
h4{ font-size:14px; }
.f8 {font-size:8px;}
.f12{font-size:12px; font-weight:normal;}
.f14{font-size:14px;}
.f16{font-size:16px;}
@ -130,6 +131,7 @@ h4{ font-size:14px; }
.mt12 { margin-top:12px !important;}
.mt15 {margin-top:15px;}
.mt19 {margin-top:19px !important;}
.mt35 {margin-top:35px;}
.ml70{margin-left: 70px;}
.mb0 {margin-bottom: 0px !important;}
.mb4{ margin-bottom:4px;}
@ -137,6 +139,8 @@ h4{ font-size:14px; }
.mb8 {margin-bottom:8px;}
.mb10{ margin-bottom:10px !important;}
.mb20{ margin-bottom:20px;}
.mb30 {margin-bottom:30px;}
.mb40 {margin-bottom:40px;}
.pl10 {padding-left:10px;}
.pl15{ padding-left:15px;}
.pl5{ padding-left:5px;}
@ -228,6 +232,7 @@ a.c_green{ color:#28be6c;}
.b_grey{ background: #F5F5F5;}
.b_dgrey{ background: #CCC;}
.c_white {color:#fff;}
.c_orange{color:#e8770d;}
.c_dark{ color:#2d2d2d;}
.c_lorange{ color:#ff9900;}
@ -239,6 +244,11 @@ a.c_green{ color:#28be6c;}
.c_dblue{ color:#09658c;}
.b_blue{background:#64bdd9;}
.b_green{background:#28be6c;}
.b_slow_yellow{background:#adde18;}
.b_yellow{background:#DDDF0D;}
.b_slow_red{background:#df8538;}
.b_green2 {background:#63c360;}
.b_red {background:#d60308;}
.b_w{ background:#fff !important;}
/*add by Tim*/
@ -341,6 +351,8 @@ a:hover.bgreen_n_btn{background:#08a384;}
.orange_btn_cir{ background:#e67e22; padding:1px 10px; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; color:#fff; font-weight:normal; font-size:12px;white-space:nowrap;}
.bgreen_btn_cir{ background:#1abc9c; padding:1px 10px; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; color:#fff; font-weight:normal; font-size:12px;white-space:nowrap;}
.grey_border{border:1px solid #dddddd !important;}
.borderRadius {border-radius:5px;}
.tac {text-align:center;}
/* commonpic */
.pic_date{ display:block; background:url(../images/new_project/public_icon.png) -31px 0 no-repeat; width:16px; height:15px; float:left;}
.pic_add{ display:block; background:url(../images/new_project/public_icon.png) -31px -273px no-repeat; width:16px; height:15px; float:left;}
@ -1167,4 +1179,7 @@ a.shadowbox_news_all{ display:block; width:305px; height:40px; line-height:40px;
/*未登录回复提示*/
.visitor-box {width:620px; height:33px; line-height:33px; text-align:center; vertical-align: middle; border:1px solid #ccc; background-color: #fff;}
.reply_iconup{ position:absolute; top:21px; left:13px; color:#d4d4d4; font-size:16px; background:#f1f1f1; line-height:13px;}
.reply_iconup{ position:absolute; top:21px; left:13px; color:#d4d4d4; font-size:16px; background:#f1f1f1; line-height:13px;}
/*20160622代码分析弹窗*/
.analysis-option-box {width:100%; border:1px solid #ccc; padding:3px 5px;}

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe QualityAnalysisController, :type => :controller do
end

View File

@ -0,0 +1,7 @@
FactoryGirl.define do
factory :quality_analyasis, class: 'QualityAnalysis' do
project_id 1
author_login "MyString"
rep_identifier "MyString"
end
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe QualityAnalysis, :type => :model do
pending "add some examples to (or delete) #{__FILE__}"
end