CSC/ECE 517 Fall 2020 - E2063. Refactor tree-display.js and tree display controller.rb: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
 
(28 intermediate revisions by the same user not shown)
Line 19: Line 19:
===Accomplishments===
===Accomplishments===
The following tasks were accomplished in this project:
The following tasks were accomplished in this project:
* Removed methods that were not used
* Removed methods that were not used
* Implemented previous changes from past groups on this assignment if useful
* Modified tree display controller to better parse data prior to being accessed by the react client
* Modified tree display controller to better parse data prior to being accessed by the react client  
* Fixed beta branch issue with showing dropdowns for courses and questionnaires.
 
* Fixed beta branch issue with showing edit option for courses and assignments.
 
 


===About Tree Display Controller===
===About Tree Display Controller===
Line 35: Line 31:


=====Functionality=====
=====Functionality=====
* Instructors can view the course, assignment and questionnaire tabs.
* Instructors can view the course, assignment and questionnaire tabs. Instructors can select rows for questionnaire's and courses to view a dropdown of more information. For course rows, they have options to edit, delete, copy, add TA, create an assignment, create teams, view grades, assign surveys, and view reviews. For assignments, they can edit, delete, copy, assign to course, create teams, assign reviewers, view submissions, view scores, view reports, and view survey responses.  
::
::


=====Drawbacks and Solutions=====
* '''Problem 1''': The dropdown doesn't work for assignments and questionaires. This is due to the handleExpandClick function, as it appears that the get_sub_folder_contents method is not capable of returning the data.
<pre>
if(this.props.dataType!='assignment') {
            _this = this;
            jQuery.get('/tree_display/get_sub_folder_contents',
                {
                    reactParams2: newParams
                },
                function (data) {
                    _this.props.data[id.split("_")[2]]['children'] = data;
                    _this.forceUpdate();
                }, 'json');
        }
</pre>
* '''Solution''': Change the the JQuery route to a post because you can't give parameters to a get route and also change this in routes.rb file.


