CSC/ECE 517 Fall 2021 - E2149. Finish Github metrics integration - Reputations: Difference between revisions
(→Rspec) |
|||
(21 intermediate revisions by 3 users not shown) | |||
Line 4: | Line 4: | ||
==Problem Statement== | ==Problem Statement== | ||
The functionality of the showing GitHub metrics cannot be turned off, meaning that an instructor who uses Expertiza for writing assignments in an English course would get a Github metrics report. There needs to be a way to turn the report off for a particular assignment | The functionality of the showing GitHub metrics cannot be turned off, meaning that an instructor who uses Expertiza for writing assignments in an English course would get a Github metrics report. There needs to be a way to turn the report off for a particular assignment. We will some RSpec test code to test the function. | ||
==Team Action Items== | ==Team Action Items== | ||
==== Adding turn on and turn | ==== Adding turn on and turn off option ==== | ||
The functionality of the current project cannot be turned off. We will provide an option for instructors not to query Github metrics data before they post the assignment, which is the way we turn the GitHub report off for a particular assignment. | The functionality of the current project cannot be turned off. We will provide an option for instructors not to query Github metrics data before they post the assignment, which is the way we turn the GitHub report off for a particular assignment. | ||
Line 17: | Line 14: | ||
==== Database Modification ==== | ==== Database Modification ==== | ||
In order to determine whether the GitHub report of the assignment is turned on or turned off, we need to create a new table named " | In order to determine whether the GitHub report of the assignment is turned on or turned off, we need to create a new table named "github_metric_uses" to save the id of the assignment whose GitHub metric is turned on. We also have an alternative solution to add a boolean field in the assignment table, but due to security concerns, we give it up. We think we should not change the data of the assignment. | ||
'''Table: | '''Table: github_metric_uses''' | ||
{| class="wikitable" | {| class="wikitable" | ||
|- style="font-weight:bold;" | |- style="font-weight:bold;" | ||
Line 31: | Line 28: | ||
| 15 | | 15 | ||
|} | |} | ||
==== Frontend Modification ==== | ==== Frontend Modification ==== | ||
Line 42: | Line 33: | ||
1. We edited expertiza\app\views\assignments\edit\_general.html.erb and added a checkbox. The codes are as follow. | 1. We edited expertiza\app\views\assignments\edit\_general.html.erb and added a checkbox. The codes are as follow. | ||
If the id of the assignment exists, there will be a checkbox for the user to use github metrics. The | If the id of the assignment exists, there will be a checkbox for the user to use github metrics. The GithubMetricUsesController.exist use the class method in the controller. | ||
<!--Github--> | <!--Github--> | ||
<tr> | <tr> | ||
<td style='padding:5px' id=' | <td style='padding:5px' id='assignment_github_use' colspan="2"> | ||
<input type="checkbox" name = " | <input type="checkbox" name = "github_use" id="github_use" <%= "checked" | ||
if | if GithubMetricUsesController.exist(@assignment_form.assignment.id) %>></input> | ||
<%= label_tag(' | <%= label_tag('github_use', 'Use github metrics?') %> | ||
</td> | </td> | ||
</tr> | </tr> | ||
Line 57: | Line 48: | ||
If the id of the assignment exists and github_access_token is null (it means that the user hasn't logged in), then the page will show 'Login to Query Github data' button. If github_access_token is not null, it would show 'Refresh Github data' button because the user has logged in. | If the id of the assignment exists and github_access_token is null (it means that the user hasn't logged in), then the page will show 'Login to Query Github data' button. If github_access_token is not null, it would show 'Refresh Github data' button because the user has logged in. | ||
<%if | <%if GithubMetricUsesController.exist(@assignment.id) %> | ||
<% if session["github_access_token"].nil? %> | <% if session["github_access_token"].nil? %> | ||
<% topic_identifier, topic_name, users_for_curr_team, participants = get_data_for_list_submissions(@teams.first) %> | <% topic_identifier, topic_name, users_for_curr_team, participants = get_data_for_list_submissions(@teams.first) %> | ||
Line 80: | Line 71: | ||
jQuery("#topics_tab_header").hide(); | jQuery("#topics_tab_header").hide(); | ||
} | } | ||
$('# | $('#github_use').change(function () { | ||
if (jQuery('# | if (jQuery('#github_use').is(':checked')) { | ||
jQuery.ajax({ | jQuery.ajax({ | ||
url: '/ | url: '/github_metric_uses/' + <%= @assignment.id %>, | ||
method: 'POST', | method: 'POST', | ||
xhrFields: { | xhrFields: { | ||
Line 92: | Line 83: | ||
} else{ | } else{ | ||
jQuery.ajax({ | jQuery.ajax({ | ||
url: '/ | url: '/github_metric_uses/' + <%= @assignment.id %>, | ||
method: 'DELETE', | method: 'DELETE', | ||
xhrFields: { | xhrFields: { | ||
Line 104: | Line 95: | ||
==== Controller Modification ==== | ==== Controller Modification ==== | ||
We added a function in expertiza\app\controllers\ | We added a function in expertiza\app\controllers\github_metric_uses_controller.rb as follwowing. | ||
===== Class Methods ===== | ===== Class Methods ===== | ||
1. Check if assignment_id exists in the table | 1. Check if assignment_id exists in the table github_metric_uses. If so, return TRUE. | ||
#check if assignment_id exists in the table | #check if assignment_id exists in the table github_metric_uses | ||
def self.exist(assignment_id) | def self.exist(assignment_id) | ||
github_use = GithubMetricUses.find_by(assignment_id: assignment_id) | |||
!github_use.nil? | |||
end | end | ||
2. If assignment_id does not exist in the table | 2. If assignment_id does not exist in the table github_metric_uses, save it. | ||
#if assignment_id does not exist in the table | #if assignment_id does not exist in the table github_metric_uses, save it | ||
def self.save(assignment_id) | def self.save(assignment_id) | ||
unless exist(assignment_id) | |||
github_use = GithubMetricUses.new(assignment_id) | |||
github_use.assignment_id = assignment_id | |||
github_use.save | |||
end | |||
end | end | ||
3. If assignment_id exists in the table | 3. If assignment_id exists in the table github_metric_uses, delete it | ||
#if assignment_id exists in the table | #if assignment_id exists in the table github_metric_uses, delete it | ||
def self.delete(assignment_id) | def self.delete(assignment_id) | ||
if exist(assignment_id) | |||
github_use = GithubMetricUses.find_by(assignment_id: assignment_id) | |||
github_use.destroy | |||
end | |||
end | end | ||
Line 138: | Line 129: | ||
1. Return TRUE if the assignment id exists. | 1. Return TRUE if the assignment id exists. | ||
#check if assignment_id exists in the table | #check if assignment_id exists in the table github_metric_uses | ||
def exist | def exist | ||
assignment_id = params[:assignment_id] | |||
GithubMetricUsesController.exist(assignment_id) | |||
end | end | ||
2. Save assignment id if it doesn't exist. This function is for the frontend page to select the checkbox. | 2. Save assignment id if it doesn't exist. This function is for the frontend page to select the checkbox. | ||
#if assignment_id does not exist in the table | #if assignment_id does not exist in the table github_metric_uses, save it | ||
def save | def save | ||
assignment_id = params[:assignment_id] | |||
GithubMetricUsesController.save(assignment_id) | |||
respond_to do |format| | |||
format.json { render json: assignment_id } | |||
end | |||
end | end | ||
3. Delete assignment id if it exists. This function is for the frontend page to cancel the checkbox. | 3. Delete assignment id if it exists. This function is for the frontend page to cancel the checkbox. | ||
#if assignment_id exists in the table | #if assignment_id exists in the table github_metric_uses, delete it | ||
def delete | def delete | ||
assignment_id = params[:assignment_id] | |||
GithubMetricUsesController.delete(assignment_id) | |||
respond_to do |format| | |||
format.json { render json: assignment_id } | |||
end | |||
end | end | ||
==== Testing ==== | ==== Testing ==== | ||
The details are in the Test Plan [https://expertiza.csc.ncsu.edu/index.php/CSC/ECE_517_Fall_2021_-_E2149._Finish_Github_metrics_integration_-_Reputations#Test_Plan]. | |||
==Functionality Walk Through== | ==Functionality Walk Through== | ||
Assignments could be edited on the assignment viewing page. Also, the submissions would be showed and there would be differences when "Github metrics" is chosen or is not chosen. | |||
=== Assignment edit page view === | === Assignment edit page view === | ||
An instructor can customize an assignment in this view. We will provide an option for instructors to decide whether they need Github metrics in the assignment or not. | An instructor can customize an assignment in this view. We will provide an option for instructors to decide whether they need Github metrics in the assignment or not. | ||
Line 195: | Line 188: | ||
[[File:E2149_Uml_1.png|800px]] | [[File:E2149_Uml_1.png|800px]] | ||
== | ==OmniAuth Github API update== | ||
The Github API requires users to be authenticated to query the API. Since GitHub no longer supports authentication through query parameters, we move the authentication to the header. From September 8, 2021, using access_token as a query parameter to access the API (as a user or as a GitHub App) and communication between the Expertiza server and Github, and using client_id/client_secret to make OAuth app unauthenticated are disabled. | The Github API requires users to be authenticated to query the API. Since GitHub no longer supports authentication through query parameters, we move the authentication to the header. From September 8, 2021, using access_token as a query parameter to access the API (as a user or as a GitHub App) and communication between the Expertiza server and Github, and using client_id/client_secret to make OAuth app unauthenticated are disabled. | ||
Line 214: | Line 207: | ||
when "google_oauth2" | when "google_oauth2" | ||
google_login | google_login | ||
when "github2021" # due to github | when "github2021" # due to github https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/ | ||
custom_github_login | custom_github_login | ||
else | else | ||
Line 235: | Line 228: | ||
session["github_access_token"] = access_token | session["github_access_token"] = access_token | ||
redirect_to controller: 'assignments', action: 'list_submissions', id: session["assignment_id"] | redirect_to controller: 'assignments', action: 'list_submissions', id: session["assignment_id"] | ||
end | |||
Move the authentication to the header | |||
def query_commit_statistics(data) | |||
uri = URI.parse("https://api.github.com/graphql") | |||
http = Net::HTTP.new(uri.host, uri.port) # host: api.github.com, port: 443 | |||
http.use_ssl = true | |||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER | |||
# request = Net::HTTP::Post.new(uri.path, 'Authorization' => 'Bearer' + ' ' + session["github_access_token"]) # set up authorization | |||
request = Net::HTTP::Post.new(uri.path) # set up authorization | |||
# send the token in the header | |||
request['Authorization'] = 'Bearer' + ' ' + session["github_access_token"] | |||
request.body = data.to_json # convert query message to json and pass as request body | |||
response = http.request(request) # make the actual request | |||
ActiveSupport::JSON.decode(response.body.to_s) # convert the response body to string, decoded then return | |||
end | end | ||
Line 243: | Line 251: | ||
Our UI tests aim to capture the following core pieces of functionality: | Our UI tests aim to capture the following core pieces of functionality: | ||
'''Turn on and turn | '''Turn on and turn off functionality button: ''' | ||
In order to test the functionality manually, we follow the following steps: | In order to test the functionality manually, we follow the following steps: | ||
Line 254: | Line 262: | ||
=== Rspec === | === Rspec === | ||
Detailed of Github Testing | Detailed of Using Github Testing | ||
first, go to the assignment edit page and check the checkbox of use github metric, then go to the list submission page check the page if has the content about ithub data. | |||
it "check the box of the use github metrics? and the list_submissions page will have the content 'Github data' " do | it "check the box of the use github metrics? and the list_submissions page will have the content 'Github data' " do | ||
visit "/assignments/#{@assignment.id}/edit" | visit "/assignments/#{@assignment.id}/edit" | ||
check('Use github metrics?', allow_label_click: true) | |||
visit "/assignments/list_submissions?id=#{@assignment.id}" | visit "/assignments/list_submissions?id=#{@assignment.id}" | ||
expect(page).to have_content("Github data") | expect(page).to have_content("Github data") | ||
Line 266: | Line 273: | ||
it "uncheck the checkbox of the use github metrics? and the list_submissions page will not have the content 'Github data' " do | it "uncheck the checkbox of the use github metrics? and the list_submissions page will not have the content 'Github data' " do | ||
visit "/assignments/#{@assignment.id}/edit" | |||
check('Use github metrics?', allow_label_click: false) | |||
page.uncheck('Use github metrics?') | |||
visit "/assignments/list_submissions?id=#{@assignment.id}" | |||
expect(page).to have_no_content("Github data") | |||
end | end | ||
==Resources== | ==Resources== | ||
=====Key Links===== | =====Key Links===== | ||
[https://github.com/expertiza/expertiza/pull/2158 Pull Request Of E2149] | |||
[https://www.youtube.com/watch?v=8BveAT6ourQ&t=80s demo video Of E2149] | |||
[https://www.youtube.com/watch?v=UiPNnQdWPqo test video Of E2149] | |||
[https://docs.github.com/en/rest Github API Documentation] | [https://docs.github.com/en/rest Github API Documentation] |
Latest revision as of 05:10, 6 December 2021
Abstract
Expertiza provides teammate reviews to measure how much each team member contributed. It also augments this data with Github data of each group’s submitted repo link to help differentiate the performance of team members for grading purposes and predict which projects are likely to be merged. Instructors would like to compile this Github repository statistics on a per-project, per-team basis, and integrate the display of these data within Expertiza. The project team will troubleshoot existing code and add new features.
Problem Statement
The functionality of the showing GitHub metrics cannot be turned off, meaning that an instructor who uses Expertiza for writing assignments in an English course would get a Github metrics report. There needs to be a way to turn the report off for a particular assignment. We will some RSpec test code to test the function.
Team Action Items
Adding turn on and turn off option
The functionality of the current project cannot be turned off. We will provide an option for instructors not to query Github metrics data before they post the assignment, which is the way we turn the GitHub report off for a particular assignment.
Database Modification
In order to determine whether the GitHub report of the assignment is turned on or turned off, we need to create a new table named "github_metric_uses" to save the id of the assignment whose GitHub metric is turned on. We also have an alternative solution to add a boolean field in the assignment table, but due to security concerns, we give it up. We think we should not change the data of the assignment.
Table: github_metric_uses
id(PK) | Assignment_id(FK) |
---|---|
1 | 1 |
2 | 15 |
Frontend Modification
If a user chooses not to include the GitHub metric in the assignment (the functionality is turned off), the icon named "Login to Query of Github data" should not be displayed on the frontend page.
1. We edited expertiza\app\views\assignments\edit\_general.html.erb and added a checkbox. The codes are as follow. If the id of the assignment exists, there will be a checkbox for the user to use github metrics. The GithubMetricUsesController.exist use the class method in the controller.
<input type="checkbox" name = "github_use" id="github_use" <%= "checked" if GithubMetricUsesController.exist(@assignment_form.assignment.id) %>></input> <%= label_tag('github_use', 'Use github metrics?') %>
2. The expertiza\app\views\assignments\list_submissions.html.erb is edited too. If the id of the assignment exists and github_access_token is null (it means that the user hasn't logged in), then the page will show 'Login to Query Github data' button. If github_access_token is not null, it would show 'Refresh Github data' button because the user has logged in. <%if GithubMetricUsesController.exist(@assignment.id) %> <% if session["github_access_token"].nil? %> <% topic_identifier, topic_name, users_for_curr_team, participants = get_data_for_list_submissions(@teams.first) %> <%= link_to 'Login to Query Github data', { controller: 'metrics', action: 'show', id: participants.first.id } %> <% else %> <%= link_to 'Refresh Github data', { controller: 'metrics', action: 'query_assignment_statistics', id: @assignment.id}, :onclick => "showLoadIcon()"%> <img id="load_icon" src="<%= image_url('ajax-loader.gif') %>" style="display:none"> <% end %> <% end %> 3. Post the assignment id if the user selects the 'check' button and delete the data if the user cancels it by url. The function uses the instance method in controller. jQuery(document).ready(function () { // This function determines whether the 'Topics' tab must be displayed when the page is re-loaded var checkbox = jQuery('#assignment_has_topics'); if (checkbox.is(':checked')) { // If this checkbox is checked, show the 'Topics' tab jQuery("#topics_tab_header").show(); } else { // Otherwise, hide topics tab jQuery("#topics_tab_header").hide(); } $('#github_use').change(function () { if (jQuery('#github_use').is(':checked')) { jQuery.ajax({ url: '/github_metric_uses/' + <%= @assignment.id %>, method: 'POST', xhrFields: { withCredentials: true }, dataType: 'json' }) } else{ jQuery.ajax({ url: '/github_metric_uses/' + <%= @assignment.id %>, method: 'DELETE', xhrFields: { withCredentials: true }, dataType: 'json' }) } }) });
Controller Modification
We added a function in expertiza\app\controllers\github_metric_uses_controller.rb as follwowing.
Class Methods
1. Check if assignment_id exists in the table github_metric_uses. If so, return TRUE.
#check if assignment_id exists in the table github_metric_uses def self.exist(assignment_id) github_use = GithubMetricUses.find_by(assignment_id: assignment_id) !github_use.nil? end
2. If assignment_id does not exist in the table github_metric_uses, save it.
#if assignment_id does not exist in the table github_metric_uses, save it def self.save(assignment_id) unless exist(assignment_id) github_use = GithubMetricUses.new(assignment_id) github_use.assignment_id = assignment_id github_use.save end end
3. If assignment_id exists in the table github_metric_uses, delete it
#if assignment_id exists in the table github_metric_uses, delete it def self.delete(assignment_id) if exist(assignment_id) github_use = GithubMetricUses.find_by(assignment_id: assignment_id) github_use.destroy end end
Instance Methods
URL in frontend uses such functions.
1. Return TRUE if the assignment id exists.
#check if assignment_id exists in the table github_metric_uses def exist assignment_id = params[:assignment_id] GithubMetricUsesController.exist(assignment_id) end
2. Save assignment id if it doesn't exist. This function is for the frontend page to select the checkbox.
#if assignment_id does not exist in the table github_metric_uses, save it def save assignment_id = params[:assignment_id] GithubMetricUsesController.save(assignment_id) respond_to do |format| format.json { render json: assignment_id } end end
3. Delete assignment id if it exists. This function is for the frontend page to cancel the checkbox.
#if assignment_id exists in the table github_metric_uses, delete it def delete assignment_id = params[:assignment_id] GithubMetricUsesController.delete(assignment_id) respond_to do |format| format.json { render json: assignment_id } end end
Testing
The details are in the Test Plan [1].
Functionality Walk Through
Assignments could be edited on the assignment viewing page. Also, the submissions would be showed and there would be differences when "Github metrics" is chosen or is not chosen.
Assignment edit page view
An instructor can customize an assignment in this view. We will provide an option for instructors to decide whether they need Github metrics in the assignment or not.
Before we modified this part, this page is as follows:
After modifying it, this page is:
List Submissions View: with Github metrics option
This is how a user will view List Submissions when they choose Github metrics in the previous assignment edit page and is not authenticated to Github yet. The Login link redirects to an omniauth login page, which redirects back to this page.
List Submissions View: without Github metrics option
This is how a user will view List Submissions when they did not choose Github metrics in the previous assignment edit page. The Login link for querying Github data would not be provided to the instructors if they don't choose the "use GitHub metric" option.
Final Design UML
This UML shows the new classes and models we created and implemented in our final project. Besides these, we also modified some of the existing classes and methods, we will give more detailed information later.
OmniAuth Github API update
The Github API requires users to be authenticated to query the API. Since GitHub no longer supports authentication through query parameters, we move the authentication to the header. From September 8, 2021, using access_token as a query parameter to access the API (as a user or as a GitHub App) and communication between the Expertiza server and Github, and using client_id/client_secret to make OAuth app unauthenticated are disabled.
The callback URL (the URL of the Expertiza server) is set within Github.com, where the OAuth app is configured (and Client Keys/Secrets are generated). The code from the previous project uses Client Key/Secret pairs that are tied to a Github OAuth app that is configured to callback to http://localhost:3000/auth/github/callback. Naturally, when Expertiza is running in a production environment, or a deployment on a remote server where the client is not on the localhost, this callback will fail to redirect.
Therefore, we create a new OAuth app using a Github.com account that configures a callback URL to http://server_ip_or_url:port/auth/github2021/callback/. The production callback URL is: https://expertiza.ncsu.edu/auth/github2021/callback. The reason "github2021" is used for this project is due to Github's 2021 updates to their security infrastructure -- our code handles "github" and "github2021" callbacks differently to match these security updates.
GitHub's OAuth implementation
#Use Oauth protocols to attempt to authorize user for either google or github def oauth_login case params[:provider] when "github" github_login when "google_oauth2" google_login when "github2021" # due to github https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/ custom_github_login else ExpertizaLogger.error LoggerMessage.new(controller_name, user.name, "Invalid OAuth Provider", "") end end
After requesting the user's GitHub identity, the user will be redirected back to Expertiza by GitHub. If the user accepts the request, GitHub redirects back to expertiza with a temporary code in a code parameter and the state provided in the previous step in a state parameter. The client_id and client_secret are required as they received from GitHub for Expertiza. The code is also required as it would be a response to the request. Then the access token allows Expertiza to make requests to the API on a behalf of a user.
#Login functionality for Github to get access_token def custom_github_login session_code = request.env['rack.request.query_hash']['code'] result = RestClient.post('https://github.com/login/oauth/access_token', {:client_id => GITHUB_CONFIG['client_key'], :client_secret => GITHUB_CONFIG['client_secret'], :code => session_code}, :accept => :json) access_token = JSON.parse(result)['access_token'] session["github_access_token"] = access_token redirect_to controller: 'assignments', action: 'list_submissions', id: session["assignment_id"] end
Move the authentication to the header
def query_commit_statistics(data) uri = URI.parse("https://api.github.com/graphql") http = Net::HTTP.new(uri.host, uri.port) # host: api.github.com, port: 443 http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER # request = Net::HTTP::Post.new(uri.path, 'Authorization' => 'Bearer' + ' ' + session["github_access_token"]) # set up authorization request = Net::HTTP::Post.new(uri.path) # set up authorization # send the token in the header request['Authorization'] = 'Bearer' + ' ' + session["github_access_token"] request.body = data.to_json # convert query message to json and pass as request body response = http.request(request) # make the actual request ActiveSupport::JSON.decode(response.body.to_s) # convert the response body to string, decoded then return end
Test Plan
UI Testing
Our UI tests aim to capture the following core pieces of functionality:
Turn on and turn off functionality button:
In order to test the functionality manually, we follow the following steps:
- 1. Log in to Expertiza as an instructor
- 2. Navigate to assignments through Manage
- 3. Show a turn on/turn down option for Github metric on the page
- 4. Process the page without choosing Github metrics report for a particular assignment
- 5. The page should not display "Login to Query of Github data" options.
Rspec
Detailed of Using Github Testing first, go to the assignment edit page and check the checkbox of use github metric, then go to the list submission page check the page if has the content about ithub data.
it "check the box of the use github metrics? and the list_submissions page will have the content 'Github data' " do visit "/assignments/#{@assignment.id}/edit" check('Use github metrics?', allow_label_click: true) visit "/assignments/list_submissions?id=#{@assignment.id}" expect(page).to have_content("Github data") end
it "uncheck the checkbox of the use github metrics? and the list_submissions page will not have the content 'Github data' " do visit "/assignments/#{@assignment.id}/edit" check('Use github metrics?', allow_label_click: false) page.uncheck('Use github metrics?') visit "/assignments/list_submissions?id=#{@assignment.id}" expect(page).to have_no_content("Github data") end
Resources
Key Links
Deprecating API authentication through query parameters