CSC/ECE 517 Fall 2017/E1781 Topic Management: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
Line 283: Line 283:
             });
             });
           });
           });
On the model '''''app/models/institution.rb''''' we also created the relationship where institution has many courses.  This is also to allow linkage of the two tables for the drop down to work in the view.
  class Institution < ActiveRecord::Base
    attr_accessible :name
    has_many :courses
    validates_length_of :name, minimum: 1
    validates_uniqueness_of :name
  end
On the view '''''app/views/course/_course.html.erb''''' we added the collection box to display the linked name from the institution table to the institution_id in the course table.  This completed the ability to select and store the institution_id into the database.
<pre>
  <p><label for="institution_name">Institution Name</label><br/>
  <%= select("course", "institutions_id", Institution.all.collect{ |c| [ c.name, c.id] }) %></p>
</pre>
In order to properly display the institution_id associated with the courses on the view '''''app/views/tree_display/list.html.erb''''' it was necessary to understand the Factory Method design pattern. A subclass of '''''app/models/node.rb''''' is '''''app/models/course_node.rb''''' and '''''app/models/assigment_node.rb'''''.  Both need similar functions to be used by '''''app/assets/javascripts/tree_display.jsx''''' for displaying rows of courses and assignments.  This was added to both, but only used by '''''app/models/course_node.rb''''':
  def get_institution_id
    # Course.find(self.node_object_id).course_id
    @course = Course.find(self.node_object_id) unless @course
    @course.institutions_id
  end
'''''app/controllers/tree_display_controller.rb''''' was modified to include a local variable for used by the view in displaying the institution_id.
  def update_tmp_obj(tmp_object, node)
    tmp={
      "directory" => node.get_directory,
      "creation_date" => node.get_creation_date,
      "updated_date" => node.get_modified_date,
      "institution" => Institution.where(:id => node.get_institution_id),
      "private" => node.get_instructor_id == session[:user].id ? true : false
      }
  tmp_object.merge!(tmp)
  end