=====Drawbacks and Solutions=====
* '''Problem 1''': Checks made by the controller are too complicated.
::The method paginate_list was building a complex search criteria based on the input params, getting the list of versions from the Database matching this search criteria and then calling the Page API. All these tasks in a single method made it difficult to understand.
<pre>
<pre>
showElement={_this.state.expandedRow.indexOf(entry.type+'_'(parseInt(entry.nodeinfo.node_object_id)*2).toString()+'_'+i) > -1 ? "" : "none"}
jQuery.post('/tree_display/get_sub_folder_contents',
                {
                    reactParams2: newParams
                },
</pre>
</pre>
* '''Solution''': The implementation has been changed in such a way that the versions which a user is allowed to see depends on the privileges of the user. The approach we have taken is as follows:
**An administrator can see all the versions
**An instructor can see all the versions created by him and other users who are in his course or are participants in the assignments he creates.
**A TA can see all the versions created by him and other users who are in the course for which he/ she assists.
**A Student can see all the versions created by him/ her.
* '''Problem 2''': The search criteria created in the method paginate_list was difficult to comprehend.
::The code which builds the search criteria in the method paginate_list uses many string literals and conditions and is hardly intuitive. The programmer will have to spend some time to understand what the code is really doing.
* '''Solution''': The implementation has been changed. A student is not allowed to delete any versions now. Other types of users, for instance administrators, instructors and TAs are allowed to delete only the versions they are authorized to view.
* '''Problem 3''': The paginate method can be moved to a helper class.
::VersionsController is not the only component which require to paginate items. There are other components too. For instance, the UsersController has to paginate the list of users. Hence the Paginate method can be moved to a helper class which can be accessed by other components as well.
* '''Solution''': The filtering options has also been enhanced. The current user can now choose as part of the version search filter any user from a list of users if the current user is authorized to see the versions created by that user.


===New Implementation===
<pre>               
*The method paginate_list has been split into 2 methods now.
post :get_sub_folder_contents
** BuildSearchCriteria – as the name suggests the sole purpose of this method is to build a search criteria based on the input search filters when the current user initiates a search in versions.
</pre>
** paginate_list – this method will call the paginate API.
 
:First the search criteria is built, then the criteria is applied to versions in the database to get all versions which matches the criteria and then the retrieved versions are paginated.
* '''Problem 2''': Tree display controller needs to better parse the data prior to being access to the react client because currently, there are too many comparisons when checking for the type of tab.  
 
Example:
<pre>
<pre>
  # pagination.
var SimpleTableRow = React.createClass({
  def paginate_list(versions)
    render: function () {
    paginate(versions, VERSIONS_PER_PAGE);
      var creation_date;
  end
      var updated_date;
      var colWidthArray = ["30%", "0%", "0%", "0%", "25%", "25%", "20%"]
      var colDisplayStyle = {
        "display": ""
      }
      if (this.props.dataType === 'questionnaire') {
        colWidthArray = ["30%", "0%", "0%", "0%", "20%", "20%", "30%"]
        colDisplayStyle = {
          "display": "none"
        }
      } else if(this.props.dataType === 'course') {
        colWidthArray = ["20%", "0%", "0%", "20%", "20%", "20%", "20%"]
      }
    ......


  def BuildSearchCriteria(id, user_id, item_type, event)
}
    # Set up the search criteria
</pre>
    search_criteria = ''
 
    search_criteria = search_criteria + add_id_filter_if_valid(id).to_s
* '''Solution''': Centralize the attributes of each tab so that the tree_display doesn't need to keep parsing between the tabs.
    if current_user_role? == 'Super-Administrator'
 
      search_criteria = search_criteria + add_user_filter_for_super_admin(user_id).to_s
* '''Problem 3''': The Assignments and Courses tab isn't showing the edit option for each row. It seems that what's implemented is trying to verify that there was an active user and the active user is null.
    end
 
    search_criteria = search_criteria + add_user_filter
<pre>
    search_criteria = search_criteria + add_version_type_filter(item_type).to_s
if (app_variables.currentUserId == null || this.props.instructor_id == app_variables.currentUserId) {
    search_criteria = search_criteria + add_event_filter(event).to_s
              moreContent.push(
    search_criteria = search_criteria + add_date_time_filter
                  <span>
    search_criteria
                <a title="Edit" href={"/" + newNodeType + "/" + (parseInt(this.props.id) / 2).toString() + "/edit"}><img
  end
                    src="/assets/tree_view/edit-icon-24.png"/></a>
              </span>
              );
          }
</pre>
</pre>
* The string literals and conditions in the method paginate_list were replaced with methods with intuitive names so that the programmer can understand the code more easily. We also removed an empty if clause and a redundant statement.
 
* '''Solution''':  Remove the active user implementation and replace with a check to make sure the current tab isn't Questionnaire.
<pre>
<pre>
  def add_id_filter_if_valid (id)
if(newNodeType != 'questionnaires') { // should only be viewed by either assignments or courses
    "id = #{id} AND " if id && id.to_i > 0
              moreContent.push(
  end
                  <span>
                <a title="Edit" href={"/" + newNodeType + "/" + (parseInt(this.props.id) / 2).toString() + "/edit"}><img
                    src="/assets/tree_view/edit-icon-24.png"/></a>
              </span>
              );
          }
</pre>
 
 
* '''Drawback''':The assignments tab did not have any data when we used the reset debugging extension (i.e. the childNodes attribute was null). Thus we assumed there was no data to display underneath each assignment.
 


  def add_user_filter_for_super_admin (user_id)
-After debugging, we found that the "nodeType" attribute in the :reactParams field of the post request identifies the type of childNodes to be retrieved (i.e. "Courses" or "Questionnaires")
    "whodunnit = #{user_id} AND " if user_id && user_id.to_i > 0
  end


  def add_user_filter
-We found that a "FolderNode" value for this attribute equates to a questionnaire.
    "whodunnit = #{current_user.try(:id)} AND " if current_user.try(:id) && current_user.try(:id).to_i > 0
  end


  def add_event_filter (event)
===New Implementation===
    "event = '#{event}' AND " if event && !(event.eql? 'Any')
====Consolidation====
  end


  def add_date_time_filter
A lot of the efforts in pushing objects into moreContent could be consolidated into a central format. An example of how its currently implemented for courses is:
    "created_at >= '#{time_to_string(params[:start_time])}' AND " +
        "created_at <= '#{time_to_string(params[:end_time])}'"
  end


  def add_version_type_filter (version_type)
<pre>
    "item_type = '#{version_type}' AND " if version_type && !(version_type.eql? 'Any')
 
  end
if (newNodeType === 'course') {
          moreContent.push(
            <br/>
          )
          if (this.props.is_available) {
            moreContent.push(
              <span>
                <a title="Add TA" href={"/course/view_teaching_assistants?id="+(parseInt(this.props.id)/2).toString()+"&model=Course"}>
                  <img src="/assets/tree_view/add-ta-24.png" />
                </a>
                <a title="Create assignment" href={"/assignments/new?parent_id="+(parseInt(this.props.id)/2).toString()}>
                  <img src="/assets/tree_view/add-assignment-24.png" />
                </a>
                <a title="Add participants" href={"/participants/list?id="+(parseInt(this.props.id)/2).toString()+"&model=Course"}>
                  <img src="/assets/tree_view/add-participant-24.png" />
                </a>
                <a title="Create teams" href={"/teams/list?id="+(parseInt(this.props.id)/2).toString()+"&type=Course"}>
                  <img src="/assets/tree_view/create-teams-24.png" />
                </a>
                <a title="View grade summary by student" href={"/assessment360/course_student_grade_summary?course_id="+(parseInt(this.props.id)/2).toString()}>
                  <img src="/assets/tree_view/360-dashboard-24.png" />
                </a>
                <a title="Assign survey" href={"/survey_deployment/new?id="+(parseInt(this.props.id)/2).toString()+"&type=CourseSurveyDeployment"}>
                  <img src="/assets/tree_view/assign-survey-24.png" />
                </a>
                <a title="View aggregated teammate & meta reviews" href={"/assessment360/all_students_all_reviews?course_id="+(parseInt(this.props.id)/2).toString()}>
                  <span style={{"fontSize": "22px", "top": "8px"}} className="glyphicon glyphicon-list-alt"></span>
                </a>
              </span>
            )
          }
</pre>
</pre>
* The paginate method has been moved to the helper class Pagination_Helper. This new method can be now reused by the different components like UsersController etc. The method receives two parameters, first the list to paginate and second the number of items to be displayed in a page.
 
With a centralized implementation, we consolidated this information into const node_attribute. (See code repository for implementations for questionnaire and assignments, Course only shown for simplicity of demonstrating implementation).


<pre>
<pre>
module PaginationHelper
const node_attributes = {
isCourse(name) {
    return name === 'course' || name === "courses"
  },


  def paginate (items, number_of_items_per_page)
course: {
     items.page(params[:page]).per_page(number_of_items_per_page)
    plural: "course",
   end
    actions: [ {
      title: "Add TA",
      href: "/course/view_teaching_assistants?model=Course&id=",
      src: "/assets/tree_view/add-ta-24.png",
    },
    {
      title: "Create assignment",
      href: "/assignments/new?parent_id=",
      src: "/assets/tree_view/add-assignment-24.png",
    },
    {
      title: "Add participants",
      href: "/participants/list?model=Course&id=",
      src: "/assets/tree_view/add-participant-24.png",
    },
    {
      title: "Create teams",
      href: "/teams/list?type=Course&id=",
      src: "/assets/tree_view/create-teams-24.png",
    },
    {
      title: "View grade summary by student",
      href: "/assessment360/course_student_grade_summary?course_id=",
      src: "/assets/tree_view/360-dashboard-24.png",
    },
     {
      title: "Assign survey",
      href: "/survey_deployment/new?type=CourseSurveyDeployment&id=",
      src: "/assets/tree_view/assign-survey-24.png",
    },
    {
      title: "View aggregated teammate & meta reviews",
      href: "/assessment360/all_students_all_reviews?course_id=",
      src: null,
      extra: <span style={{"fontSize": "22px", "top": "8px"}} className="glyphicon glyphicon-list-alt"></span>
    }],
    getActions: function (id) {
    return (node_attributes.course.actions.map((action) =>
        (action.src
          ? <a title={action.title} href={action.href + id}> <img src={action.src} /></a>
          : <a title={action.title} href={action.href + id}> {action.extra} </a>)))
    }
   },


end
</pre>
</pre>


===Code improvements===
With this new implementation to obtain these actions, we just use the getActions function. Additionally now call the is{Tab} to receive the name of the tab.  
* Introduced a constant VERSIONS_PER_PAGE and assigned the value 25 to it. The pagination algorithm for VersionsController displays at most 25 versions in a page. The existing implementation uses the value 25 straight in the code and there are few problems associated with such an approach.
** It is not easy to understand what 25 is unless the programmer takes a close look at the code.
** In case if the value 25 is used at more than one places and in future a new requirement comes to show at most 30 versions in a page, all the values will have to be modified. It is not very DRY.
* The VersionsController was overriding AccessHelper - action_allowed? method to return true in all the cases. This was violating the whole purpose of the method action_allowed?. The purpose of this method is to determine whether the user who is triggering a CRUD operation is allowed to do so. So when the current user invokes a CRUD operation, the action_allowed? method is invoked first and if the method returns true the CRUD operation is triggered or else the user is intimated with a message and gracefully exited. Hence, when the action_allowed? method is overridden to return true always, it results in providing unauthorized access to certain users.


<pre>
<pre>
def action_allowed?
      if (node_attributes.isCourse(this.props.dataType)) {
    true
          moreContent.push(<br/>)
  end
          moreContent.push(...node_attributes.course.getActions(parseInt(this.props.id)/2))
        }
</pre>
</pre>


:With the new implementation the AccessHelper - action_allowed? method has been modified in such a way that unauthorized access is prevented. As per the new algorithm, 'new', 'create', 'edit', 'update' cannot be invoked by any user. These operations can be accessed only by ‘papertrail’ gem. Only an ‘Administrator’ or ‘Super-Administrator’ can call 'destroy_all' method. All the other methods are accessible to ‘Administrator’,  ‘Super-Administrator’, ‘Instructor’, ‘Teaching Assistant’ and ‘Student’.
===Code improvements===
 
====Unused Methods/Functions====
Unused Methods and Functions were commented out
* '''Example 1''':
<pre>
  function showIntelligentAssignmentDialog() {
    jQuery( "#intelligent_assignment_dialog" ).dialog({ closeText: "hide", modal: true, resizable: false, width: 500 });
  }
</pre>
* '''Example 2''':
The assignments tab did not have any data when we used the reset debugging extension (i.e. the childNodes attribute was null). Thus we assumed there was no data to display underneath each assignment
<pre>
<pre>
  def action_allowed?
    def children_node_ng
     case params[:action]
     flash[:error] = "Invalid JSON in the TreeList" unless json_valid? params[:reactParams][:child_nodes]
     when 'new', 'create', 'edit', 'update'
     child_nodes = child_nodes_from_params(params[:reactParams][:child_nodes])
     #Modifications can only be done by papertrail
     tmp_res = {}
      return false
     unless child_nodes.blank?
     when 'destroy_all'
       child_nodes.each do |node|
      ['Super-Administrator',
        initialize_fnode_update_children(params, node, tmp_res)
      'Administrator'].include? current_role_name
      end
    else
       #Allow all others
      ['Super-Administrator',
      'Administrator',
      'Instructor',
      'Teaching Assistant',
      'Student'].include? current_role_name
     end
     end
  end
</pre>
</pre>


===Automated Testing using RSPEC===
===Automated Testing using RSPEC===
The current version of expertiza did not have any test for VersionsController. Using the test driven development(TDD) approach, we have added an exhaustive set of RSPEC tests for VersionsController, to test all the modifications we have done to the code of the controller class. The tests use double and stub features of rspec-rails gem, to fake the log in by different users - Administrator, Instructor, Student etc. The tests can be executed "rpec spec" command as shown below.
<pre>
user-expertiza $rspec spec
.
.
.
Finished in 5.39 seconds (files took 25.33 seconds to load)
66 examples, 0 failures


Randomized with seed 19254
.
.
</pre>


===Testing from UI===
===Testing from UI===
Following are a few testcases with respectto our code changes that can be tried from UI:
1. To go to versions index page, type in the following url after logging in:
  http://152.46.16.81:3000/versions
2. After logging in as student/instructor or admin : Try accessing the  new, create, edit, update actions. These actions are not allowed to any of the users.
  http://152.46.16.81:3000/versions/new
  This calls the new action. In the current production version of expertiza, it is unhandled and application gives a default 404 page.
3. Another feature that can be tested from UI is Pagination. Try searching for a user's versions and see if the results are paginated or not. Search here:
  http://152.46.16.81:3000/versions/search


4. Visit the same URL as step 3, you should see only the students under that instructor in the users dropdown.


===References===
===References===


#[https://github.com/expertiza/expertiza Expertiza on GitHub]
#[https://github.com/expertiza/expertiza Expertiza on GitHub]
#[https://github.com/WintersLt/expertiza GitHub Project Repository Fork]
#[https://github.com/nlozevs/expertiza GitHub Project Repository Fork]
#[http://expertiza.ncsu.edu/ The live Expertiza website]
#[http://expertiza.ncsu.edu/ The live Expertiza website]
#[http://bit.ly/myexpertiza Demo link]  
#[http://13.58.196.224:3000/  Demo link]  
#[http://wikis.lib.ncsu.edu/index.php/Expertiza Expertiza project documentation wiki]
#[http://wikis.lib.ncsu.edu/index.php/Expertiza Expertiza project documentation wiki]
#[https://relishapp.com/rspec Rspec Documentation]
#[https://relishapp.com/rspec Rspec Documentation]
#Clean Code: A handbook of agile software craftsmanship. Author: Robert C Martin
#Clean Code: A handbook of agile software craftsmanship. Author: Robert C Martin

Latest revision as of 03:31, 20 October 2020

E2063. Refactoring the tree-display.js and tree display controller.rb

This page provides a description of the Expertiza based OSS project.



About Expertiza

Expertiza is an open source project based on Ruby on Rails framework. Expertiza allows the instructor to create new assignments and customize new or existing assignments. It also allows the instructor to create a list of topics the students can sign up for. Students can form teams in Expertiza to work on various projects and assignments. Students can also peer review other students' submissions. Expertiza supports submission across various document types, including the URLs and wiki pages.

Background

The tree-display.js and its tree_display_controller.rb files are designed to allow Expertiza users to view their Assignments, Courses and Questionnaires at one place. This is the primary control page, as well as the home page for instructors on Expertiza which allows them to create, modify, delete and view Assignments.

The primary problem with this is that both the files, due to their bulky and unoptimized methods, slow the rendering of UI on screen. The methods in these files can be studied and refactored to improve the overall performance of this controller and its corresponding UI. Moreover, any obsolete or unused methods can be removed and DRY principle should be implemented. This project mostly revolves around these 2 files, and would involve refactoring JavaScript more than Ruby on Rails. Knowledge of JavaScript is a prerequisite for this project.


Accomplishments

The following tasks were accomplished in this project:

  • Removed methods that were not used
  • Modified tree display controller to better parse data prior to being accessed by the react client
  • Fixed beta branch issue with showing dropdowns for courses and questionnaires.
  • Fixed beta branch issue with showing edit option for courses and assignments.

About Tree Display Controller

This class manages different the different tabs: courses, assignments, and questionaires.

Current Implementation

Functionality
  • Instructors can view the course, assignment and questionnaire tabs. Instructors can select rows for questionnaire's and courses to view a dropdown of more information. For course rows, they have options to edit, delete, copy, add TA, create an assignment, create teams, view grades, assign surveys, and view reviews. For assignments, they can edit, delete, copy, assign to course, create teams, assign reviewers, view submissions, view scores, view reports, and view survey responses.
Drawbacks and Solutions
  • Problem 1: The dropdown doesn't work for assignments and questionaires. This is due to the handleExpandClick function, as it appears that the get_sub_folder_contents method is not capable of returning the data.
if(this.props.dataType!='assignment') {
            _this = this;
            jQuery.get('/tree_display/get_sub_folder_contents',
                {
                    reactParams2: newParams
                },
                function (data) {
                    _this.props.data[id.split("_")[2]]['children'] = data;
                    _this.forceUpdate();
                }, 'json');
        }
  • Solution: Change the the JQuery route to a post because you can't give parameters to a get route and also change this in routes.rb file.
jQuery.post('/tree_display/get_sub_folder_contents',
                {
                    reactParams2: newParams
                },
                
post :get_sub_folder_contents
  • Problem 2: Tree display controller needs to better parse the data prior to being access to the react client because currently, there are too many comparisons when checking for the type of tab.

Example:

var SimpleTableRow = React.createClass({
    render: function () {
      var creation_date;
      var updated_date;
      var colWidthArray = ["30%", "0%", "0%", "0%", "25%", "25%", "20%"]
      var colDisplayStyle = {
        "display": ""
      }
      if (this.props.dataType === 'questionnaire') {
        colWidthArray = ["30%", "0%", "0%", "0%", "20%", "20%", "30%"]
        colDisplayStyle = {
          "display": "none"
        }
      } else if(this.props.dataType === 'course') {
        colWidthArray = ["20%", "0%", "0%", "20%", "20%", "20%", "20%"]
      }
     ......

}
  • Solution: Centralize the attributes of each tab so that the tree_display doesn't need to keep parsing between the tabs.
  • Problem 3: The Assignments and Courses tab isn't showing the edit option for each row. It seems that what's implemented is trying to verify that there was an active user and the active user is null.
if (app_variables.currentUserId == null || this.props.instructor_id == app_variables.currentUserId) {
              moreContent.push(
                  <span>
                <a title="Edit" href={"/" + newNodeType + "/" + (parseInt(this.props.id) / 2).toString() + "/edit"}><img
                    src="/assets/tree_view/edit-icon-24.png"/></a>
              </span>
              );
          }
  • Solution: Remove the active user implementation and replace with a check to make sure the current tab isn't Questionnaire.
if(newNodeType != 'questionnaires') { // should only be viewed by either assignments or courses
              moreContent.push(
                  <span>
                <a title="Edit" href={"/" + newNodeType + "/" + (parseInt(this.props.id) / 2).toString() + "/edit"}><img
                    src="/assets/tree_view/edit-icon-24.png"/></a>
              </span>
              );
          }


  • Drawback:The assignments tab did not have any data when we used the reset debugging extension (i.e. the childNodes attribute was null). Thus we assumed there was no data to display underneath each assignment.


-After debugging, we found that the "nodeType" attribute in the :reactParams field of the post request identifies the type of childNodes to be retrieved (i.e. "Courses" or "Questionnaires")

-We found that a "FolderNode" value for this attribute equates to a questionnaire.

New Implementation

Consolidation

A lot of the efforts in pushing objects into moreContent could be consolidated into a central format. An example of how its currently implemented for courses is:


if (newNodeType === 'course') {
          moreContent.push(
            <br/>
          )
          if (this.props.is_available) {
            moreContent.push(
              <span>
                <a title="Add TA" href={"/course/view_teaching_assistants?id="+(parseInt(this.props.id)/2).toString()+"&model=Course"}>
                  <img src="/assets/tree_view/add-ta-24.png" />
                </a>
                <a title="Create assignment" href={"/assignments/new?parent_id="+(parseInt(this.props.id)/2).toString()}>
                  <img src="/assets/tree_view/add-assignment-24.png" />
                </a>
                <a title="Add participants" href={"/participants/list?id="+(parseInt(this.props.id)/2).toString()+"&model=Course"}>
                  <img src="/assets/tree_view/add-participant-24.png" />
                </a>
                <a title="Create teams" href={"/teams/list?id="+(parseInt(this.props.id)/2).toString()+"&type=Course"}>
                  <img src="/assets/tree_view/create-teams-24.png" />
                </a>
                <a title="View grade summary by student" href={"/assessment360/course_student_grade_summary?course_id="+(parseInt(this.props.id)/2).toString()}>
                  <img src="/assets/tree_view/360-dashboard-24.png" />
                </a>
                <a title="Assign survey" href={"/survey_deployment/new?id="+(parseInt(this.props.id)/2).toString()+"&type=CourseSurveyDeployment"}>
                  <img src="/assets/tree_view/assign-survey-24.png" />
                </a>
                <a title="View aggregated teammate & meta reviews" href={"/assessment360/all_students_all_reviews?course_id="+(parseInt(this.props.id)/2).toString()}>
                  <span style={{"fontSize": "22px", "top": "8px"}} className="glyphicon glyphicon-list-alt"></span>
                </a>
              </span>
            )
          }

With a centralized implementation, we consolidated this information into const node_attribute. (See code repository for implementations for questionnaire and assignments, Course only shown for simplicity of demonstrating implementation).

const node_attributes = {
isCourse(name) {
    return name === 'course' || name === "courses"
  },

course: {
    plural: "course",
    actions: [ {
      title: "Add TA",
      href: "/course/view_teaching_assistants?model=Course&id=",
      src: "/assets/tree_view/add-ta-24.png",
    },
    {
      title: "Create assignment",
      href: "/assignments/new?parent_id=",
      src: "/assets/tree_view/add-assignment-24.png",
    },
    {
      title: "Add participants",
      href: "/participants/list?model=Course&id=",
      src: "/assets/tree_view/add-participant-24.png",
    },
    {
      title: "Create teams",
      href: "/teams/list?type=Course&id=",
      src: "/assets/tree_view/create-teams-24.png",
    },
    {
      title: "View grade summary by student",
      href: "/assessment360/course_student_grade_summary?course_id=",
      src: "/assets/tree_view/360-dashboard-24.png",
    },
    {
      title: "Assign survey",
      href: "/survey_deployment/new?type=CourseSurveyDeployment&id=",
      src: "/assets/tree_view/assign-survey-24.png",
    },
    {
      title: "View aggregated teammate & meta reviews",
      href: "/assessment360/all_students_all_reviews?course_id=",
      src: null,
      extra: <span style={{"fontSize": "22px", "top": "8px"}} className="glyphicon glyphicon-list-alt"></span>
    }],
    getActions: function (id) {
    return (node_attributes.course.actions.map((action) =>
        (action.src
          ? <a title={action.title} href={action.href + id}> <img src={action.src} /></a>
          : <a title={action.title} href={action.href + id}> {action.extra} </a>)))
    }
  },

With this new implementation to obtain these actions, we just use the getActions function. Additionally now call the is{Tab} to receive the name of the tab.

      if (node_attributes.isCourse(this.props.dataType)) {
          moreContent.push(<br/>)
          moreContent.push(...node_attributes.course.getActions(parseInt(this.props.id)/2))
        }

Code improvements

Unused Methods/Functions

Unused Methods and Functions were commented out

  • Example 1:
  function showIntelligentAssignmentDialog() {
    jQuery( "#intelligent_assignment_dialog" ).dialog({ closeText: "hide", modal: true, resizable: false, width: 500 });
  }
  • Example 2:

The assignments tab did not have any data when we used the reset debugging extension (i.e. the childNodes attribute was null). Thus we assumed there was no data to display underneath each assignment

    def children_node_ng
    flash[:error] = "Invalid JSON in the TreeList" unless json_valid? params[:reactParams][:child_nodes]
    child_nodes = child_nodes_from_params(params[:reactParams][:child_nodes])
    tmp_res = {}
    unless child_nodes.blank?
      child_nodes.each do |node|
        initialize_fnode_update_children(params, node, tmp_res)
      end
    end

Automated Testing using RSPEC

Testing from UI

References

  1. Expertiza on GitHub
  2. GitHub Project Repository Fork
  3. The live Expertiza website
  4. Demo link
  5. Expertiza project documentation wiki
  6. Rspec Documentation
  7. Clean Code: A handbook of agile software craftsmanship. Author: Robert C Martin