Permalink
Browse files

init

  • Loading branch information...
0 parents commit 477105d72bd7a389468e6eb33dd7ae10c2143574 @kawasima committed Oct 31, 2011
Showing with 10,057 additions and 0 deletions.
  1. +4 −0 README.rdoc
  2. +25 −0 app/controllers/impasse/abstract_controller.rb
  3. +115 −0 app/controllers/impasse/execution_bugs_controller.rb
  4. +159 −0 app/controllers/impasse/executions_controller.rb
  5. +139 −0 app/controllers/impasse/test_case_controller.rb
  6. +105 −0 app/controllers/impasse/test_plans_controller.rb
  7. +16 −0 app/helpers/impasse/common_helper.rb
  8. +5 −0 app/helpers/impasse/executions_helper.rb
  9. +5 −0 app/helpers/impasse/test_plans_helper.rb
  10. +10 −0 app/models/impasse/execution.rb
  11. +9 −0 app/models/impasse/execution_bug.rb
  12. +136 −0 app/models/impasse/node.rb
  13. +6 −0 app/models/impasse/node_type.rb
  14. +66 −0 app/models/impasse/statistics.rb
  15. +9 −0 app/models/impasse/test_case.rb
  16. +23 −0 app/models/impasse/test_plan.rb
  17. +30 −0 app/models/impasse/test_plan_case.rb
  18. +8 −0 app/models/impasse/test_step.rb
  19. +7 −0 app/models/impasse/test_suite.rb
  20. +13 −0 app/views/impasse/common/_impasse_tabs.html.erb
  21. +65 −0 app/views/impasse/common/_impasse_util_js.html.erb
  22. +14 −0 app/views/impasse/execution_bugs/_new.html.erb
  23. +27 −0 app/views/impasse/executions/_edit.html.erb
  24. +160 −0 app/views/impasse/executions/_execute_js.html.erb
  25. +51 −0 app/views/impasse/executions/index.html.erb
  26. +1 −0 app/views/impasse/executions/put.html.erb
  27. +5 −0 app/views/impasse/test_case/_edit.html.erb
  28. +42 −0 app/views/impasse/test_case/_form.test_case.html.erb
  29. +7 −0 app/views/impasse/test_case/_form.test_suite.html.erb
  30. +5 −0 app/views/impasse/test_case/_new.html.erb
  31. +287 −0 app/views/impasse/test_case/_treejs.html.erb
  32. +33 −0 app/views/impasse/test_case/index.html.erb
  33. +1 −0 app/views/impasse/test_case/show.html.erb
  34. +9 −0 app/views/impasse/test_plans/_form.html.erb
  35. +187 −0 app/views/impasse/test_plans/_planjs.html.erb
  36. +5 −0 app/views/impasse/test_plans/_statistics_js.html.erb
  37. +140 −0 app/views/impasse/test_plans/_user_assign_js.html.erb
  38. +12 −0 app/views/impasse/test_plans/edit.html.erb
  39. +1 −0 app/views/impasse/test_plans/execution.html.erb
  40. +41 −0 app/views/impasse/test_plans/index.html.erb
  41. +4 −0 app/views/impasse/test_plans/new.html.erb
  42. +31 −0 app/views/impasse/test_plans/show.html.erb
  43. +37 −0 app/views/impasse/test_plans/statistics.html.erb
  44. +25 −0 app/views/impasse/test_plans/statistics/_daily.html.erb
  45. +19 −0 app/views/impasse/test_plans/statistics/_default.html.erb
  46. +31 −0 app/views/impasse/test_plans/statistics/_members.html.erb
  47. +30 −0 app/views/impasse/test_plans/tc_assign.html.erb
  48. +40 −0 app/views/impasse/test_plans/user_assign.html.erb
  49. +57 −0 assets/javascripts/excanvas.min.js
  50. +57 −0 assets/javascripts/jqplot.json2.min.js
  51. +791 −0 assets/javascripts/jquery-ui.js
  52. +499 −0 assets/javascripts/jquery.blockUI.js
  53. +96 −0 assets/javascripts/jquery.cookie.js
  54. +99 −0 assets/javascripts/jquery.hotkeys.js
  55. +57 −0 assets/javascripts/jquery.jqplot.min.js
  56. +18 −0 assets/javascripts/jquery.js
  57. +4,551 −0 assets/javascripts/jquery.jstree.js
  58. BIN assets/stylesheets/images/Thumbs.db
  59. BIN assets/stylesheets/images/book-brown.png
  60. BIN assets/stylesheets/images/bug.png
  61. BIN assets/stylesheets/images/cross.png
  62. BIN assets/stylesheets/images/document-attribute-t.png
  63. BIN assets/stylesheets/images/documents-stack.png
  64. BIN assets/stylesheets/images/tick.png
  65. BIN assets/stylesheets/images/ui-bg_flat_0_aaaaaa_40x100.png
  66. BIN assets/stylesheets/images/ui-bg_flat_75_ffffff_40x100.png
  67. BIN assets/stylesheets/images/ui-bg_glass_55_fbf9ee_1x400.png
  68. BIN assets/stylesheets/images/ui-bg_glass_65_ffffff_1x400.png
  69. BIN assets/stylesheets/images/ui-bg_glass_75_dadada_1x400.png
  70. BIN assets/stylesheets/images/ui-bg_glass_75_e6e6e6_1x400.png
  71. BIN assets/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png
  72. BIN assets/stylesheets/images/ui-bg_highlight-soft_75_cccccc_1x100.png
  73. BIN assets/stylesheets/images/ui-icons_222222_256x240.png
  74. BIN assets/stylesheets/images/ui-icons_2e83ff_256x240.png
  75. BIN assets/stylesheets/images/ui-icons_454545_256x240.png
  76. BIN assets/stylesheets/images/ui-icons_888888_256x240.png
  77. BIN assets/stylesheets/images/ui-icons_cd0a0a_256x240.png
  78. BIN assets/stylesheets/images/wall-brick.png
  79. +568 −0 assets/stylesheets/jquery-ui.css
  80. +258 −0 assets/stylesheets/jquery.jqplot.css
  81. BIN assets/stylesheets/themes/default/d.gif
  82. BIN assets/stylesheets/themes/default/d.png
  83. BIN assets/stylesheets/themes/default/file.png
  84. BIN assets/stylesheets/themes/default/folder.png
  85. BIN assets/stylesheets/themes/default/root.png
  86. +74 −0 assets/stylesheets/themes/default/style.css
  87. BIN assets/stylesheets/themes/default/throbber.gif
  88. +43 −0 config/locales/en.yml
  89. +42 −0 config/locales/ja.yml
  90. +24 −0 config/routes.rb
  91. +13 −0 db/migrate/20111011092201_create_node_types.rb
  92. +15 −0 db/migrate/20111011092256_create_nodes.rb
  93. +14 −0 db/migrate/20111011092515_create_test_plans.rb
  94. +13 −0 db/migrate/20111011092621_create_test_suites.rb
  95. +21 −0 db/migrate/20111011093032_create_test_cases.rb
  96. +16 −0 db/migrate/20111011093136_create_test_steps.rb
  97. +18 −0 db/migrate/20111018002623_create_executions.rb
  98. +12 −0 db/migrate/20111018002749_create_execution_bugs.rb
  99. +14 −0 db/migrate/20111019005654_create_test_plan_cases.rb
  100. +29 −0 init.rb
  101. +4 −0 lang/en.yml
  102. +120 −0 script/proxy.rb
  103. +9 −0 test/fixtures/execution_bugs.yml
  104. +15 −0 test/fixtures/executions.yml
  105. +23 −0 test/fixtures/node_hierarchies.yml
  106. +11 −0 test/fixtures/node_types.yml
  107. +59 −0 test/fixtures/test_case_versions.yml
  108. +23 −0 test/fixtures/test_plan_test_cases.yml
  109. +19 −0 test/fixtures/test_plans.yml
  110. +27 −0 test/fixtures/test_steps.yml
  111. +11 −0 test/fixtures/test_suites.yml
  112. +8 −0 test/functional/executions_controller_test.rb
  113. +8 −0 test/functional/impasse_bugs_controller_test.rb
  114. +8 −0 test/functional/test_case_controller_test.rb
  115. +8 −0 test/functional/test_plans_controller_test.rb
  116. +5 −0 test/test_helper.rb
  117. +10 −0 test/unit/execution_bug_test.rb
  118. +10 −0 test/unit/execution_test.rb
  119. +10 −0 test/unit/node_hierarchy_test.rb
  120. +10 −0 test/unit/node_type_test.rb
  121. +10 −0 test/unit/test_case_version_test.rb
  122. +10 −0 test/unit/test_plan_test.rb
  123. +10 −0 test/unit/test_plan_test_case_test.rb
  124. +10 −0 test/unit/test_plans_test.rb
  125. +10 −0 test/unit/test_step_test.rb
  126. +10 −0 test/unit/test_suite_test.rb