'''''app/assets/javascript/tree_display.jsx''''' was also modified to show different rows between courses and assignments, since only courses were required to show the institution_id.  This was added in order to show the institution_id which is displayed on '''''app/views/tree_display/list.html.erb''''':
  jQuery.each(this.props.data, function (i, entry) {
    if (((entry.name && entry.name.indexOf(_this.props.filterText) !== -1) ||
      (entry.directory && entry.directory.indexOf(_this.props.filterText) !== -1) ||
      (entry.creation_date && entry.creation_date.indexOf(_this.props.filterText) !== -1) ||
      (entry.instructor && entry.instructor.indexOf(_this.props.filterText) !== -1) ||
      '''(entry.institution && entry.institution.indexOf(_this.props.filterText) !== -1) ||'''
      (entry.updated_date && entry.updated_date.indexOf(_this.props.filterText) !== -1)) &&
      (entry.private == true || entry.type == 'FolderNode')) {
      _rows.push(<ContentTableRow
        key={entry.type+'_'+(parseInt(entry.nodeinfo.id)*2).toString()+'_'+i}
        id={entry.type+'_'+(parseInt(entry.nodeinfo.node_object_id)*2).toString()+'_'+i}
        name={entry.name}
        directory={entry.directory}
        instructor={entry.instructor}
        '''institution={entry.institution}'''
        creation_date={entry.creation_date}
        updated_date={entry.updated_date}
        actions={entry.actions}
        is_available={entry.is_available}
        course_id={entry.course_id}
        max_team_size={entry.max_team_size}
        is_intelligent={entry.is_intelligent}
        require_quiz={entry.require_quiz}
        dataType={_this.props.dataType}
        //this is just a hack. All current users courses are marked as private during fetch for display purpose.
        private={entry.private}
        allow_suggestions={entry.allow_suggestions}
        has_topic={entry.has_topic}
        rowClicked={_this.handleExpandClick}
        newParams={entry.newParams}
      />)
<br>


=== Issue #926: ===
=== Issue #926: ===

Revision as of 23:04, 27 October 2017

This wiki page describes the changes made according to the specification of E1781 OSS assignment for Fall 2017.

Peer Review Information

For testing the changes made, the following credentials are recommended:

  • Instructor Login: username: instructor6 password: password

This project does compile. Reviewers can refer Testing Plan: Testing from the UI and the YouTube link for navigation help.


The testing plan is at the end of this document. Design principles were not needed since we mostly modified existing work.

Introduction

Background

Expertiza is a web portal which can be used to manage assignments related to a course. It provides a platform to view assignments, manage teams, select topics and work improvement through anonymous peer reviews.

The instructor are allowed to define different topics that students or teams could choose from as their assignment. Each topic can have 0 or more slots that indicate the number of students or teams that can sign up for that topic. We identified several ideas that can improve user experience when managing the topics. Thus, we would like you to introduce new features to implement these ideas.

Problem Statement

The following were the tasks identified to be accomplished through this project.

  • Issue #971: Change create topic UI into AJAX
  • Issue #926: We need a way to sort topics by topic number in assignment#edit page
  • Issue #718: We should allow instructors to give feedback when accepting or rejecting topic suggestions

Files Modified in this Project

The following files were modified for this project.

  • app/assets/javascripts/application.js
  • app/assets/javascripts/signup.js
  • app/assets/stylesheets/application.scss
  • app/controllers/sign_up_sheet_controller.rb
  • app/models/sign_up_topic.rb
  • app/views/assignments/edit.html.erb
  • app/views/layouts/application.html.erb
  • app/views/sign_up_sheet/_add_signup_topics.html.erb
  • app/views/sign_up_sheet/_add_signup_topics_staggered.html.erb
  • bower.json
  • config/routes.rb
  • Gemfile - changed json from version 1.8.3 to 1.8.5

Files Added in this Project

The following files were added for this project:

  • app/assets/images/icons-2x.png
  • app/assets/images/icons.png

Solutions implemented and Delivered

Issue #971: Change create topic UI into AJAX

Currently, when instructors manually enter topics, they have to go back and forth between the list of the topic page (views>sign_up_sheet>_add_signup_topics.html.erb) and the create topic page(views>sign_up_sheet>new.html.erb). Adding a new topic can be done through an editable grid (see js-grid) without leaving the list of topic page, by making use of AJAX. The list should be automatically updated when a new topic is entered. To enable AJAX the sign_up_sheet_controller.rb is modified so that it renders JSON to handle the javascript table/form.

In addition, when adding a topic, the default slot is set as 1 instead of 0. The current warning message that shows up when the slot is 0, has been fixed to close properly.


On the controller app/controllers/sign_up_sheet_controller.rb we edited the Load_add_signup_topics, which was converted from a function into a Rest endpoint(Json).

  def load_add_signup_topics
   @id = params[:id]
   assignment_id =  params[:id]
   @sign_up_topics = SignUpTopic.where('assignment_id = ?', assignment_id)
   @slots_filled = SignUpTopic.find_slots_filled(assignment_id)
   @slots_waitlisted = SignUpTopic.find_slots_waitlisted(assignment_id)
   @assignment = Assignment.find(assignment_id)
   @participants = SignedUpTeam.find_team_participants(assignment_id)
   # colloborating for JSON
   @sign_up_topics.each {|topic|
   topic_id = topic.id
   slots_fill_temp = 0
   slots_waitlisted = 0
   participants = []
   if @slots_filled 
     @slots_filled.each {|slot| 
       if slot.topic_id == topic_id
         slots_fill_temp = slot.count
       end
     }
   end
   if @slots_waitlisted 
     @slots_waitlisted.each {|slot| 
       if slot.topic_id == topic_id
         slots_waitlisted = slot.count
       end
     }
   end
   if @participants 
     @participants.each {|participant| 
       if participant.topic_id == topic_id
          
         participants << participant
       end
     }
   end
   topic.slots_filled_value = slots_fill_temp
   topic.slots_waitlisted = slots_waitlisted
   topic.slots_available = topic.max_choosers - topic.slots_filled_value
   topic.partipants = participants
  }
   render :json => {
       :id => @id.as_json,
       :sign_up_topics => @sign_up_topics.as_json( :methods => [:slots_filled_value,:slots_waitlisted,:slots_available,:partipants]),
       :slots_waitlisted => @slots_waitlisted.as_json,
       :assignment => @assignment.as_json
     }
 end

An Example output of the Json rendered for Assignment Id : 843 by the load_add_signup_topics Action is as follows

{ "id": "843", "sign_up_topics": [ { "id": 3958, "topic_name": "Power consumption issue", "assignment_id": 843, "max_choosers": 3, "category": "", "topic_identifier": "1.1.1", "micropayment": 0, "private_to": null, "description": null, "link": null, "slots_filled_value": 0, "slots_waitlisted": 0, "slots_available": 3, "partipants": [] }, { "id": 3959, "topic_name": "Perspectives on parallel computers (1.4 in old edition)", "assignment_id": 843, "max_choosers": 2, "category": "", ... (And so on.)


Similarly, We have changed all the actions/methods (destroy|setup_new_topic|update_existing_topic|update|set_values_for_new_topic ) to render Json rather than rendering a view, In order to satisty our Ajax requirements in the front end.


On the model app/models/sign_up_topic.rb we have creates attribute accessor for variables to set properties that are required for JSON.

 attr_accessor :slots_filled_value
 attr_accessor :slots_waitlisted
 attr_accessor :slots_available
 attr_accessor :partipants

On the View 'app/views/sign_up_sheet/_add_signup_topics.html.erb' we have just added the following

<%= content_tag :div, id: "jsGrid", data: { assignmentid: @assignment.id }  do %>
<% end %>

The contents of the above tag is completely rendered via Javascript, where we use JSGrid for dynamically allowing users to Add, Delete and Update Topics on the same page.

               jQuery("#jsGrid").jsGrid({  
               width: "100%",
               height : "400%",
               filtering: true,
               inserting: true,
               editing: true,
               sorting: true,
               paging: true,
               autoload: true,
               updateOnResize : true,                             
               deleteConfirm: "Do you really want to delete client?",
               controller: {
                   loadData: function (filter) {
                   var data = $.Deferred();
                       $.ajax({
                           type: "GET",
                           contentType: "application/json; charset=utf-8",
                           url: "/sign_up_sheet/"+jQuery("#jsGrid").data("assignmentid")+"/load_add_signup_topics",
                             // url: "/sign_up_sheet/847/load_add_signup_topics",
                           dataType: "json"
                           }).done(function(response){
                              var sign_up_topics =  response.sign_up_topics;
                              data.resolve(sign_up_topics);
                       });
                   return data.promise();
                   },
                   insertItem: function (topic) {
                   console.log("testing")
                   console.log(topic)   
                   topic.id = jQuery("#jsGrid").data("assignmentid")
                   var data = $.Deferred();
                       $.ajax({
                           type: "POST",
                           url: "/sign_up_sheet/",
                             // url: "/sign_up_sheet/847/load_add_signup_topics",
                           data: topic
                           }).done(function(response){
                             data.resolve(response);
                       });
                   return data.promise();
                   },
                   updateItem: function (topic) {
                   console.log("testing")
                   console.log(topic)   
                   var data = $.Deferred();
                       $.ajax({
                           type: "PUT",
                           url: "/sign_up_sheet/"+topic.id,
                             // url: "/sign_up_sheet/847/load_add_signup_topics",
                           data: topic
                           }).done(function(response){
                             data.resolve(response);
                       });
                   return data.promise();
                   },
                  deleteItem: function(item) {
                       return $.ajax({
                           type: "DELETE",
                           url: "/sign_up_sheet/" + item.id
                       });
                   }
               },
               fields: [
                   { name: "topic_identifier", type: "text" ,title: "Topic #",width : "1.5%" },
                   { name: "topic_name", type: "text" ,title: "Topic name(s)",width : "5%",
                       itemTemplate: function(value,topic) {
                          var linkText =  $("<a>").attr("href", topic.link).text(value);
                           var signupUrl = "/sign_up_sheet/signup_as_instructor?assignment_id=" + assignmentId + 
                           "&topic_id="+topic.id;
                           var signUpUser = $("<a>").attr("href", signupUrl);
                           var signUpUserImage = $("<img>").attr({src: "/assets/signup-
                           806fc3d5ffb6923d8f5061db243bf1afd15ec4029f1bac250793e6ceb2ab22bf.png"
                           , title: "Sign Up Student" 
                           , alt: "Signup"});                 
                           //participants
                           var participants_temp = topic.partipants;
                           if(participants_temp == null)
                               participants_temp = [];
                           var participants_div = $('< div >');
                           for(var p = 0 ; p < participants_temp.length ; p ++)
                           {                              
                               var current_participant = participants_temp[p];
                               var text = $("");
                               text.html(current_participant.user_name_placeholder);
                               var dropStudentUrl = "/sign_up_sheet/delete_signup_as_instructor/" + current_participant.team_id + "?
                               topic_id="+topic.id;                               
                               var dropStudentAnchor = $("<a>").attr("href", dropStudentUrl);             
                               var dropStudentImage = $("<img>").attr({src: "/assets/delete_icon.png"
                                , title: "Drop Student" 
                                , alt: "Drop Student Image"});
                               participants_div.append(text).append(dropStudentAnchor.append(dropStudentImage));
                           }
                             return $('< div >').append(linkText).append(signUpUser.append(signUpUserImage)).append(participants_div);      
                      } , filtering: true 
               },
                   { name: "category", type: "text",title: "Topic category" ,width : "5%" },
                   { name: "max_choosers", type: "text" ,title: "# Slots" ,width : "2%"},
                   { name: "slots_available", editing: false ,title: "Available Slots",width : "2%"},
                   { name: "slots_waitlisted",  editing: false  ,title: "Num on Waitlist"  , width : "2%"},
                   { name: "id",title: "Book marks",width :"20%", editing: false,width :"2%",
                       itemTemplate: function(value, topic) {
                       console.log("value ",value)
                       console.log("topic ",topic)
                       var $customBookmarkAddButton = $("<a>").attr({
                           href:"/bookmarks/list/"+topic.id });                       
                       var $BookmarkSelectButton= $("").attr({class :"jsgrid-bookmark-show fa fa-bookmark", title :"View Topic 
                       Bookmarks"});
                       var $customBookmarkSetButton = $("<a>").attr({
                           href:"/bookmarks/new?id="+topic.id });                       
                       var $BookmarkSetButton= $("").attr({class :"jsgrid-bookmark-add fa fa-plus" , title :"Add Bookmark to 
                       Topic"});
                       var set1 = $customBookmarkAddButton.append($BookmarkSelectButton);
                       var set2 = $customBookmarkSetButton.append($BookmarkSetButton);
                       return $('< div >').attr("align","center").append(set1).append(set2);
                    }
                    /*   itemTemplate: function(value, topic) {
                       console.log("value ",value)
                       console.log("topic ",topic)
                      return $customBookmarkSetButton.append($BookmarkSetButton);
                    }*/
                    ,filtering: true 
                     },               
                  { type: "control",                      
                       editButton: true,                               // show edit button
                       deleteButton: true,                             // show delete button
                       clearFilterButton: true,                        // show clear filter button
                       modeSwitchButton: true,                         // show switching filtering/inserting button
                       width : "3%"
                       
                }  ,
                 { name: "link", type: "text",title: "Topic Link" ,width :"12%" },
                   { name: "description", type: "textarea",title: "Topic Description",width :"12%" }
                               ]
           });
         });

Issue #926:

This task called for removing the 'Actions' column on the signup sheet for assignments that have been completed (sign_up_sheet/list.html.erb).


(sign_up_sheet/list.html.erb) renders app/view/sign_up_sheet/_table_line.html.erb and an if statement is used to not show finished assignements.

 <% if @assignment.current_stage_name(@topic_id) != 'Finished' %>

<%= render :partial => '/sign_up_sheet/actions', :locals => {:i=>i,:topic=>topic, :is_suggested_topic=>is_suggested_topic ||= false} %>

 <% end %>



Issue #718:

This task called for adding a one-time notification at the top of the page to show changes/notifications. It necessitated adding a table to the database called notifications. Therefore, when conducting peer reviews, a migration must be performed to add the new table. Then we used scaffolding to add notifications (controller, model, and views). Next we added a link on the view (app/view/tree_display/index.html.erb) to go to the view (app/view/notification/index.html.erb) in order to manage notifications.


A migration to create a notifications table was added.

  class CreateNotifications < ActiveRecord::Migration
    def change
      create_table :notifications do |t|
        t.string :subject
        t.text :description
        t.date :expiration_date
        t.boolean :active_flag
        t.timestamps null: false
      end
    end
  end


Using scaffolding, a new index was added app/view/notifications/index.html.erb.

  <h1>Listing Notifications</h1>

  <table>
    <thead>
      <tr>
        <th>Subject</th>
        <th>Description</th>
        <th>Expiration date</th>
        <th>Active flag</th>
        <th colspan="3"></th>
      </tr>
    </thead>

    <tbody>
      <% @notifications.each do |notification| %>
        <tr>
          <td><%= notification.subject %></td>
          <td><%= notification.description %></td>
          <td><%= notification.expiration_date %></td>
          <td><%= notification.active_flag %></td>
          <td><%= link_to 'Show', notification %></td>
          <td><%= link_to 'Edit', edit_notification_path(notification) %></td>
          <td><%= link_to 'Destroy', notification, method: :delete, :confirm => 'Are you sure?' %></td>
        </tr>
      <% end %>
    </tbody>
  </table>

  <br>

  <%= link_to 'New Notification', new_notification_path %>


Added link on page for managers to manage notifications app/view/tree_display/list.html.erb.



Notifications are added and modified in similar views app/view/tree_display/new.html.erb or app/view/tree_display/edit.html.erb.



When any type of user successfully logs in, flash notifications called from the controller app/controllers/auth_controller.rb.

  def after_login(user)
    session[:user] = user
    AuthController.set_current_role(user.role_id, session)

    flash[:notification] = 'This will display notifications on login'

    redirect_to controller: AuthHelper.get_home_controller(session[:user]),
                action: AuthHelper.get_home_action(session[:user])
  end


Flash notifications are displayed once at login due to a line in the view app/view/layouts/application.html.erb which is used by all other views.

<%= render 'shared/flash_notifications' %>


Each individual flash notification that is active and is not expired is displayed through the use of the view app/view/shared/_flash_notifications.html.erb.

  <% if  (flash_message :notification) != nil %>
    <% @notifications = Notification.where(:active_flag => true) %>
    <% if !@notifications.nil? %>
      <% @notifications.each do |notification| %>
        <% if notification.expiration_date >= Date.today %>
          <table style="background-color: #faebcc">
            <tbody style="background-color: #faebcc"><br>
              <tr><p style="color:black; font-width:bold; background-color: #faebcc"><%= notification.subject.upcase %></p></tr>
              <tr><%= notification.description %></tr>
            </tbody>
          </table>
        <% end %>
      <% end %>
    <% end %>
  <% end %>


The notifications are displayed on whatever page the login screen goes to first depending of the type of user logging into the system. They are only display once and are gone whenever the page is refreshed or changed.


Testing Plan: Testing from the UI

UI Testing

The majority of the changes can be tested via the UI. Follow the instructions below to check the tasks:

  • Issue #971: Change create topic UI into AJAX
  1. Login as an instructor. It will automatically go to the manage courses page
  2. Click Assignments tab next to Courses
  3. Select the assignment for which a new topic has to be added
  4. Click the edit icon for the assignment and go to Topics tab
  5. You will be able to see the topics list in a grid format. Click on add icon on the right side corner of the grid
  6. You can also edit an existing entry by just clicking on that row in the grid without being redirected to a new page
  • Issue #926: Sort topics by topic number in assignment#edit page.
  1. Log in as any user or instructor.
  2. Go to the assignments list. Note: this is not the manage assignments for instructors.
  3. Click into a finished assignment. You will notice the actions column is gone.
  4. Click into a current assignment. You will notice the actions column appears.
  • Issue #718:
  1. Login as an instructor. It will automatically go to the manage courses page.
  2. Click on the "Manage Notifications" link above the "Courses" and "Assignments" links. Note: The link was placed here because the system has numerous issues when adding menu items. It would require us to do more work that the entirety of this project to correct the menu additions in the superuser menu.
  3. You will be directed to a creation page for notifications. Note: in order for a notification to display the expiration date must be the current date or later, and the active checkbox must be selected.
  4. Log out once the notification is created.
  5. Log in using any account. The notification will display on the first page the user is shown. It will disappear when they change or reload the page. Note: instructors on the management page will retain the notification if they switch between Assignments, Courses, and Questionnaires. This is because the JavaScript does not actually reload the page.