4 README.rdoc
@@ -0,0 +1,4 @@
+= impasse
+
+Impasse is test management tool like Testlink.
+
25 app/controllers/impasse/abstract_controller.rb
@@ -0,0 +1,25 @@
+module Impasse
+ class AbstractController < ApplicationController
+ unloadable
+ def require_login
+ if !User.current.logged?
+ # Extract only the basic url parameters on non-GET requests
+ if request.get?
+ url = url_for(params)
+ else
+ url = url_for(:controller => "/params[:controller]", :action => params[:action], :id => params[:id], :project_id => params[:project_id])
+ end
+
+ respond_to do |format|
+ format.html { redirect_to :controller => "/account", :action => "login", :back_url => url }
+ format.atom { redirect_to :controller => "/account", :action => "login", :back_url => url }
+ format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
+ format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
+ format.json { head :unauthorized }
+ end
+ return false
+ end
+ true
+ end
+ end
+end
115 app/controllers/impasse/execution_bugs_controller.rb
@@ -0,0 +1,115 @@
+module Impasse
+ class ExecutionBugsController < ApplicationController
+ unloadable
+
+ menu_item :impasse
+ before_filter :find_project_by_project_id, :only => [:new, :create]
+ before_filter :check_for_default_issue_status, :only => [:new, :create]
+ before_filter :build_new_issue_from_params, :only => [:new, :create]
+
+ helper :journals
+ helper :projects
+ include ProjectsHelper
+ helper :custom_fields
+ include CustomFieldsHelper
+ helper :issue_relations
+ include IssueRelationsHelper
+ helper :watchers
+ include WatchersHelper
+ helper :attachments
+ include AttachmentsHelper
+ helper :queries
+ include QueriesHelper
+ helper :repositories
+ include RepositoriesHelper
+ helper :sort
+ include SortHelper
+ include IssuesHelper
+
+ def new
+ respond_to do |format|
+ format.html { render :partial => 'new' }
+ end
+ end
+
+ def create
+ call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
+ @issue.save!
+
+ execution_bug = ExecutionBug.new(:execution_id => params[:execution_bug][:execution_id], :bug_id => @issue.id)
+ execution_bug.save!
+
+ flash[:notice] = l(:notice_successful_create)
+
+ respond_to do |format|
+ format.json { render :json => {status => true} }
+ end
+ end
+
+
+ def edit
+ update_issue_from_params
+
+ @journal = @issue.current_journal
+
+ respond_to do |format|
+ format.html { }
+ format.xml { }
+ end
+ end
+
+ def update
+ update_issue_from_params
+
+ if @issue.save_issue_with_child_records(params, @time_entry)
+ render_attachment_warning_if_needed(@issue)
+ flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
+
+ respond_to do |format|
+ format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
+ format.api { head :ok }
+ end
+ else
+ render_attachment_warning_if_needed(@issue)
+ flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
+ @journal = @issue.current_journal
+
+ respond_to do |format|
+ format.html { render :action => 'edit' }
+ format.api { render_validation_errors(@issue) }
+ end
+ end
+ end
+
+ def build_new_issue_from_params
+ if params[:id].blank?
+ @issue = Issue.new
+ @issue.copy_from(params[:copy_from]) if params[:copy_from]
+ @issue.project = @project
+ else
+ @issue = @project.issues.visible.find(params[:id])
+ end
+ @issue.project = @project # Tracker must be set before custom field values
+ @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
+ if @issue.tracker.nil?
+ render_error l(:error_no_tracker_in_project)
+ return false
+ end
+
+ if params[:issue].is_a?(Hash)
+ @issue.safe_attributes = params[:issue]
+ end
+ @issue.start_date ||= Date.today
+ @issue.author = User.current
+ @priorities = IssuePriority.all
+ @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
+ end
+
+ def check_for_default_issue_status
+ if IssueStatus.default.nil?
+ render_error l(:error_no_default_issue_status)
+ return false
+ end
+ end
+end
+end
159 app/controllers/impasse/executions_controller.rb
@@ -0,0 +1,159 @@
+require 'erb'
+
+module Impasse
+ class ExecutionsController < AbstractController
+ unloadable
+
+ REL = {1=>"test_project", 2=>"test_suite", 3=>"test_case"}
+
+ menu_item :impasse
+ before_filter :find_project_by_project_id, :authorize
+
+ include ActionView::Helpers::AssetTagHelper
+
+ def index
+ params[:tab] = 'execution'
+ @test_plan = TestPlan.find(params[:id])
+ end
+
+ def put
+ @node = Node.find(params[:test_plan_case][:test_case_id])
+ test_case_ids = (@node.is_test_case?) ? [ @node.id ] : @node.all_decendant_cases.collect{|tc| tc.id}
+
+ status = true
+ for test_case_id in test_case_ids
+ @test_plan_case = TestPlanCase.find(:first, :conditions=>[
+ "test_plan_id=? AND test_case_id=?", params[:test_plan_case][:test_plan_id], test_case_id])
+ next if @test_plan_case.nil?
+ @execution = Execution.find_or_initialize_by_test_plan_case_id(@test_plan_case.id)
+ @execution.attributes = params[:execution]
+ @execution.execution_ts = Time.now.to_datetime
+ status &= @execution.save
+ end
+
+ respond_to do |format|
+ format.json { render :json => { :status => status } }
+ end
+ end
+
+ def get_list
+ sql = <<-'END_OF_SQL'
+SELECT T.*, users.login, exec.expected_date, exec.status
+FROM (
+ SELECT distinct parent.*, tpc.test_plan_id
+ FROM impasse_nodes AS parent
+ LEFT JOIN impasse_nodes AS child
+ ON INSTR(child.path, parent.path) > 0
+ LEFT JOIN impasse_test_cases AS tc
+ ON child.id = tc.id
+ LEFT JOIN impasse_test_plan_cases AS tpc
+ ON tc.id=tpc.test_case_id
+ WHERE tpc.test_plan_id=:test_plan_id
+<% if conditions.include? :path %>
+ AND parent.path LIKE :path
+<% end %>
+) AS T
+LEFT JOIN impasse_test_plan_cases AS tpcs
+ ON T.id=tpcs.test_case_id AND T.test_plan_id = tpcs.test_plan_id
+LEFT JOIN impasse_executions AS exec
+ ON tpcs.id = exec.test_plan_case_id
+LEFT OUTER JOIN users
+ ON users.id = exec.tester_id
+WHERE 1=1
+<% if conditions.include? :user_id %>
+ AND (tester_id = :user_id OR T.node_type_id != 3)
+<% end %>
+ORDER BY LENGTH(T.path) - LENGTH(REPLACE(T.path,'.','')), T.node_order
+END_OF_SQL
+
+ conditions = { :test_plan_id => params[:test_plan_id] }
+
+ if !params[:id].nil? and params[:id] != "-1" # TODO
+ node = Node.find(params[:id])
+ conditions[:path] = "#{node.path}_%"
+ end
+
+ if params[:myself]
+ conditions[:user_id] = User.current.id
+ end
+
+ @nodes = Node.find_by_sql([ERB.new(sql).result(binding), conditions])
+
+ jstree_nodes = convert(@nodes, params[:prefix])
+
+ respond_to do |format|
+ format.json { render :json => jstree_nodes }
+ end
+ end
+
+ def edit
+ sql = <<-END_OF_SQL
+SELECT impasse_executions.*
+FROM impasse_executions
+WHERE exists (
+ SELECT *
+ FROM impasse_test_plan_cases AS tpc
+ WHERE impasse_executions.test_plan_case_id = tpc.id
+ AND tpc.test_plan_id=? AND tpc.test_case_id=?)
+END_OF_SQL
+ executions = Execution.find_by_sql [sql, params[:test_plan_case][:test_plan_id], params[:test_plan_case][:test_case_id]]
+ if executions.size == 0
+ @execution = Execution.new
+ @execution.test_plan_case = TestPlanCase.find_by_test_plan_id_and_test_case_id(
+ params[:test_plan_case][:test_plan_id], params[:test_plan_case][:test_case_id])
+ else
+ @execution = executions.first
+ end
+ @execution.attributes = params[:execution]
+ if request.post? and @execution.save
+ respond_to do |format|
+ format.json { render :json => {'status'=>true} }
+ end
+ end
+ respond_to do |format|
+ format.html { render :partial=>'edit' }
+ end
+ end
+
+ def convert(nodes, prefix='node')
+ node_map = {}
+ jstree_nodes = []
+
+ for node in nodes
+ jstree_node = {
+ 'attr' => {'id' => "#{prefix}_#{node.id}" , 'rel' => REL[node.node_type_id]},
+ 'data' => {},
+ 'children'=>[]}
+ jstree_node['data']['title'] = node.name
+ if node.node_type_id != 3
+ jstree_node['state'] = 'open'
+ end
+ if !node.login.nil? or !node.expected_date.nil?
+
+ jstree_node['data']['title'] << " (#{node.login} #{node.expected_date})"
+ end
+
+ jstree_node['data']['icon'] = status_icon(node.status) if node.node_type_id == 3
+
+ node_map[node.id] = jstree_node
+ if node_map.include? node.parent_id
+ # non-root node
+ node_map[node.parent_id]['children'] << jstree_node
+ else
+ #root node
+ jstree_nodes << jstree_node
+ end
+ end
+ jstree_nodes
+ end
+
+ def status_icon(status)
+ [
+ image_path("../stylesheets/images/document-attribute-t.png", :plugin=>'redmine_impasse'),
+ image_path("../stylesheets/images/tick.png", :plugin=>'redmine_impasse'),
+ image_path("../stylesheets/images/cross.png", :plugin=>'redmine_impasse'),
+ image_path("../stylesheets/images/wall-brick.png", :plugin=>'redmine_impasse'),
+ ][status.to_i]
+ end
+ end
+end
139 app/controllers/impasse/test_case_controller.rb
@@ -0,0 +1,139 @@
+module Impasse
+ class TestCaseController < AbstractController
+ unloadable
+
+ REL = {1=>"test_project", 2=>"test_suite", 3=>"test_case"}
+
+ menu_item :impasse
+ before_filter :find_project, :authorize
+
+ def index
+ @nodes = Node.find(:all, :conditions => ["name=? and node_type_id=?", @project.name, 1])
+ end
+
+ def list
+ @nodes = Node.find_children(params[:node_id], params[:test_plan_id])
+ jstree_nodes = convert(@nodes, params[:prefix])
+
+ respond_to do |format|
+ format.json { render :json => jstree_nodes }
+ end
+ end
+
+ def new
+ @node = Node.new(params[:node])
+
+ case params[:node_type]
+ when 'test_case'
+ @test_case = TestCase.new(params[:test_case])
+ @node.node_type_id = 3
+ else
+ @test_case = TestSuite.new(params[:test_case])
+ @node.node_type_id = 2
+ end
+
+ if request.post? and @node.save
+ @test_case.id = @node.id
+ if @node.is_test_case? and params.include? :test_steps
+ test_steps = params[:test_steps].collect{|i, ts| TestStep.new(ts) }
+ @test_case.test_steps.replace(test_steps)
+ end
+ @test_case.save!
+
+ respond_to do |format|
+ format.json { render :json => @test_case }
+ end
+ else
+ render :partial => 'new'
+ end
+ end
+
+ def edit
+ @node = Node.find(params[:node][:id])
+ old_node = @node.clone
+
+ case params[:node_type]
+ when 'test_case'
+ @test_case = TestCase.find(params[:node][:id])
+ else
+ @test_case = TestSuite.find(params[:node][:id])
+ end
+
+ if request.post?
+ @node.attributes = params[:node]
+ @node.save!
+ @node.update_siblings_order!(old_node)
+ # If node has children, must update the node path of child nodes.
+ @node.update_child_nodes_path(old_node.path)
+
+
+ @test_case.attributes = params[:test_case]
+ @test_case.save!
+ if @node.is_test_case? and params.include? :test_steps
+ test_steps = params[:test_steps].collect{|i, ts| TestStep.new(ts) }
+ @test_case.test_steps.replace(test_steps)
+ end
+
+ respond_to do |format|
+ format.json { render :json => @test_case }
+ end
+ else
+ render :partial => 'edit'
+ end
+ end
+
+ def destroy
+ @node = Node.find(params[:node][:id])
+ case @node.node_type_id
+ when 2
+ TestSuite.delete(@node.id)
+ end
+
+ @node.delete
+
+ respond_to do |format|
+ format.json { render :json => params[:node][:id] }
+ end
+ end
+
+ private
+ def find_project
+ begin
+ @project = Project.find(params[:project_id])
+ @project_node = Node.find(:first, :conditions=>["name=? and node_type_id=?", @project.name, 1])
+ if @project_node.nil?
+ @project_node = Node.new(:name=>@project.name, :node_type_id=>1, :node_order=>1)
+ @project_node.save
+ end
+ rescue ActiveRecord::RecordNotFound
+ render_404
+ end
+ end
+
+ private
+ def convert(nodes, prefix='node')
+ node_map = {}
+ jstree_nodes = []
+
+ for node in nodes
+ jstree_node = {
+ 'attr' => {'id' => "#{prefix}_#{node.id}" , 'rel' => REL[node.node_type_id]},
+ 'data' => { 'title' => node.name },
+ 'children'=>[]}
+ if node.node_type_id != 3
+ jstree_node['state'] = 'open'
+ end
+
+ node_map[node.id] = jstree_node
+ if node_map.include? node.parent_id
+ # non-root node
+ node_map[node.parent_id]['children'] << jstree_node
+ else
+ #root node
+ jstree_nodes << jstree_node
+ end
+ end
+ jstree_nodes
+ end
+ end
+end
105 app/controllers/impasse/test_plans_controller.rb
@@ -0,0 +1,105 @@
+module Impasse
+ class TestPlansController < AbstractController
+ unloadable
+
+ helper :projects
+ include ProjectsHelper
+
+ menu_item :impasse
+ before_filter :find_project_by_project_id, :authorize
+
+ def index
+ @test_plans_by_version, @versions = TestPlan.find_all_by_version(@project)
+ end
+
+ def new
+ @test_plan = TestPlan.new(params[:test_plan])
+ if request.post? and @test_plan.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => :tc_assign, :project_id => @project, :id => @test_plan
+ end
+ @versions = @project.versions
+ end
+
+ def show
+ @test_plan = TestPlan.find(params[:id])
+ end
+
+ def edit
+ @test_plan = TestPlan.find(params[:id])
+ @test_plan.attributes = params[:test_plan]
+ if request.post? and @test_plan.save
+ flash[:notice] = l(:notice_successful_create)
+ redirect_to :action => :edit, :project_id => @project, :id => @test_plan
+ end
+ @versions = @project.versions
+ end
+
+ def tc_assign
+ params[:tab] = 'tc_assign'
+ @versions = @project.versions
+ @test_plan = TestPlan.find(params[:id])
+ end
+
+ def user_assign
+ params[:tab] = 'user_assign'
+ @versions = @project.versions
+ @test_plan = TestPlan.find(params[:id])
+ end
+
+ def statistics
+ @test_plan = TestPlan.find(params[:id])
+ params[:tab] = 'statistics'
+ if params.include? :type
+ @statistics = Statistics.__send__("summary_#{params[:type]}", @test_plan.id)
+ else
+ params[:type] = "default"
+ @statistics = Statistics.summary_default(@test_plan.id)
+ end
+
+ respond_to do |format|
+ if request.xhr?
+ format.html { render :partial => "impasse/test_plans/statistics/#{params[:type]}" }
+ else
+ format.html
+ end
+ end
+ end
+
+ def add_test_case
+ if params.include? :test_case_ids
+ new_cases = 0
+ nodes = Node.find(:all, :conditions => ["id in (?)", params[:test_case_ids]])
+ for node in nodes
+ test_case_ids = []
+ if node.is_test_suite?
+ test_case_ids.concat node.all_decendant_cases.collect{|n| n.id}
+ else
+ test_case_ids << node.id
+ end
+
+ for test_case_id in test_case_ids
+ test_plan_case =
+ TestPlanCase.find_or_create_by_test_case_id_and_test_plan_id(
+ :test_case_id => test_case_id,
+ :test_plan_id => params[:test_plan_id],
+ :node_order => 0,
+ :urgency => 2)
+ new_cases += 1
+ end
+ end
+ end
+
+ respond_to do |format|
+ format.json { render :json => {'num'=>new_cases} }
+ end
+ end
+
+ def remove_test_case
+ TestPlanCase.delete_cascade!(params[:test_plan_id], params[:test_case_id])
+ respond_to do |format|
+ format.json { render :json => { :status => true} }
+ end
+ end
+ end
+end
16 app/helpers/impasse/common_helper.rb
@@ -0,0 +1,16 @@
+module Impasse
+ module CommonHelper
+ unloadable
+
+ TABS = [{:name => 'basic', :url=>{:controller=>:test_plans, :action=>:show}, :label => :label_general},
+ {:name => 'tc_assign', :url=>{:controller=>:test_plans, :action=>:tc_assign},:label => :label_tc_assign},
+ {:name => 'user_assign', :url=>{:controller=>:test_plans, :action=>:user_assign}, :label => :label_user_assign},
+ {:name => 'execution', :url=>{:controller=>:executions, :action=>:index}, :label => :label_execution},
+ {:name => 'statistics', :url=>{:controller=>:test_plans, :action=>:statistics}, :label => :label_statistics}
+ ]
+
+ def render_impasse_tabs
+ render :partial => 'impasse/common/impasse_tabs', :locals => { :tabs => TABS }
+ end
+ end
+end
5 app/helpers/impasse/executions_helper.rb
@@ -0,0 +1,5 @@
+module Impasse
+ module ExecutionsHelper
+ include CommonHelper
+ end
+end
5 app/helpers/impasse/test_plans_helper.rb
@@ -0,0 +1,5 @@
+module Impasse
+ module TestPlansHelper
+ include CommonHelper
+ end
+end
10 app/models/impasse/execution.rb
@@ -0,0 +1,10 @@
+module Impasse
+ class Execution < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_executions"
+
+ belongs_to :test_plan_case
+ has_many :issues, :through => :execution_bugs
+ has_many :execution_bugs
+ end
+end
9 app/models/impasse/execution_bug.rb
@@ -0,0 +1,9 @@
+module Impasse
+ class ExecutionBug < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_execution_bugs"
+
+ belongs_to :issue, :foreign_key => :bug_id
+ belongs_to :execution
+ end
+end
136 app/models/impasse/node.rb
@@ -0,0 +1,136 @@
+module Impasse
+ class Node < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_nodes"
+
+ belongs_to :parent, :class_name=>'Node', :foreign_key=> :parent_id
+ has_many :children, :class_name=> 'Node', :foreign_key=> :parent_id
+
+ def is_test_case?
+ self.node_type_id == 3
+ end
+
+ def is_test_suite?
+ self.node_type_id == 2
+ end
+
+ def self.find_children(node_id, test_plan_id=nil)
+ sql = <<-'END_OF_SQL'
+ SELECT distinct parent.*
+ FROM impasse_nodes AS parent
+ LEFT JOIN impasse_nodes AS child
+ ON INSTR(child.path, parent.path) > 0
+ LEFT JOIN impasse_test_cases AS tc
+ ON child.id = tc.id
+ <% if conditions.include? :test_plan_id %>
+ LEFT JOIN impasse_test_plan_cases AS tpts
+ ON tc.id=tpts.test_case_id
+ <% end %>
+ WHERE 1=1
+ <% if conditions.include? :test_plan_id %>
+ AND tpts.test_plan_id=:test_plan_id
+ <% end %>
+ <% if conditions.include? :path %>
+ AND parent.path LIKE :path
+ <% end %>
+ ORDER BY LENGTH(parent.path) - LENGTH(REPLACE(parent.path,'.','')), node_order
+ END_OF_SQL
+
+ conditions = {}
+
+ unless test_plan_id.nil?
+ conditions[:test_plan_id] = test_plan_id
+ end
+
+ unless node_id.to_i == -1
+ node = find(node_id)
+ conditions[:path] = "#{node.path}_%"
+ end
+
+ find_by_sql([ERB.new(sql).result(binding), conditions])
+ end
+
+ def all_decendant_cases
+ sql = <<-'END_OF_SQL'
+ SELECT distinct parent.*
+ FROM impasse_nodes AS parent
+ LEFT JOIN impasse_nodes AS child
+ ON INSTR(child.path, parent.path) > 0
+ LEFT JOIN impasse_test_cases AS tc
+ ON child.id = tc.id
+ WHERE parent.path LIKE :path
+ AND parent.node_type_id=3
+ END_OF_SQL
+ conditions = {:path => "#{self.path}%"}
+ Node.find_by_sql([ERB.new(sql).result(binding), conditions])
+ end
+
+ def save!
+ recalculate_path
+ super
+ end
+
+ def save
+ recalculate_path
+ super
+ end
+
+ def update_siblings_order!(old_node)
+ sql = if old_node.parent_id == self.parent_id
+ if self.node_order < old_node.node_order
+ <<-END_OF_SQL
+UPDATE impasse_nodes
+SET node_order = node_order + 1
+WHERE parent_id = #{self.parent_id}
+ AND node_order >= #{self.node_order}
+ AND node_order < #{old_node.node_order}
+ AND id != #{self.id}
+ END_OF_SQL
+ else
+ <<-END_OF_SQL
+UPDATE impasse_nodes
+SET node_order = node_order - 1
+WHERE parent_id = #{self.parent_id}
+ AND node_order > #{old_node.node_order}
+ AND node_order <= #{self.node_order}
+ AND id != #{self.id}
+ END_OF_SQL
+ end
+ else
+ <<-END_OF_SQL
+UPDATE impasse_nodes
+SET node_order = node_order + 1
+WHERE parent_id = #{self.parent_id}
+ AND node_order >= #{self.node_order}
+ AND id != #{self.id}
+ END_OF_SQL
+ end
+ connection.update(sql)
+ end
+
+ def update_child_nodes_path(old_path)
+ sql = <<-END_OF_SQL
+ UPDATE impasse_nodes
+ SET path = replace(path, '#{old_path}', '#{self.path}')
+ WHERE path like '#{old_path}_%'
+ END_OF_SQL
+
+ connection.update(sql)
+ end
+
+ private
+ def recalculate_path
+ if new_record?
+ # dummy path
+ write_attribute(:path, ".")
+ super
+ end
+
+ if parent.nil?
+ write_attribute(:path, ".#{read_attribute(:id)}.")
+ else
+ write_attribute(:path, "#{parent.path}#{read_attribute(:id)}.")
+ end
+ end
+ end
+end
6 app/models/impasse/node_type.rb
@@ -0,0 +1,6 @@
+module Impasse
+ class NodeType < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_node_types"
+ end
+end
66 app/models/impasse/statistics.rb
@@ -0,0 +1,66 @@
+module Impasse
+ class Statistics < ActiveRecord::Base
+ unloadable
+ set_table_name 'impasse_test_plans'
+
+ def self.summary_default(test_plan_id)
+ sql = <<-END_OF_SQL
+ SELECT tp.id, tp.name, count(*) AS total_cases,
+ SUM(CASE WHEN exe.status IS NULL OR exe.status=0 THEN 0 ELSE 1 END) AS total_executions,
+ SUM(CASE WHEN bug.bug_id IS NULL THEN 0 ELSE 1 END) AS total_bugs
+ FROM impasse_test_cases AS tc
+ INNER JOIN impasse_test_plan_cases AS tpc
+ ON tc.id = tpc.test_case_id
+ INNER JOIN impasse_test_plans AS tp
+ ON tp.id = tpc.test_plan_id
+ LEFT OUTER JOIN impasse_executions AS exe
+ ON exe.test_plan_case_id = tpc.id
+ LEFT OUTER JOIN impasse_execution_bugs AS bug
+ ON exe.id = bug.execution_id
+ WHERE tp.id=?
+ END_OF_SQL
+ find_by_sql([sql, test_plan_id]).first
+ end
+ def self.summary_members(test_plan_id)
+ sql = <<-END_OF_SQL
+SELECT users.id, users.login, users.mail, users.firstname, users.lastname, stat.*
+FROM (
+SELECT
+ exe.tester_id,
+ SUM(1) AS assigned,
+ SUM(CASE exe.status WHEN 1 THEN 1 ELSE 0 END) AS ok,
+ SUM(CASE exe.status WHEN 2 THEN 1 ELSE 0 END) AS ng,
+ SUM(CASE exe.status WHEN 3 THEN 1 ELSE 0 END) AS block
+FROM impasse_test_cases AS tc
+INNER JOIN impasse_test_plan_cases AS tpc
+ ON tpc.test_case_id = tc.id
+LEFT OUTER JOIN impasse_executions AS exe
+ ON exe.test_plan_case_id = tpc.id
+WHERE tpc.test_plan_id=?
+GROUP BY tester_id
+) AS stat
+LEFT OUTER JOIN users
+ ON users.id = stat.tester_id
+ END_OF_SQL
+ find_by_sql([sql, test_plan_id])
+ end
+
+ def self.summary_daily(test_plan_id)
+ sql = <<-END_OF_SQL
+SELECT CASE WHEN execution_ts IS NULL OR exe.status=0 THEN NULL ELSE date_format(execution_ts, '%Y%m%d') END AS execution_date,
+ SUM(CASE exe.status WHEN 1 THEN 1 ELSE 0 END) AS ok,
+ SUM(CASE exe.status WHEN 2 THEN 1 ELSE 0 END) AS ng,
+ SUM(CASE exe.status WHEN 3 THEN 1 ELSE 0 END) AS block,
+ SUM(1) AS total
+FROM impasse_test_cases AS tc
+INNER JOIN impasse_test_plan_cases AS tpc
+ ON tpc.test_case_id = tc.id
+LEFT OUTER JOIN impasse_executions AS exe
+ ON exe.test_plan_case_id = tpc.id
+WHERE tpc.test_plan_id=?
+GROUP BY execution_date
+ END_OF_SQL
+ find_by_sql([sql, test_plan_id])
+ end
+ end
+end
9 app/models/impasse/test_case.rb
@@ -0,0 +1,9 @@
+module Impasse
+ class TestCase < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_test_cases"
+
+ has_many :test_steps, :dependent=>:destroy
+ belongs_to :node, :foreign_key=>"id"
+ end
+end
23 app/models/impasse/test_plan.rb
@@ -0,0 +1,23 @@
+module Impasse
+ class TestPlan < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_test_plans"
+
+ has_many :test_plan_cases
+ has_many :test_cases, :through => :test_plan_cases
+
+ def self.find_all_by_version(project)
+ versions = project.shared_versions || []
+ versions = versions.uniq.sort
+ versions.reject! {|version| version.closed? || version.completed? }
+
+ test_plans_by_version = {}
+ versions.each do |version|
+ test_plans = TestPlan.find(:all, :conditions => ["version_id=?", version.id])
+ test_plans_by_version[version] = test_plans
+ end
+
+ [test_plans_by_version, versions]
+ end
+ end
+end
30 app/models/impasse/test_plan_case.rb
@@ -0,0 +1,30 @@
+module Impasse
+ class TestPlanCase < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_test_plan_cases"
+
+ belongs_to :test_plan
+ belongs_to :test_case
+
+ def self.delete_cascade!(test_plan_id, test_case_id)
+ node = Node.find(test_case_id)
+
+ sql = <<-END_OF_SQL
+DELETE FROM impasse_test_plan_cases
+WHERE test_plan_id=#{test_plan_id}
+ AND test_case_id in (
+ SELECT distinct parent.id
+ FROM impasse_nodes AS parent
+ LEFT JOIN impasse_nodes AS child
+ ON INSTR(child.path, parent.path) > 0
+ LEFT JOIN impasse_test_cases AS tc
+ ON child.id = tc.id
+ WHERE parent.path LIKE '#{node.path}%'
+ AND parent.node_type_id=3
+ )
+ END_OF_SQL
+
+ connection.update(sql)
+ end
+ end
+end
8 app/models/impasse/test_step.rb
@@ -0,0 +1,8 @@
+module Impasse
+ class TestStep < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_test_steps"
+
+ belongs_to :test_case
+ end
+end
7 app/models/impasse/test_suite.rb
@@ -0,0 +1,7 @@
+module Impasse
+ class TestSuite < ActiveRecord::Base
+ unloadable
+ set_table_name "impasse_test_suites"
+ belongs_to :node, :foreign_key => :id
+ end
+end
13 app/views/impasse/common/_impasse_tabs.html.erb
@@ -0,0 +1,13 @@
+<% selected_tab = params[:tab] ? params[:tab].to_s : tabs.first[:name] %>
+
+<div class="tabs">
+ <ul>
+ <% tabs.each do |tab| -%>
+ <% tab[:url][:project_id] = @project; tab[:url][:id] = @test_plan.id %>
+ <li><%= link_to l(tab[:label]), tab[:url],
+ :id => "tab-#{tab[:name]}",
+ :class => (tab[:name] != selected_tab ? nil : 'selected') %></li>
+ <% end -%>
+ </ul>
+</div>
+
65 app/views/impasse/common/_impasse_util_js.html.erb
@@ -0,0 +1,65 @@
+function impasse_loading_options() {
+ return {
+ message: '<span><%=l(:label_loading)%></span>',
+ css: {
+ backgroundPosition: "0% 40%",
+ backgroundRepeat: "no-repeat",
+ backgroundImage: "url(<%=image_path('../images/loading.gif')%>)",
+ paddingLeft: "26px",
+ backgroundColor: "#EEE",
+ border: "1px solid #BBB",
+ top: "35%",
+ left: "40%",
+ width: "20%",
+ fontWeight: "bold",
+ textAlign: "center",
+ padding: "0.6em",
+ opacity: 0.9,
+ cursor: "auto"
+ }
+ };
+}
+function ajax_error_handler(xhr, status ,ex) {
+ var message = "<%=l :error_unable_to_connect %>";
+ if(xhr.status == 401) {
+ message = "<%=l :error_can_not_manage_test_cases %>";
+ }
+ show_notification_dialog('error', message);
+}
+
+function show_notification_dialog(type, message) {
+ STYLE = {
+ success: {
+ background: "-webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(240, 255, 200)), to(rgb(180, 255, 180)))",
+ color: "#2f7c00",
+ borderBottom: "1px solid #2f7c00"
+ },
+ error: {
+ background: "-webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 240, 240)), to(rgb(255, 180, 180)))",
+ color: "#a20510",
+ borderBottom: "1px solid #a20510"
+ }
+ };
+ var dialog = jQuery("div#message-dialog");
+ if(dialog.size() == 0) {
+ dialog = jQuery('<div id="message-dialog"></div>').appendTo(jQuery("body"));
+ dialog.dialog({
+ autoOpen: false,
+ position: [0, 0],
+ width: "105%",
+ height: 51,
+ draggable: false,
+ resizable: false,
+ show: "blind",
+ hide: "blind"
+ });
+ }
+ dialog.empty().append(jQuery("<p/>").css({textAlign:'center'}).text(message))
+ dialog.siblings(".ui-dialog-titlebar:first").hide();
+ dialog.parents(".ui-widget-content").css({background:"transparent", border: "none"});
+ dialog.css(jQuery.extend({ padding: 0, font:"bold 14px arial", overflow: 'hidden', textShadow: '0 1px 0 #fff'}, STYLE[type]));
+ dialog.dialog('open');
+ setTimeout(function() {dialog.dialog('close'); }, 2500);
+}
+
+jQuery.jstree._themes = '<%=Engines::RailsExtensions::AssetHelpers.plugin_asset_path 'redmine_impasse', 'stylesheets', 'themes'%>/';
14 app/views/impasse/execution_bugs/_new.html.erb
@@ -0,0 +1,14 @@
+<% labelled_tabular_form_for :issue, @issue, :url => {:controller => 'issues', :action => 'create', :project_id => @project},
+ :html => {:multipart => true, :id => 'issue-form', :class => 'tabular new-issue-form'} do |f| %>
+ <%= error_messages_for 'issue' %>
+ <div class="box">
+ <%= render :partial => 'issues/form', :locals => {:f => f} %>
+ </div>
+ <%= submit_tag l(:button_create) %>
+<% end %>
+
+<div id="preview" class="wiki"></div>
+
+<% content_for :header_tags do %>
+ <%= stylesheet_link_tag 'scm' %>
+<% end %>
27 app/views/impasse/executions/_edit.html.erb
@@ -0,0 +1,27 @@
+<% labelled_tabular_form_for :execution, @execution, {} do |f| %>
+<% test_plan_case = @execution.test_plan_case
+ test_case = test_plan_case.test_case %>
+<%= f.hidden_field :id %>
+<%= hidden_field 'test_plan_case', 'test_plan_id', :value => @execution.test_plan_case.test_plan_id %>
+<%= hidden_field 'test_plan_case', 'test_case_id', :value => @execution.test_plan_case.test_case_id %>
+<div class="box">
+ <p><%=label_tag :name, l(:field_name) %><%=h test_case.node.name %></p>
+ <p><%=label_tag :summary, l(:field_summary) %><%=h test_case.summary%></p>
+ <p><%=label_tag :preconditions, l(:field_preconditions)%><%=h test_case.preconditions%></p>
+ <div style="margin:0; padding: 5px 0 8px 0; padding-left: 180px; height: 1%; clear: left;">
+ <label><%=l :field_execution_status %></label>
+ <div>
+ <%= f.radio_button :status, "0" %><span class="label"><%=l(:label_execution_status_0) %></span>
+ <%= f.radio_button :status, "1"%><span class="label"><%=l(:label_execution_status_1) %></span>
+ <%= f.radio_button :status, "2"%><span class="label"><%=l(:label_execution_status_2) %></span>
+ <%= f.radio_button :status, "3"%><span class="label"><%=l(:label_execution_status_3) %></span>
+ </div>
+ </div>
+ <% if @execution.issues.size > 0 %>
+ <p><%=label_tag :issue, l(:default_tracker_bug) %>
+ <%= @execution.issues.collect{|issue| link_to("##{issue.id}", {:controller=>'/issues', :action=>:show, :id=>issue }, :class => issue.css_classes) }.join(",") %></p>
+ <% end %>
+ <p><%= f.text_area :notes, :label=>:field_notes, :rows=>4 %></p>
+ <p><%= submit_tag l(:button_save) %><p>
+</div>
+<% end %>
160 app/views/impasse/executions/_execute_js.html.erb
@@ -0,0 +1,160 @@
+var $jq = jQuery.noConflict();
+
+ <%= render :partial =>'impasse/common/impasse_util_js' %>
+
+jQuery(document).ready(function ($jq) {
+ var PLAN_CASE_MENU = {
+ contextmenu: {
+ remove: {
+ label: "Delete",
+ action: function(node) { this.remove(node); }
+ }
+ }
+ };
+
+ var EXEC_ICONS = [
+ '<%=image_path("../stylesheets/images/document-attribute-t.png", :plugin=>"redmine_impasse")%>',
+ '<%=image_path("../stylesheets/images/tick.png", :plugin=>"redmine_impasse")%>',
+ '<%=image_path("../stylesheets/images/cross.png", :plugin=>"redmine_impasse")%>',
+ '<%=image_path("../stylesheets/images/wall-brick.png", :plugin=>"redmine_impasse")%>'
+ ];
+
+ var bind_node_event = function(e, data) {
+ $jq("#testplan-tree").unblock();
+ $jq(this).find("li[rel=test_case]").click(function(e) {
+ var $node = $jq(this);
+ $jq("#executions-view").block(impasse_loading_options());
+ $jq.ajax({
+ url: '<%= url_for :controller=>:executions, :action=>:edit, :project_id=>@project%>',
+ data: {
+ "test_plan_case[test_plan_id]": "<%= @test_plan.id %>",
+ "test_plan_case[test_case_id]": $node.attr("id").replace("exec_", "")
+ },
+ success: function(html) {
+ $jq("#executions-view").empty().append($jq(html))
+ $jq("span.label", $jq("#executions-view"))
+ .css({cursor:'pointer'})
+ .click(function(e) {
+ $jq(this).prev().attr("checked", "checked");
+ });
+ },
+ error: ajax_error_handler,
+ complete: function() { $jq("#executions-view").unblock(); }
+ });
+ });
+ };
+
+ var $tree = $jq("#testplan-tree")
+ .bind("loaded.jstree", bind_node_event)
+ .bind("refresh.jstree", bind_node_event)
+ .jstree({
+ "plugins" : [
+ "themes","json_data","ui","crrm","search","types","hotkeys", "contextmenu"
+ ],
+ json_data : {
+ ajax : {
+ url : "<%= url_for :controller=>:executions, :action=>:get_list, :project_id=>@project, :test_plan_id=>@test_plan.id %>",
+ data : function (n) {
+ var params = {
+ prefix: "exec",
+ id : n.attr ? n.attr("id").replace("exec_","") : -1
+ };
+ if ($jq("#filters #myself").is(":checked")) {
+ params["myself"] = true;
+ }
+ return params;
+ }
+ }
+ },
+ types : {
+ max_depth : -2,
+ max_children : -2,
+ valid_children : [ "test_project" ],
+ types : {
+ test_case : {
+ valid_children : "none",
+ icon : {
+ image : "<%=image_path "../stylesheets/images/document-attribute-t.png", :plugin=>'redmine_impasse' %>"
+ }
+ },
+ test_suite : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/documents-stack.png", :plugin=>'redmine_impasse' %>"
+ }
+ },
+ test_project : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/book-brown.png", :plugin=>'redmine_impasse' %>"
+ },
+ start_drag : false,
+ move_node : false,
+ delete_node : false,
+ remove : false
+ }
+ }
+ }
+ });
+
+ $jq("p.buttons a.icon.icon-checked").click(function(e) {
+ $tree.jstree("refresh", -1);
+ $jq("#testplan-tree").block(impasse_loading_options());
+ return false;
+ });
+ $jq("#executions-view form").live("submit", function(e) {
+ var $this = $jq(this);
+ var post_save_function = function() {};
+ var execution_status = $this.find(":radio[name='execution[status]']:checked").val();
+ if(execution_status == "2") { // NG
+ post_save_function = function() {
+ $jq.get('<%=url_for :controller=>:execution_bugs, :action=>:new, :project_id=>@project %>', {},
+ function(data) {
+ $jq("#issue-dialog").empty().append(data).dialog({
+ modal:true,
+ minWidth: 800,
+ title: '<%=l(:label_issue_new)%>'
+ });
+ });
+ };
+ }
+ $jq.ajax({
+ url: '<%=url_for :controller=>:executions, :action=>:put, :project_id=>@project %>',
+ type: 'POST',
+ data: $this.serialize() + "&format=json",
+ success: function(data) {
+ $jq("#executions-view .flash").remove();
+ $jq("#executions-view").prepend(
+ $jq("<div/>").addClass("flash").addClass("notice")
+ .text("<%=l :notice_successful_create %>"));
+ post_save_function();
+ var test_case_id = $jq(":hidden[name='test_plan_case[test_case_id]']" ,$this).val();
+ $jq("#testplan-tree li#exec_"+test_case_id+" a ins").css({backgroundImage: "url("+EXEC_ICONS[execution_status]+")"});
+ },
+ error: function(data) {
+ $jq("#executions-view .flash").remove();
+ $jq("#executions-view").prepend(
+ $jq("<div/>").addClass("flash").addClass("error")
+ .text("Save failure."));
+ }
+ });
+ return false;
+ });
+
+ $jq("#issue-dialog form").live("submit", function(e) {
+ var $this = $jq(this);
+ $jq.ajax({
+ url: '<%=url_for :controller=>:execution_bugs, :action=>:create %>',
+ type: 'POST',
+ data: $this.serialize()
+ + "&execution_bug[execution_id]="+ $jq("#executions-view :hidden#execution_id").val()
+ +"&format=json",
+ success: function(data) {
+ },
+ complete: function() {
+ $jq("#issue-dialog").dialog("close");
+ }
+ });
+ return false;
+ })
+});
51 app/views/impasse/executions/index.html.erb
@@ -0,0 +1,51 @@
+<%= stylesheet_link_tag 'default/style.css', :plugin => 'redmine_impasse' %>
+<%= stylesheet_link_tag 'jquery-ui', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery-ui', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.hotkeys.js', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.jstree.js', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.blockUI.js', :plugin => 'redmine_impasse' %>
+<%= include_calendar_headers_tags %>
+
+<%= breadcrumb
+ [link_to(l(:label_impasse), {:controller => :test_case, :action => :index, :project_id => @project}),
+ h(" > "),
+ link_to(l(:label_test_plans_list), {:controller => :test_plans, :action => :index, :project_id => @project})
+ ]%>
+<h2><%=h @test_plan.name %></h2>
+
+<%= render_impasse_tabs %>
+
+<script>
+ <%= render :partial => 'execute_js' %>
+</script>
+
+<div class="tab-content">
+ <div class="splitcontentleft">
+ <fieldset id="filters" class="collapsible collapsed">
+ <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
+ <div style="display:none;">
+ <table>
+ <tr class="filter">
+ <td style="width:200px;">
+ <%= check_box_tag 'myself', true, :id=>'cb_myself' %>
+ <label for="cb_myself"><%=l(:label_assined_to_myself)%></label>
+ </td>
+ <td style="width:150px;">
+ </td>
+ </tr>
+ </table>
+ </div>
+ </fieldset>
+ <p class="buttons hide-when-print">
+ <%=link_to l(:button_apply), {:controller=>:executions, :action=>:index, :project_id=>@project, :test_plan_id=>@test_plan}, :class => 'icon icon-checked'%>
+ </p>
+ <div id="testplan-tree"></div>
+ </div>
+ <div class="splitcontentright">
+ <div id="executions-view">
+ </div>
+ </div>
+</div>
+
+<div id="issue-dialog"></div>
1 app/views/impasse/executions/put.html.erb
@@ -0,0 +1 @@
+<h2>Executions#put</h2>
5 app/views/impasse/test_case/_edit.html.erb
@@ -0,0 +1,5 @@
+<% labelled_tabular_form_for :test_case, @test_case, {} do |f| %>
+ <%= render :partial => "impasse/test_case/form.#{params[:node_type]}", :locals => {:form => f} %>
+ <button type="button" class="ui-button-submit"><%=l(:button_update)%></button>
+ <button type="button" class="ui-button-cancel"><%=l(:button_cancel)%></button>
+<% end %>
42 app/views/impasse/test_case/_form.test_case.html.erb
@@ -0,0 +1,42 @@
+<%= error_messages_for 'test_case' %>
+<div class="box">
+ <p><label><span class="required">*</span><%=l :field_name %></label><%= text_field_tag 'node[name]', @node.name, :size => 50, :required=>true %></p>
+ <p><%= form.text_area :summary,
+ :class => 'wiki-edit', :rows => 5 %></p>
+ <p><%= form.text_area :preconditions,
+ :class => 'wiki-edit', :rows => 5 %></p>
+ <div style="margin:0; padding: 5px 0 8px 0; padding-left: 180px; height: 1%; clear: left;">
+ <label>TestStep</label>
+ <div>
+ <div class="contextual">
+ <a href="#" class="icon icon-add add-test-step">add</a>
+ </div>
+ <div style="clear: both;"/>
+ <table class="list">
+ <thead>
+ <tr class="entry">
+ <th>#</th>
+ <th>Operation</th>
+ <th>Expectation</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody class="sortable">
+ <% @test_case.test_steps.each_with_index do |test_step, i| %>
+ <tr>
+ <td class="ui-sort-handle"><%=h test_step.step_number %></td>
+ <td><%=text_area_tag "test_steps[#{i+1}][actions]", test_step.actions, :rows=>3, :style=>'width:100%' %>
+ <%= hidden_field_tag "test_steps[#{i+1}][step_number]", test_step.step_number %>
+ <%= hidden_field_tag "test_steps[#{i+1}][id]", test_step.id %>
+ </td>
+ <td><%=text_area_tag "test_steps[#{i+1}][expected_results]", test_step.expected_results, :rows=>3, :style=>'width:100%' %></td>
+ <td><span class='icon icon-del'/></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ <p><%= form.check_box :active,
+ :class => 'wiki-edit' %></p>
+</div>
7 app/views/impasse/test_case/_form.test_suite.html.erb
@@ -0,0 +1,7 @@
+<%= error_messages_for 'test_case' %>
+<div class="box">
+ <p><label><span class="required">*</span><%=l :field_name %></label><%= text_field_tag 'node[name]', @node.name, :size => 50, :required=>true %></p>
+ <p><%= form.text_area :details, :rows => 10,
+ :class => 'wiki-edit' %></p>
+ <%= wikitoolbar_for 'test_case_details' %>
+</div>
5 app/views/impasse/test_case/_new.html.erb
@@ -0,0 +1,5 @@
+<% labelled_tabular_form_for :test_case, @test_case, {} do |f| %>
+ <%= render :partial => "impasse/test_case/form.#{params[:node_type]}", :locals => {:form => f} %>
+ <button type="button" class="ui-button-submit"><%=l(:button_create)%></button>
+ <button type="button" class="ui-button-cancel"><%=l(:button_cancel)%></button>
+<% end %>
287 app/views/impasse/test_case/_treejs.html.erb
@@ -0,0 +1,287 @@
+var jq$ = $.noConflict();
+
+ <%= render :partial =>'impasse/common/impasse_util_js' %>
+
+jQuery(document).ready(function ($) {
+ var AJAX_URL = {
+ "new":"<%=url_for :controller=>:test_case, :action=>:new, :project_id=>@project%>",
+ "edit":"<%=url_for :controller=>:test_case, :action=>:edit, :project_id=>@project%>"
+ };
+ var LEAF_MENU = {
+ contextmenu: {
+ edit: {
+ label: "Edit",
+ action: function(node) { openDialog({
+ rslt: {
+ name: $("#testcase-tree").jstree("get_text", node),
+ position: $("#testcase-tree").jstree("get_index", node),
+ obj: node,
+ parent: $(node).parents("li:first")
+ }
+ }, 'edit'); }
+ },
+ remove: {
+ label: "Delete",
+ action: function(node) { this.remove(node); }
+ }
+ }
+ };
+ var FOLDER_MENU = {
+ contextmenu: {
+ create: {
+ label: "Create",
+ submenu: {
+ createTestSuite: {
+ label: "Test suite",
+ action: function(node) {
+ this.create(node, "last", {attr: {rel: "test_suite"}}, null, true);
+ }
+ },
+ createTestCase: {
+ label: "Test case",
+ action: function(node) {
+ this.create(node, "last", {attr: {rel: "test_case"}}, null, true);
+ }
+ }
+ }
+ },
+ edit: LEAF_MENU.contextmenu.edit,
+ remove: LEAF_MENU.contextmenu.remove
+ }
+ };
+
+ var dialog = {
+ test_suite: $("#testsuite-dialog").dialog({
+ autoOpen: false,
+ modal:true,
+ minWidth: 700,
+ title: "<%=l :label_test_suite_edit %>"
+ }),
+ test_case: $("#testcase-dialog").dialog({
+ autoOpen: false,
+ modal:true,
+ minWidth: 700,
+ title: "<%=l :label_test_case_edit %>"
+ })
+ };
+
+ var openDialog = function(data, edit_type) {
+ var node = $(data.rslt.obj);
+ var node_type = node.attr("rel");
+ var request = { node_type: node_type };
+ if (node.attr("id")) {
+ request['node[id]'] = node.attr("id").replace("node_", "");
+ }
+
+ $.ajax({
+ url: AJAX_URL[edit_type],
+ data: request,
+ success: function(html) {
+ dialog[node_type].empty().append(html);
+ dialog[node_type].find(".ui-button-cancel").click(function(e) {
+ dialog[node_type].dialog('close');
+ });
+ dialog[node_type].dialog('open');
+ dialog[node_type].find(".sortable").sortable({
+ handle: ".ui-sort-handle",
+ placeholder: 'ui-state-highlight',
+ update: function(e, ui) {
+ var i=1;
+ $(this).find("tr").each(function() {
+ var row = $(this);
+ $("td.ui-sort-handle", row).text(i);
+ $("input[name*=step_number]", row).each(function() {
+ $(this).val(i);
+ });
+ $("textarea,:hidden", row).each(function() {
+ var f = $(this);
+ f.attr("name", f.attr("name").replace(/\[\d+\]/, "["+i+"]"));
+ });
+ i++;
+ });
+ }
+ });
+ $(".sortable .icon-del", dialog[node_type]).click(function(e) {
+ $(this).parents("tr:last").remove();
+ });
+
+ dialog[node_type].find(":button.ui-button-submit").click(function(e) {
+ var tc = {format:"json"};
+ dialog[node_type].find(":hidden,:text,textarea,:checkbox:checked,radiobutton:checked,select").each(function() {
+ tc[$(this).attr("name")] = $(this).val();
+
+ });
+ if(edit_type == 'edit')
+ tc["node[id]"] = node.attr("id").replace("node_","");
+ tc["node_type"] = node_type;
+ tc["node[parent_id]"] = $(data.rslt.parent).attr("id").replace("node_", "");
+ tc["node[node_order]"] = data.rslt.position;
+ $.ajax({
+ type: 'POST',
+ url:AJAX_URL[edit_type],
+ data: tc,
+ success: function (r) {
+ if(r.id) {
+ dialog[node_type].unbind("dialogbeforeclose");
+ node.attr("id", "node_" + r.id);
+ node.data("jstree", (node_type=='test_case')?LEAF_MENU:FOLDER_MENU);
+ $.jstree._reference(node).set_text(node, tc["node[name]"]);
+ show_notification_dialog('success', (edit_type=='edit')?"<%=l(:notice_successful_update)%>":"<%=l(:notice_successful_create)%>");
+ }
+ },
+ error: ajax_error_handler,
+ complete: function() { dialog[node_type].dialog('close'); }
+ });
+ });
+ },
+ error: ajax_error_handler
+ });
+ };
+
+ var testcaseTree =$("#testcase-tree")
+ .jstree({
+ "plugins": [
+ "themes","json_data","ui","crrm","cookies","dnd","search","types","hotkeys","contextmenu"
+ ],
+ core: {
+ animation: 0
+ },
+ json_data: {
+ ajax: {
+ url: "<%= url_for :controller=>:test_case, :action=>:list, :project_id=>@project %>",
+ data: function (n) {
+ return {
+ prefix: "node",
+ node_id : n.attr ? n.attr("id").replace("node_","") : -1
+ };
+ }
+ }
+ },
+ types : {
+ max_depth : -2,
+ max_children : -2,
+ valid_children : [ "test_project" ],
+ types : {
+ test_case : {
+ valid_children : "none",
+ icon : {
+ image : "<%=image_path "../stylesheets/images/document-attribute-t.png", :plugin=>'redmine_impasse' %>"
+ }
+ },
+ test_suite : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/documents-stack.png", :plugin=>'redmine_impasse' %>"
+ }
+ },
+ test_project : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/book-brown.png", :plugin=>'redmine_impasse' %>"
+ },
+ start_drag : false,
+ move_node : false,
+ delete_node : false,
+ remove : false
+ }
+ }
+ }
+ })
+ .bind("loaded.jstree", function (e, data) {
+ $(this).find("li[rel=test_project],li[rel=test_suite]").data("jstree", FOLDER_MENU);
+ $(this).find("li[rel=test_case]").data("jstree", LEAF_MENU);
+ })
+ .bind("refresh.jstree", function (e, data) {
+ $(this).find("li[rel=test_project],li[rel=test_suite]").data("jstree", FOLDER_MENU);
+ $(this).find("li[rel=test_case]").data("jstree", LEAF_MENU);
+ })
+ .bind("create.jstree", function (e, data) {
+ dialog[$(data.rslt.obj).attr("rel")].bind('dialogbeforeclose', function(e) {
+ $.jstree.rollback(data.rlbk);
+ });
+ openDialog(data, 'new');
+ })
+ .bind("remove.jstree", function (e, data) {
+ data.rslt.obj.each(function () {
+ $.ajax({
+ async : false,
+ type: 'POST',
+ url: "<%= url_for :controller=>:test_case, :action=>:destroy, :project_id=>@project %>",
+ data : {
+ format: "json",
+ "node[id]": this.id.replace("node_","")
+ },
+ success : function (r) {
+ if(!r.status) {
+ data.inst.refresh();
+ }
+ },
+ error: ajax_error_handler
+ });
+ });
+ })
+ .bind("move_node.jstree", function (e, data) {
+ data.rslt.o.each(function (i) {
+ $.ajax({
+ async : false,
+ type: 'POST',
+ url: "<%= url_for :controller=>:test_case, :action=>:edit, :project_id=>@project %>",
+ data : {
+ format: "json",
+ node_type: $(this).attr("rel"),
+ "node[id]" : $(this).attr("id").replace("node_",""),
+ "node[parent_id]" : data.rslt.cr === -1 ? 1 : data.rslt.np.attr("id").replace("node_",""),
+ "node[node_order]" : data.rslt.cp,
+ "node[name]" : data.rslt.name,
+ "copy" : data.rslt.cy ? 1 : 0
+ },
+ success : function (r) {
+ if(!r.id) {
+ $.jstree.rollback(data.rlbk);
+ }
+ else {
+ $(data.rslt.oc).attr("id", "node_" + r.id);
+ if(data.rslt.cy && $(data.rslt.oc).children("UL").length) {
+ data.inst.refresh(data.inst._get_parent(data.rslt.oc));
+ }
+ }
+ },
+ error: function(xhr, status, ex) {
+ $.jstree.rollback(data.rlbk);
+ ajax_error_handler(xhr, status, ex);
+ }
+ });
+ });
+ });
+
+ $("#testcase-dialog .add-test-step").live("click", function() {
+ var id = 0;
+ var test_steps = $("#testcase-dialog table.list");
+ test_steps.find("td.ui-sort-handle").each(function() {
+ if (id < Number($(this).text()))
+ id = Number($(this).text());
+ });
+ id += 1;
+
+ var actions = $("<textarea/>").attr("name", "test_steps["+id+"][actions]")
+ .attr("rows", 3).css({width:"100%", padding:0, margin:0});
+ var expected_results = $("<textarea/>").attr("name", "test_steps["+id+"][expected_results]")
+ .attr("rows", 3).css({width:"100%", padding:0, margin:0});
+ var step_number = $("<input/>").attr("type", "hidden")
+ .attr("name", "test_steps["+id+"][step_number]")
+ .attr("value", id);
+
+ var del_button = $("<td/>");
+ var test_step = $("<tr/>").addClass("entry")
+ .append($("<td/>").addClass("ui-sort-handle").text(id))
+ .append($("<td/>").append(actions).append(step_number))
+ .append($("<td/>").append(expected_results))
+ .append(del_button);
+ del_button.append($("<span class='icon icon-del'/>").click(function(e) {
+ test_step.remove();
+ }));
+ test_steps.append(test_step);
+
+ return false;
+ });
+});
33 app/views/impasse/test_case/index.html.erb
@@ -0,0 +1,33 @@
+<%= stylesheet_link_tag 'jquery-ui', :plugin => 'redmine_impasse' %>
+<style>
+.ui-state-highlight { height: 1.5em; line-height: 1.2em; }
+</style>
+<%= javascript_include_tag 'jquery', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery-ui', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.cookie.js', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.hotkeys.js', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.jstree.js', :plugin => 'redmine_impasse' %>
+<h2><%= l(:label_impasse)%></h2>
+<script>
+<%= render :partial => 'treejs' %>
+</script>
+<div id="testcase-tree">
+</div>
+
+<% content_for :sidebar do %>
+ <h3><%=l(:label_test_plan_plural)%></h3>
+ <div class="contextual">
+ <%= link_to l(:label_test_plan_new), {:controller=>:test_plans, :action=>:new, :project_id=>@project}, :class=>"icon icon-add" %>
+ </div>
+ <% plans, versions = Impasse::TestPlan.find_all_by_version(@project) %>
+ <% versions.each do |version| %>
+ <h4><%=h version.name%></h4>
+ <% plans[version].each do |test_plan| %>
+ <%= link_to test_plan.name, :controller=>:test_plans, :action=>:show, :project_id=>@project, :id=>test_plan%><br/>
+ <% end %>
+ <% end %>
+<% end %>
+
+<div id="testsuite-dialog" style="display:none"></div>
+
+<div id="testcase-dialog" style="display:none"></div>
1 app/views/impasse/test_case/show.html.erb
@@ -0,0 +1 @@
+<h2>Test</h2>
9 app/views/impasse/test_plans/_form.html.erb
@@ -0,0 +1,9 @@
+<%= error_messages_for 'test_case' %>
+
+<div class="box">
+ <p><%= form.text_field :name, :required => true %></p>
+ <p><%= form.text_area :notes, :rows => 10,
+ :class => 'wiki-edit' %></p>
+ <%= wikitoolbar_for 'test_plan_notes' %>
+ <p><%= form.select :version_id, @versions.collect{|v| [v.name, v.id]}, {:include_blank => false}, :required=>true%></p>
+</div>
187 app/views/impasse/test_plans/_planjs.html.erb
@@ -0,0 +1,187 @@
+$.noConflict();
+
+ <%= render :partial =>'impasse/common/impasse_util_js' %>
+
+jQuery(document).ready(function ($) {
+ var PLAN_CASE_MENU = {
+ contextmenu: {
+ remove: {
+ label: "Delete",
+ action: function(node) { this.remove(node); }
+ }
+ }
+ };
+
+ $("#testcase-tree")
+ .bind("before.jstree", function (e, data) {
+ })
+ .bind("loaded.jstree", function (e, data) {
+ })
+ .jstree({
+ "plugins" : [
+ "themes", "json_data","ui","crrm","cookies","dnd","search","types","hotkeys"
+ ],
+ json_data : {
+ ajax : {
+ url : "<%= url_for :controller=>:test_case, :action=>:list, :project_id=>@project %>",
+ data : function (n) {
+ return {
+ prefix: "tc",
+ node_id : n.attr ? n.attr("id").replace("tc_","") : -1
+ };
+ }
+ }
+ },
+ types : {
+ max_depth : -2,
+ max_children : -2,
+ valid_children : [ "test_project" ],
+ types : {
+ test_case : {
+ valid_children : "none",
+ icon : {
+ image : "<%=image_path "../stylesheets/images/document-attribute-t.png", :plugin=>'redmine_impasse' %>"
+ },
+ move_node : false,
+ delete_node : false,
+ remove : false
+ },
+ test_suite : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/documents-stack.png", :plugin=>'redmine_impasse' %>"
+ },
+ move_node : false,
+ delete_node : false,
+ remove : false
+ },
+ test_project : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/book-brown.png", :plugin=>'redmine_impasse' %>"
+ },
+ start_drag : false,
+ move_node : false,
+ delete_node : false,
+ remove : false
+ }
+ }
+ },
+ dnd: {
+ drop_target: ".jstree-drop",
+ drag_target: false,
+ drop_finish: function(data) {
+ var $this = this;
+ $.post('<%= url_for :controller=>:test_plans, :action=>:add_test_case, :project_id=>@project %>',
+ { test_case_ids: $.map(data.o, function(n, i) { return $this._get_node(n).attr("id").replace("tc_", "") }),
+ test_plan_id: <%=@test_plan.id%>,
+ format: "json"
+ },
+ function(result) {
+ if(result.num) {
+ $("#testplan-tree").jstree("refresh", -1);
+ }
+ });
+
+ }
+ }
+ });
+
+ $("#testplan-tree")
+ .bind("before.jstree", function (e, data) {
+ })
+ .bind("loaded.jstree", function (e, data) {
+ $(this).find("li[rel=test_case]").data("jstree", PLAN_CASE_MENU);
+ $(this).find("li[rel!=test_case]").data("jstree", {contextmenu:{}});
+ })
+ .bind("contextmenu.jstree", function(e,data) {
+ })
+ .bind("remove.jstree", function (e, data) {
+ data.rslt.obj.each(function () {
+ $.ajax({
+ async : false,
+ type: 'POST',
+ url: "<%= url_for :controller=>:test_plans, :action=>:remove_test_case, :project_id=>@project %>",
+ data : {
+ format: "json",
+ test_plan_id: "<%= @test_plan.id %>",
+ test_case_id: this.id.replace("plan_","")
+ },
+ success : function (r) {
+ show_notification_dialog('success', '<%=l(:notice_successful_delete)%>');
+ },
+ error: function(xhr, status, ex) {
+ ajax_error_handler(xhr, status, ex);
+ $.jstree.rollback(data.rlbk);
+ }
+ });
+ });
+ })
+ .jstree({
+ "plugins" : [
+ "themes", "json_data","ui","crrm","cookies","dnd","search","types","hotkeys", "contextmenu"
+ ],
+ json_data : {
+ ajax : {
+ url : "<%= url_for :controller=>:test_case, :action=>:list, :project_id=>@project, :test_plan_id=>@test_plan.id %>",
+ data : function (n) {
+ return {
+ prefix: "plan",
+ node_id : n.attr ? n.attr("id").replace("plan_","") : -1
+ };
+ }
+ }
+ },
+ types : {
+ max_depth : -2,
+ max_children : -2,
+ valid_children : [ "test_project" ],
+ types : {
+ test_case : {
+ valid_children : "none",
+ icon : {
+ image : "<%=image_path "../stylesheets/images/document-attribute-t.png", :plugin=>'redmine_impasse' %>"
+ }
+ },
+ test_suite : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/documents-stack.png", :plugin=>'redmine_impasse' %>"
+ }
+ },
+ test_project : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/book-brown.png", :plugin=>'redmine_impasse' %>"
+ },
+ start_drag : false,
+ move_node : false,
+ delete_node : false,
+ remove : false
+ }
+ }
+ },
+ crrm: {
+ move: {
+ check_move : function (m) {
+ var p = this._get_parent(m.o);
+ if(!p) return false;
+ p = p == -1 ? this.get_container() : p;
+ if(p === m.np) return true;
+ if(p[0] && m.np[0] && p[0] === m.np[0]) return true;
+ return false;
+ return true;
+ }
+ }
+ },
+ dnd: {
+ drag_target: false,
+ drag_finish: function(data) {
+ return true;
+ }
+ },
+ contextmenu: {
+ select_node: true
+ }
+ });
+});
5 app/views/impasse/test_plans/_statistics_js.html.erb
@@ -0,0 +1,5 @@
+var $jq = jQuery.noConflict();
+
+jQuery(document).ready(function ($jq) {
+
+});
140 app/views/impasse/test_plans/_user_assign_js.html.erb
@@ -0,0 +1,140 @@
+jQuery.noConflict();
+
+ <%= render :partial =>'impasse/common/impasse_util_js' %>
+
+jQuery(document).ready(function ($) {
+ var PLAN_CASE_MENU = {
+ contextmenu: {
+ remove: {
+ label: "Delete",
+ action: function(node) { this.remove(node); }
+ }
+ }
+ };
+
+ $("#testplan-tree")
+ .bind("before.jstree", function (e, data) {
+ })
+ .bind("loaded.jstree", function (e, data) {
+ $(this).find("li[rel=test_case]").data("jstree", PLAN_CASE_MENU);
+ $(this).find("li[rel!=test_case]").data("jstree", {contextmenu:{}});
+ })
+ .bind("remove.jstree", function (e, data) {
+ data.rslt.obj.each(function () {
+ $.ajax({
+ async : false,
+ type: 'POST',
+ url: "<%= url_for :controller=>:test_plans, :action=>:remove_test_case, :id=>@project %>",
+ data : {
+ format: "json",
+ test_plan_id: "<%= @test_plan.id %>",
+ test_case_id: this.id.replace("plan_","")
+ },
+ success : function (r) {
+ if(!r.status) {
+ data.inst.refresh();
+ }
+ },
+ error: function(xhr, status, ex) {
+ ajax_error_handle(xhr, status, ex);
+ $.jstree.rollback(data.rlbk);
+ }
+ });
+ });
+ })
+ .jstree({
+ "plugins" : [
+ "themes","json_data","ui","crrm","cookies","dnd","search","types","hotkeys", "contextmenu"
+ ],
+ core : {
+ animation: 0
+ },
+ json_data : {
+ ajax : {
+ url : "<%= url_for :controller=>:executions, :action=>:get_list, :project_id=>@project, :test_plan_id=>@test_plan.id %>",
+ data : function (n) {
+ return {
+ prefix: "plan",
+ id : n.attr ? n.attr("id").replace("plan_","") : -1
+ };
+ },
+ progressive_render: true
+ }
+ },
+ types : {
+ max_depth : -2,
+ max_children : -2,
+ valid_children : [ "test_project" ],
+ types : {
+ test_case : {
+ valid_children : "none",
+ icon : {
+ image : "<%=image_path "../stylesheets/images/document-attribute-t.png", :plugin=>'redmine_impasse' %>"
+ }
+ },
+ test_suite : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/documents-stack.png", :plugin=>'redmine_impasse' %>"
+ }
+ },
+ test_project : {
+ valid_children : [ "test_suite", "test_case" ],
+ icon : {
+ image : "<%=image_path "../stylesheets/images/book-brown.png", :plugin=>'redmine_impasse' %>"
+ },
+ start_drag : false,
+ move_node : false,
+ delete_node : false,
+ remove : false
+ }
+ }
+ },
+ crrm: {
+ move: {
+ check_move : function (m) {
+ var p = this._get_parent(m.o);
+ if(!p) return false;
+ p = p == -1 ? this.get_container() : p;
+ if(p === m.np) return true;
+ if(p[0] && m.np[0] && p[0] === m.np[0]) return true;
+ return false;
+ return true;
+ }
+ }
+ },
+ dnd: {
+ drag_finish: function(data) {
+ var $this = this;
+ var draggable = $(data.o).hasClass("jstree-draggable") ? $(data.o) : $(data.o).parents(".jstree-draggable");
+ var request = {
+ format: "json",
+ "test_plan_case[test_plan_id]": '<%= @test_plan.id %>',
+ "test_plan_case[test_case_id]": data.r.attr("id").replace("plan_", "")
+ };
+ if (draggable.hasClass("test-day")) {
+ var date = $("#calendar-view").datepicker("getDate");
+ date.setDate($(data.o).text());
+ request["execution[expected_date]"] = date.toUTCString();
+ } else if (draggable.hasClass("test-member")) {
+ request["execution[tester_id]"] = data.o.id.replace("principal-", "");
+ }
+
+ $.ajax({
+ type: 'POST',
+ url: '<%= url_for :controller=>:executions, :action=>:put, :project_id=>@project %>',
+ data: request,
+ success: function(r) {
+ $this.refresh($this._get_parent(data.r));
+ },
+ error: function(xhr, status, ex) {
+ ajax_error_handle(xhr, status, ex);
+ }
+ });
+ }
+ }
+ });
+ var cal = $("#calendar-view").datepicker();
+ $(".ui-datepicker-calendar td:not(.ui-datepicker-other-month)", cal)
+ .addClass("jstree-draggable").addClass("test-day");
+});
12 app/views/impasse/test_plans/edit.html.erb
@@ -0,0 +1,12 @@
+<%= breadcrumb
+ [link_to(l(:label_impasse), {:controller => :test_case, :action => :index, :project_id => @project}),
+ h(" > "),
+ link_to(l(:label_test_plans_list), {:controller => :test_plans, :action => :index, :project_id => @project})
+]%>
+<h2><%=h @test_plan.name %></h2>
+
+<%= render_impasse_tabs %>
+<% labelled_tabular_form_for :test_plan, @test_plan, {} do |f| %>
+ <%= render :partial => "form", :locals => {:form => f} %>
+ <%= submit_tag "Update" %>
+<% end %>
1 app/views/impasse/test_plans/execution.html.erb
@@ -0,0 +1 @@
+
41 app/views/impasse/test_plans/index.html.erb
@@ -0,0 +1,41 @@
+<%= stylesheet_link_tag 'jquery-ui', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery-ui', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.cookie.js', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.hotkeys.js', :plugin => 'redmine_impasse' %>
+<%= javascript_include_tag 'jquery.jstree.js', :plugin => 'redmine_impasse' %>
+
+<%= breadcrumb
+ [link_to(l(:label_impasse), {:controller => :test_case, :action => :index, :project_id => @project})]%>
+
+<div class="contextual">
+<%= link_to(l(:label_test_plan_new),
+ {:controller=>:test_plans, :action=>:new,:project_id=>@project},
+ :class => 'icon icon-add')%>
+</div>
+
+<h2><%=l(:label_test_plan_plural)%></h2>
+
+<% if @versions.empty? %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% else %>
+<div id="testplans">
+<% @versions.each do |version| %>
+ <h3 class="version"><%= tag 'a', :name => version.name %><%= link_to_version version %></h3>
+ <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
+
+ <% if (test_plans = @test_plans_by_version[version]) && test_plans.size > 0 %>
+ <% form_tag({}) do -%>
+ <table class="list">
+ <%- test_plans.each do |test_plan| -%>
+ <tr>
+ <td><%= link_to test_plan.name, :action => :show, :project_id => @project, :id => test_plan.id %></td>
+ </tr>
+ <%- end -%>
+ </table>
+ <% end %>
+ <% end %>
+<% end %>
+</div>
+<% end %>
+</div>
4 app/views/impasse/test_plans/new.html.erb
@@ -0,0 +1,4 @@
+<% labelled_tabular_form_for :test_plan, @test_plan, {} do |f| %>
+ <%= render :partial => "form", :locals => {:form => f} %>
+ <%= submit_tag "Create" %>
+<% end %>
31 app/views/impasse/test_plans/show.html.erb
@@ -0,0 +1,31 @@