<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.expertiza.ncsu.edu/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Asubram9</id>
	<title>Expertiza_Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.expertiza.ncsu.edu/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Asubram9"/>
	<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=Special:Contributions/Asubram9"/>
	<updated>2026-07-18T06:27:34Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.0</generator>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150227</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150227"/>
		<updated>2023-04-26T03:48:58Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Note to the reviwer for final submission==&lt;br /&gt;
* This project is a refactoring of some files done in Project 3. We have made these changes in a separate repo so that the new changes can be seen clearly.&lt;br /&gt;
&lt;br /&gt;
* We have added a new section in this documentation called '''Changes made / Solutions''' that has all the changes we have made and explanations. &lt;br /&gt;
&lt;br /&gt;
'''PR for this project:''' https://github.com/expertiza/reimplementation-back-end/pull/38&lt;br /&gt;
&lt;br /&gt;
'''The PR for the previous project (Project 3) on top of which these changes have been made:''' https://github.com/expertiza/reimplementation-back-end/pull/23&lt;br /&gt;
&lt;br /&gt;
* Check the beginning of the test plan section on how to test the functionality.&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments belongs in assignment.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''assignment.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
'''Update for final submission:''' We have decided not to do this since the repeated code was only of 2 lines and that is too small to be considered not DRY.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
'''Update for final submission:''' We have decided not to do this since the repeated code was only of 2 lines and that is too small to be considered not DRY.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Changes made / Solutions==&lt;br /&gt;
&lt;br /&gt;
===Issue 1===&lt;br /&gt;
The get_all_review_comments method has been moved to be an instance method of the assignment class and modified to only take the reviewee_id. However, the return value has not been modified. This is because upon further investigation the get_all_review_comments (previously called concatenate_all_review_comments) is only used within the volume_of_review_comments method and that modifying volume_of_review_comments to only get a single variable from get_all_review_comments proved to difficult. Unlike the avg_scores_and_count_for_prev_reviews, however, get_all_review_comments was not merged into get_all_review_comments due to its size and that doing so would make get_all_review_comments difficult to understand.&lt;br /&gt;
&lt;br /&gt;
===Issue 2===&lt;br /&gt;
The avg_scores_and_count_for_prev_reviews method has been merged with the significant_difference? method.&lt;br /&gt;
&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    # gets all responses made by a reviewee&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    #&lt;br /&gt;
    count = 0&lt;br /&gt;
    total = 0&lt;br /&gt;
    # gets the sum total percentage scores of all responses that are not this response&lt;br /&gt;
    existing_responses.each do |response|&lt;br /&gt;
      unless id == response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        total +=  response.aggregate_questionnaire_score.to_f / response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    #&lt;br /&gt;
    # if this response is the only response by the reviewee, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    #&lt;br /&gt;
    # calculates the average score of all other responses&lt;br /&gt;
    average_score = total / count&lt;br /&gt;
    #&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    #&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    #&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3===&lt;br /&gt;
We have made the volume_of_review_comments method to be an instance method and moved it to the assignment.rb since it used to take a 'assignment_id' as an input argument. In this way, we are able to make it more sense and removed the ReviewCommentMixin.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def volume_of_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      comments, counter,&lt;br /&gt;
        @comments_in_round, @counter_in_round = Response.get_all_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      # Index 0 is a nil element that can be ignored in the round count&lt;br /&gt;
      num_rounds = @comments_in_round.count - 1&lt;br /&gt;
  &lt;br /&gt;
      overall_avg_vol = (Lingua::EN::Readability.new(comments).num_words / (counter.zero? ? 1 : counter)).round(0)&lt;br /&gt;
      review_comments_volume = []&lt;br /&gt;
      review_comments_volume.push(overall_avg_vol)&lt;br /&gt;
      (1..num_rounds).each do |round|&lt;br /&gt;
        num = Lingua::EN::Readability.new(@comments_in_round[round]).num_words&lt;br /&gt;
        den = (@counter_in_round[round].zero? ? 1 : @counter_in_round[round])&lt;br /&gt;
        avg_vol_in_round = (num / den).round(0)&lt;br /&gt;
        review_comments_volume.push(avg_vol_in_round)&lt;br /&gt;
      end&lt;br /&gt;
      review_comments_volume&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4===&lt;br /&gt;
* 1) We have decided not to address this part of the issue (See the Issue 4) after discussing with the professor. The reason behind it is that the lines involved in this section is only 2 lines long and hence it does not have to be moved to a method.&lt;br /&gt;
&lt;br /&gt;
* 2) We looked at a lot of solutions to make this. The issue is that using just a 'where' method will not ensure that the Active Record Association returned will be in the same order of that of the list of ids given.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  ids = [4,1,2,7]&lt;br /&gt;
  answers = Answer.where(id: ids)&lt;br /&gt;
  return answers.map(&amp;amp;:id)&lt;br /&gt;
&lt;br /&gt;
This example snippet would return 1,2,4,7&lt;br /&gt;
&lt;br /&gt;
Hence, to ensure the order of the return values(thereby ensuring the correct working) and to improve the performance, we have used a small gem 'find_with_order'. The main reason why we decided to add this gem over a custom solution is portability / ease of modification. A custom solution would have required us to write SQL queries in where clauses and this would be tied to the mysql(current database used). This gem is compatible with popular databases and hence this would be automatically taken care of even if the database is changed.&lt;br /&gt;
&lt;br /&gt;
Hence, our final solution is:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def calculate_total_score&lt;br /&gt;
      # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
      # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
      sum = 0&lt;br /&gt;
      question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
      # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
      questions = Question.find_with_order(question_ids)&lt;br /&gt;
      scores.each_with_index do |score, idx|&lt;br /&gt;
        sum += score.answer * questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
      end&lt;br /&gt;
      sum&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def maximum_score&lt;br /&gt;
    # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
    # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
    total_weight = 0&lt;br /&gt;
    question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
    # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
    questions = Question.find_with_order(question_ids)&lt;br /&gt;
    scores.each_with_index do |score, idx|&lt;br /&gt;
      total_weight += questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire = if scores.empty?&lt;br /&gt;
                      questionnaire_by_answer(nil)&lt;br /&gt;
                    else&lt;br /&gt;
                      questionnaire_by_answer(scores.first)&lt;br /&gt;
                    end&lt;br /&gt;
    total_weight * questionnaire.max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
* 3) Usage of visitor pattern to avoid checking class types: We solved this by adding a method '''response_assignment''' to ReviewResponseMap and ResponseMap classes.&lt;br /&gt;
&lt;br /&gt;
In response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return Participant.find(self.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
In review_response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the review response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return self.assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Hence, the '''questionnaire_by_answer''' method in '''ScorableHelper''' will have the following snippet:&lt;br /&gt;
&lt;br /&gt;
      map = ResponseMap.find(map_id)&lt;br /&gt;
      assignment = map.response_assignment&lt;br /&gt;
&lt;br /&gt;
===Issue 5===&lt;br /&gt;
The get_all_responses method has been made an instance method of response_map and simplified as the function no longer need to find a response map.&lt;br /&gt;
&lt;br /&gt;
  def get_all_responses&lt;br /&gt;
    Response.where(map_id: map_id).all&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 6===&lt;br /&gt;
Additional comments have been made to the tests for the response class.&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
Our is a refactor project for models. Hence, to test the functionality, we have ensured to cover existing test cases and add new test cases for any new code we have added.&lt;br /&gt;
&lt;br /&gt;
To run rspec for this project,&lt;br /&gt;
&lt;br /&gt;
 rspec spec/models/response_spec.rb&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150204</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150204"/>
		<updated>2023-04-26T01:23:07Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Note to the reviwer for final submission==&lt;br /&gt;
* This project is a refactoring of some files done in Project 3. We have made these changes in a separate repo so that the new changes can be seen clearly.&lt;br /&gt;
&lt;br /&gt;
* We have added a new section in this documentation called '''Changes made / Solutions''' that has all the changes we have made and explanations. &lt;br /&gt;
&lt;br /&gt;
'''PR for this project:''' https://github.com/expertiza/reimplementation-back-end/pull/38&lt;br /&gt;
&lt;br /&gt;
'''The PR for the previous project (Project 3) on top of which these changes have been made:''' https://github.com/expertiza/reimplementation-back-end/pull/23&lt;br /&gt;
&lt;br /&gt;
* Check the beginning of the test plan section on how to test the functionality.&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments belongs in assignment.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''assignment.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
'''Update for final submission:''' We have decided not to do this since the repeated code was only of 2 lines and that is too small to be considered not DRY.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
'''Update for final submission:''' We have decided not to do this since the repeated code was only of 2 lines and that is too small to be considered not DRY.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Changes made / Solutions==&lt;br /&gt;
&lt;br /&gt;
===Issue 1===&lt;br /&gt;
The get_all_review_comments method has been moved to be an instance method of the assignment class and modified to only take the reviewee_id. However, the return value has not been modified. This is because upon further investigation the get_all_review_comments (previously called concatenate_all_review_comments) is only used within the volume_of_review_comments method and that modifying volume_of_review_comments to only get a single variable from get_all_review_comments proved to difficult. Unlike the avg_scores_and_count_for_prev_reviews, however, get_all_review_comments was not merged into get_all_review_comments due to its size and that doing so would make get_all_review_comments difficult to understand.&lt;br /&gt;
&lt;br /&gt;
===Issue 2===&lt;br /&gt;
The avg_scores_and_count_for_prev_reviews method has been merged with the significant_difference? method.&lt;br /&gt;
&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    # gets all responses made by a reviewee&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    #&lt;br /&gt;
    count = 0&lt;br /&gt;
    total = 0&lt;br /&gt;
    # gets the sum total percentage scores of all responses that are not this response&lt;br /&gt;
    existing_responses.each do |response|&lt;br /&gt;
      unless id == response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        total +=  response.aggregate_questionnaire_score.to_f / response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    #&lt;br /&gt;
    # if this response is the only response by the reviewee, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    #&lt;br /&gt;
    # calculates the average score of all other responses&lt;br /&gt;
    average_score = total / count&lt;br /&gt;
    #&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    #&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    #&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3===&lt;br /&gt;
We have made the volume_of_review_comments method to be an instance method and moved it to the assignment.rb since it used to take a 'assignment_id' as an input argument. In this way, we are able to make it more sense and removed the ReviewCommentMixin.&lt;br /&gt;
&lt;br /&gt;
  # frozen_string_literal: true&lt;br /&gt;
  module MetricHelper&lt;br /&gt;
    # Determine the average size of review comments in each round&lt;br /&gt;
    # the first entry in the returned list is the overall average&lt;br /&gt;
    # word count.&lt;br /&gt;
    def volume_of_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      comments, counter,&lt;br /&gt;
        @comments_in_round, @counter_in_round = Response.get_all_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      # Index 0 is a nil element that can be ignored in the round count&lt;br /&gt;
      num_rounds = @comments_in_round.count - 1&lt;br /&gt;
  &lt;br /&gt;
      overall_avg_vol = (Lingua::EN::Readability.new(comments).num_words / (counter.zero? ? 1 : counter)).round(0)&lt;br /&gt;
      review_comments_volume = []&lt;br /&gt;
      review_comments_volume.push(overall_avg_vol)&lt;br /&gt;
      (1..num_rounds).each do |round|&lt;br /&gt;
        num = Lingua::EN::Readability.new(@comments_in_round[round]).num_words&lt;br /&gt;
        den = (@counter_in_round[round].zero? ? 1 : @counter_in_round[round])&lt;br /&gt;
        avg_vol_in_round = (num / den).round(0)&lt;br /&gt;
        review_comments_volume.push(avg_vol_in_round)&lt;br /&gt;
      end&lt;br /&gt;
      review_comments_volume&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 4===&lt;br /&gt;
* 1) We have decided not to address this part of the issue (See the Issue 4) after discussing with the professor. The reason behind it is that the lines involved in this section is only 2 lines long and hence it does not have to be moved to a method.&lt;br /&gt;
&lt;br /&gt;
* 2) We looked at a lot of solutions to make this. The issue is that using just a 'where' method will not ensure that the Active Record Association returned will be in the same order of that of the list of ids given.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  ids = [4,1,2,7]&lt;br /&gt;
  answers = Answer.where(id: ids)&lt;br /&gt;
  return answers.map(&amp;amp;:id)&lt;br /&gt;
&lt;br /&gt;
This example snippet would return 1,2,4,7&lt;br /&gt;
&lt;br /&gt;
Hence, to ensure the order of the return values(thereby ensuring the correct working) and to improve the performance, we have used a small gem 'find_with_order'. The main reason why we decided to add this gem over a custom solution is portability / ease of modification. A custom solution would have required us to write SQL queries in where clauses and this would be tied to the mysql(current database used). This gem is compatible with popular databases and hence this would be automatically taken care of even if the database is changed.&lt;br /&gt;
&lt;br /&gt;
Hence, our final solution is:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def calculate_total_score&lt;br /&gt;
      # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
      # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
      sum = 0&lt;br /&gt;
      question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
      # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
      questions = Question.find_with_order(question_ids)&lt;br /&gt;
      scores.each_with_index do |score, idx|&lt;br /&gt;
        sum += score.answer * questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
      end&lt;br /&gt;
      sum&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def maximum_score&lt;br /&gt;
    # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
    # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
    total_weight = 0&lt;br /&gt;
    question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
    # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
    questions = Question.find_with_order(question_ids)&lt;br /&gt;
    scores.each_with_index do |score, idx|&lt;br /&gt;
      total_weight += questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire = if scores.empty?&lt;br /&gt;
                      questionnaire_by_answer(nil)&lt;br /&gt;
                    else&lt;br /&gt;
                      questionnaire_by_answer(scores.first)&lt;br /&gt;
                    end&lt;br /&gt;
    total_weight * questionnaire.max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
* 3) Usage of visitor pattern to avoid checking class types: We solved this by adding a method '''response_assignment''' to ReviewResponseMap and ResponseMap classes.&lt;br /&gt;
&lt;br /&gt;
In response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return Participant.find(self.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
In review_response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the review response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return self.assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Hence, the '''questionnaire_by_answer''' method in '''ScorableHelper''' will have the following snippet:&lt;br /&gt;
&lt;br /&gt;
      map = ResponseMap.find(map_id)&lt;br /&gt;
      assignment = map.response_assignment&lt;br /&gt;
&lt;br /&gt;
===Issue 5===&lt;br /&gt;
The get_all_responses method has been made an instance method of response_map and simplified as the function no longer need to find a response map.&lt;br /&gt;
&lt;br /&gt;
  def get_all_responses&lt;br /&gt;
    Response.where(map_id: map_id).all&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 6===&lt;br /&gt;
Additional comments have been made to the tests for the response class.&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
Our is a refactor project for models. Hence, to test the functionality, we have ensured to cover existing test cases and add new test cases for any new code we have added.&lt;br /&gt;
&lt;br /&gt;
To run rspec for this project,&lt;br /&gt;
&lt;br /&gt;
 rspec spec/models/response_spec.rb&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150200</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150200"/>
		<updated>2023-04-26T01:16:28Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Note to the reviwer for final submission==&lt;br /&gt;
* This project is a refactoring of some files done in Project 3. We have made these changes in a separate repo so that the new changes can be seen clearly.&lt;br /&gt;
&lt;br /&gt;
* We have added a new section in this documentation called '''Changes made / Solutions''' that has all the changes we have made and explanations. &lt;br /&gt;
&lt;br /&gt;
'''PR for this project:''' https://github.com/expertiza/reimplementation-back-end/pull/38&lt;br /&gt;
&lt;br /&gt;
'''The PR for the previous project (Project 3) on top of which these changes have been made:''' https://github.com/expertiza/reimplementation-back-end/pull/23&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments belongs in assignment.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''assignment.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
'''Update for final submission:''' We have decided not to do this since the repeated code was only of 2 lines and that is too small to be considered not DRY.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
'''Update for final submission:''' We have decided not to do this since the repeated code was only of 2 lines and that is too small to be considered not DRY.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Changes made / Solutions==&lt;br /&gt;
&lt;br /&gt;
===Issue 1===&lt;br /&gt;
The get_all_review_comments method has been moved to be an instance method of the assignment class and modified to only take the reviewee_id. However, the return value has not been modified. This is because upon further investigation the get_all_review_comments (previously called concatenate_all_review_comments) is only used within the volume_of_review_comments method and that modifying volume_of_review_comments to only get a single variable from get_all_review_comments proved to difficult. Unlike the avg_scores_and_count_for_prev_reviews, however, get_all_review_comments was not merged into get_all_review_comments due to its size and that doing so would make get_all_review_comments difficult to understand.&lt;br /&gt;
&lt;br /&gt;
===Issue 2===&lt;br /&gt;
The avg_scores_and_count_for_prev_reviews method has been merged with the significant_difference? method.&lt;br /&gt;
&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    # gets all responses made by a reviewee&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    #&lt;br /&gt;
    count = 0&lt;br /&gt;
    total = 0&lt;br /&gt;
    # gets the sum total percentage scores of all responses that are not this response&lt;br /&gt;
    existing_responses.each do |response|&lt;br /&gt;
      unless id == response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        total +=  response.aggregate_questionnaire_score.to_f / response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    #&lt;br /&gt;
    # if this response is the only response by the reviewee, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    #&lt;br /&gt;
    # calculates the average score of all other responses&lt;br /&gt;
    average_score = total / count&lt;br /&gt;
    #&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    #&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    #&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3===&lt;br /&gt;
We have made the volume_of_review_comments method to be an instance method and moved it to the assignment.rb since it used to take a 'assignment_id' as an input argument. In this way, we are able to make it more sense and removed the ReviewCommentMixin.&lt;br /&gt;
&lt;br /&gt;
  # frozen_string_literal: true&lt;br /&gt;
  module MetricHelper&lt;br /&gt;
    # Determine the average size of review comments in each round&lt;br /&gt;
    # the first entry in the returned list is the overall average&lt;br /&gt;
    # word count.&lt;br /&gt;
    def volume_of_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      comments, counter,&lt;br /&gt;
        @comments_in_round, @counter_in_round = Response.get_all_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      # Index 0 is a nil element that can be ignored in the round count&lt;br /&gt;
      num_rounds = @comments_in_round.count - 1&lt;br /&gt;
  &lt;br /&gt;
      overall_avg_vol = (Lingua::EN::Readability.new(comments).num_words / (counter.zero? ? 1 : counter)).round(0)&lt;br /&gt;
      review_comments_volume = []&lt;br /&gt;
      review_comments_volume.push(overall_avg_vol)&lt;br /&gt;
      (1..num_rounds).each do |round|&lt;br /&gt;
        num = Lingua::EN::Readability.new(@comments_in_round[round]).num_words&lt;br /&gt;
        den = (@counter_in_round[round].zero? ? 1 : @counter_in_round[round])&lt;br /&gt;
        avg_vol_in_round = (num / den).round(0)&lt;br /&gt;
        review_comments_volume.push(avg_vol_in_round)&lt;br /&gt;
      end&lt;br /&gt;
      review_comments_volume&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 4===&lt;br /&gt;
* 1) We have decided not to address this part of the issue (See the Issue 4) after discussing with the professor. The reason behind it is that the lines involved in this section is only 2 lines long and hence it does not have to be moved to a method.&lt;br /&gt;
&lt;br /&gt;
* 2) We looked at a lot of solutions to make this. The issue is that using just a 'where' method will not ensure that the Active Record Association returned will be in the same order of that of the list of ids given.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  ids = [4,1,2,7]&lt;br /&gt;
  answers = Answer.where(id: ids)&lt;br /&gt;
  return answers.map(&amp;amp;:id)&lt;br /&gt;
&lt;br /&gt;
This example snippet would return 1,2,4,7&lt;br /&gt;
&lt;br /&gt;
Hence, to ensure the order of the return values(thereby ensuring the correct working) and to improve the performance, we have used a small gem 'find_with_order'. The main reason why we decided to add this gem over a custom solution is portability / ease of modification. A custom solution would have required us to write SQL queries in where clauses and this would be tied to the mysql(current database used). This gem is compatible with popular databases and hence this would be automatically taken care of even if the database is changed.&lt;br /&gt;
&lt;br /&gt;
Hence, our final solution is:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def calculate_total_score&lt;br /&gt;
      # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
      # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
      sum = 0&lt;br /&gt;
      question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
      # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
      questions = Question.find_with_order(question_ids)&lt;br /&gt;
      scores.each_with_index do |score, idx|&lt;br /&gt;
        sum += score.answer * questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
      end&lt;br /&gt;
      sum&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def maximum_score&lt;br /&gt;
    # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
    # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
    total_weight = 0&lt;br /&gt;
    question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
    # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
    questions = Question.find_with_order(question_ids)&lt;br /&gt;
    scores.each_with_index do |score, idx|&lt;br /&gt;
      total_weight += questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire = if scores.empty?&lt;br /&gt;
                      questionnaire_by_answer(nil)&lt;br /&gt;
                    else&lt;br /&gt;
                      questionnaire_by_answer(scores.first)&lt;br /&gt;
                    end&lt;br /&gt;
    total_weight * questionnaire.max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
* 3) Usage of visitor pattern to avoid checking class types: We solved this by adding a method '''response_assignment''' to ReviewResponseMap and ResponseMap classes.&lt;br /&gt;
&lt;br /&gt;
In response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return Participant.find(self.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
In review_response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the review response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return self.assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Hence, the '''questionnaire_by_answer''' method in '''ScorableHelper''' will have the following snippet:&lt;br /&gt;
&lt;br /&gt;
      map = ResponseMap.find(map_id)&lt;br /&gt;
      assignment = map.response_assignment&lt;br /&gt;
&lt;br /&gt;
===Issue 5===&lt;br /&gt;
The get_all_responses method has been made an instance method of response_map and simplified as the function no longer need to find a response map.&lt;br /&gt;
&lt;br /&gt;
  def get_all_responses&lt;br /&gt;
    Response.where(map_id: map_id).all&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 6===&lt;br /&gt;
Additional comments have been made to the tests for the response class.&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150141</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150141"/>
		<updated>2023-04-25T18:40:25Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Note to the reviwer for final submission==&lt;br /&gt;
* This project is a refactoring of some files done in Project 3. We have made these changes in a separate repo so that the new changes can be seen clearly.&lt;br /&gt;
&lt;br /&gt;
* We have added a new section in this documentation called '''Changes made / Solutions''' that has all the changes we have made and explanations. &lt;br /&gt;
&lt;br /&gt;
'''PR for this project:''' https://github.com/expertiza/reimplementation-back-end/pull/38&lt;br /&gt;
&lt;br /&gt;
'''The PR for the previous project (Project 3) on top of which these changes have been made:''' https://github.com/expertiza/reimplementation-back-end/pull/23&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Changes made / Solutions==&lt;br /&gt;
===Issue 3 solved===&lt;br /&gt;
We have made the volume_of_review_comments method to be an instance method. We have also created a module called MetricHelper to make more sense and removed the ReviewCommentMixin&lt;br /&gt;
&lt;br /&gt;
  # frozen_string_literal: true&lt;br /&gt;
  module MetricHelper&lt;br /&gt;
    # Determine the average size of review comments in each round&lt;br /&gt;
    # the first entry in the returned list is the overall average&lt;br /&gt;
    # word count.&lt;br /&gt;
    def volume_of_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      comments, counter,&lt;br /&gt;
        @comments_in_round, @counter_in_round = Response.get_all_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      # Index 0 is a nil element that can be ignored in the round count&lt;br /&gt;
      num_rounds = @comments_in_round.count - 1&lt;br /&gt;
  &lt;br /&gt;
      overall_avg_vol = (Lingua::EN::Readability.new(comments).num_words / (counter.zero? ? 1 : counter)).round(0)&lt;br /&gt;
      review_comments_volume = []&lt;br /&gt;
      review_comments_volume.push(overall_avg_vol)&lt;br /&gt;
      (1..num_rounds).each do |round|&lt;br /&gt;
        num = Lingua::EN::Readability.new(@comments_in_round[round]).num_words&lt;br /&gt;
        den = (@counter_in_round[round].zero? ? 1 : @counter_in_round[round])&lt;br /&gt;
        avg_vol_in_round = (num / den).round(0)&lt;br /&gt;
        review_comments_volume.push(avg_vol_in_round)&lt;br /&gt;
      end&lt;br /&gt;
      review_comments_volume&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4 solved===&lt;br /&gt;
* 1) We have decided not to address this part of the issue (See the Issue 4) after discussing with the professor. The reason behind it is that the lines involved in this section is only 2 lines long and hence it does not have to be moved to a method.&lt;br /&gt;
&lt;br /&gt;
* 2) We looked at a lot of solutions to make this. The issue is that using just a 'where' method will not ensure that the Active Record Association returned will be in the same order of that of the list of ids given.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  ids = [4,1,2,7]&lt;br /&gt;
  answers = Answer.where(id: ids)&lt;br /&gt;
  return answers.map(&amp;amp;:id)&lt;br /&gt;
&lt;br /&gt;
This example snippet would return 1,2,4,7&lt;br /&gt;
&lt;br /&gt;
Hence, to ensure the order of the return values(thereby ensuring the correct working) and to improve the performance, we have used a small gem 'find_with_order'. The main reason why we decided to add this gem over a custom solution is portability / ease of modification. A custom solution would have required us to write SQL queries in where clauses and this would be tied to the mysql(current database used). This gem is compatible with popular databases and hence this would be automatically taken care of even if the database is changed.&lt;br /&gt;
&lt;br /&gt;
Hence, our final solution is:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def calculate_total_score&lt;br /&gt;
      # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
      # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
      sum = 0&lt;br /&gt;
      question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
      # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
      questions = Question.find_with_order(question_ids)&lt;br /&gt;
      scores.each_with_index do |score, idx|&lt;br /&gt;
        sum += score.answer * questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
      end&lt;br /&gt;
      sum&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def maximum_score&lt;br /&gt;
    # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
    # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
    total_weight = 0&lt;br /&gt;
    question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
    # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
    questions = Question.find_with_order(question_ids)&lt;br /&gt;
    scores.each_with_index do |score, idx|&lt;br /&gt;
      total_weight += questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire = if scores.empty?&lt;br /&gt;
                      questionnaire_by_answer(nil)&lt;br /&gt;
                    else&lt;br /&gt;
                      questionnaire_by_answer(scores.first)&lt;br /&gt;
                    end&lt;br /&gt;
    total_weight * questionnaire.max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
* 3) Usage of visitor pattern to avoid checking class types: We solved this by adding a method '''response_assignment''' to ReviewResponseMap and ResponseMap classes.&lt;br /&gt;
&lt;br /&gt;
In response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return Participant.find(self.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
In review_response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the review response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return self.assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Hence, the '''questionnaire_by_answer''' method in '''ScorableHelper''' will have the following snippet:&lt;br /&gt;
&lt;br /&gt;
      map = ResponseMap.find(map_id)&lt;br /&gt;
      assignment = map.response_assignment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150140</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150140"/>
		<updated>2023-04-25T18:39:34Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Note&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Note to the reviwer for final submission==&lt;br /&gt;
* This project is a refactoring of some files done in Project 3. We have made these changes in a separate repo so that the new changes can be seen clearly.&lt;br /&gt;
&lt;br /&gt;
* We have added a new section in this documentation called '''Changes made / Solutions''' that has all the changes we have made and explanations. &lt;br /&gt;
&lt;br /&gt;
'''PR for this project:''' https://github.com/expertiza/reimplementation-back-end/pull/38&lt;br /&gt;
&lt;br /&gt;
'''The PR for the previous project (Project 3) on top of which these changes have been made:''' https://github.com/expertiza/reimplementation-back-end/pull/23&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Changes made / Solutions==&lt;br /&gt;
===Issue 3:===&lt;br /&gt;
We have made the volume_of_review_comments method to be an instance method. We have also created a module called MetricHelper to make more sense and removed the ReviewCommentMixin&lt;br /&gt;
&lt;br /&gt;
  # frozen_string_literal: true&lt;br /&gt;
  module MetricHelper&lt;br /&gt;
    # Determine the average size of review comments in each round&lt;br /&gt;
    # the first entry in the returned list is the overall average&lt;br /&gt;
    # word count.&lt;br /&gt;
    def volume_of_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      comments, counter,&lt;br /&gt;
        @comments_in_round, @counter_in_round = Response.get_all_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      # Index 0 is a nil element that can be ignored in the round count&lt;br /&gt;
      num_rounds = @comments_in_round.count - 1&lt;br /&gt;
  &lt;br /&gt;
      overall_avg_vol = (Lingua::EN::Readability.new(comments).num_words / (counter.zero? ? 1 : counter)).round(0)&lt;br /&gt;
      review_comments_volume = []&lt;br /&gt;
      review_comments_volume.push(overall_avg_vol)&lt;br /&gt;
      (1..num_rounds).each do |round|&lt;br /&gt;
        num = Lingua::EN::Readability.new(@comments_in_round[round]).num_words&lt;br /&gt;
        den = (@counter_in_round[round].zero? ? 1 : @counter_in_round[round])&lt;br /&gt;
        avg_vol_in_round = (num / den).round(0)&lt;br /&gt;
        review_comments_volume.push(avg_vol_in_round)&lt;br /&gt;
      end&lt;br /&gt;
      review_comments_volume&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4:===&lt;br /&gt;
* 1) We have decided not to address this part of the issue (See the Issue 4) after discussing with the professor. The reason behind it is that the lines involved in this section is only 2 lines long and hence it does not have to be moved to a method.&lt;br /&gt;
&lt;br /&gt;
* 2) We looked at a lot of solutions to make this. The issue is that using just a 'where' method will not ensure that the Active Record Association returned will be in the same order of that of the list of ids given.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  ids = [4,1,2,7]&lt;br /&gt;
  answers = Answer.where(id: ids)&lt;br /&gt;
  return answers.map(&amp;amp;:id)&lt;br /&gt;
&lt;br /&gt;
This example snippet would return 1,2,4,7&lt;br /&gt;
&lt;br /&gt;
Hence, to ensure the order of the return values(thereby ensuring the correct working) and to improve the performance, we have used a small gem 'find_with_order'. The main reason why we decided to add this gem over a custom solution is portability / ease of modification. A custom solution would have required us to write SQL queries in where clauses and this would be tied to the mysql(current database used). This gem is compatible with popular databases and hence this would be automatically taken care of even if the database is changed.&lt;br /&gt;
&lt;br /&gt;
Hence, our final solution is:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def calculate_total_score&lt;br /&gt;
      # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
      # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
      sum = 0&lt;br /&gt;
      question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
      # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
      questions = Question.find_with_order(question_ids)&lt;br /&gt;
      scores.each_with_index do |score, idx|&lt;br /&gt;
        sum += score.answer * questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
      end&lt;br /&gt;
      sum&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def maximum_score&lt;br /&gt;
    # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
    # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
    total_weight = 0&lt;br /&gt;
    question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
    # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
    questions = Question.find_with_order(question_ids)&lt;br /&gt;
    scores.each_with_index do |score, idx|&lt;br /&gt;
      total_weight += questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire = if scores.empty?&lt;br /&gt;
                      questionnaire_by_answer(nil)&lt;br /&gt;
                    else&lt;br /&gt;
                      questionnaire_by_answer(scores.first)&lt;br /&gt;
                    end&lt;br /&gt;
    total_weight * questionnaire.max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
* 3) Usage of visitor pattern to avoid checking class types: We solved this by adding a method '''response_assignment''' to ReviewResponseMap and ResponseMap classes.&lt;br /&gt;
&lt;br /&gt;
In response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return Participant.find(self.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
In review_response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the review response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return self.assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Hence, the '''questionnaire_by_answer''' method in '''ScorableHelper''' will have the following snippet:&lt;br /&gt;
&lt;br /&gt;
      map = ResponseMap.find(map_id)&lt;br /&gt;
      assignment = map.response_assignment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150139</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150139"/>
		<updated>2023-04-25T18:34:34Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Update for resubmission==&lt;br /&gt;
We have verified the design doc with the professor and made a few changes:&lt;br /&gt;
* Added function name for issue 4&lt;br /&gt;
* Fixed typos&lt;br /&gt;
* Verified the solutions and visual aid&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Changes made / Solutions==&lt;br /&gt;
===Issue 3:===&lt;br /&gt;
We have made the volume_of_review_comments method to be an instance method. We have also created a module called MetricHelper to make more sense and removed the ReviewCommentMixin&lt;br /&gt;
&lt;br /&gt;
  # frozen_string_literal: true&lt;br /&gt;
  module MetricHelper&lt;br /&gt;
    # Determine the average size of review comments in each round&lt;br /&gt;
    # the first entry in the returned list is the overall average&lt;br /&gt;
    # word count.&lt;br /&gt;
    def volume_of_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      comments, counter,&lt;br /&gt;
        @comments_in_round, @counter_in_round = Response.get_all_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      # Index 0 is a nil element that can be ignored in the round count&lt;br /&gt;
      num_rounds = @comments_in_round.count - 1&lt;br /&gt;
  &lt;br /&gt;
      overall_avg_vol = (Lingua::EN::Readability.new(comments).num_words / (counter.zero? ? 1 : counter)).round(0)&lt;br /&gt;
      review_comments_volume = []&lt;br /&gt;
      review_comments_volume.push(overall_avg_vol)&lt;br /&gt;
      (1..num_rounds).each do |round|&lt;br /&gt;
        num = Lingua::EN::Readability.new(@comments_in_round[round]).num_words&lt;br /&gt;
        den = (@counter_in_round[round].zero? ? 1 : @counter_in_round[round])&lt;br /&gt;
        avg_vol_in_round = (num / den).round(0)&lt;br /&gt;
        review_comments_volume.push(avg_vol_in_round)&lt;br /&gt;
      end&lt;br /&gt;
      review_comments_volume&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4:===&lt;br /&gt;
* 1) We have decided not to address this part of the issue (See the Issue 4) after discussing with the professor. The reason behind it is that the lines involved in this section is only 2 lines long and hence it does not have to be moved to a method.&lt;br /&gt;
&lt;br /&gt;
* 2) We looked at a lot of solutions to make this. The issue is that using just a 'where' method will not ensure that the Active Record Association returned will be in the same order of that of the list of ids given.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
  ids = [4,1,2,7]&lt;br /&gt;
  answers = Answer.where(id: ids)&lt;br /&gt;
  return answers.map(&amp;amp;:id)&lt;br /&gt;
&lt;br /&gt;
This example snippet would return 1,2,4,7&lt;br /&gt;
&lt;br /&gt;
Hence, to ensure the order of the return values(thereby ensuring the correct working) and to improve the performance, we have used a small gem 'find_with_order'. The main reason why we decided to add this gem over a custom solution is portability / ease of modification. A custom solution would have required us to write SQL queries in where clauses and this would be tied to the mysql(current database used). This gem is compatible with popular databases and hence this would be automatically taken care of even if the database is changed.&lt;br /&gt;
&lt;br /&gt;
Hence, our final solution is:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def calculate_total_score&lt;br /&gt;
      # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
      # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
      sum = 0&lt;br /&gt;
      question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
      # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
      questions = Question.find_with_order(question_ids)&lt;br /&gt;
      scores.each_with_index do |score, idx|&lt;br /&gt;
        sum += score.answer * questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
      end&lt;br /&gt;
      sum&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def maximum_score&lt;br /&gt;
    # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
    # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
    total_weight = 0&lt;br /&gt;
    question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
    # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
    questions = Question.find_with_order(question_ids)&lt;br /&gt;
    scores.each_with_index do |score, idx|&lt;br /&gt;
      total_weight += questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire = if scores.empty?&lt;br /&gt;
                      questionnaire_by_answer(nil)&lt;br /&gt;
                    else&lt;br /&gt;
                      questionnaire_by_answer(scores.first)&lt;br /&gt;
                    end&lt;br /&gt;
    total_weight * questionnaire.max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
* 3) Usage of visitor pattern to avoid checking class types: We solved this by adding a method '''response_assignment''' to ReviewResponseMap and ResponseMap classes.&lt;br /&gt;
&lt;br /&gt;
In response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return Participant.find(self.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
In review_response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the review response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return self.assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Hence, the '''questionnaire_by_answer''' method in '''ScorableHelper''' will have the following snippet:&lt;br /&gt;
&lt;br /&gt;
      map = ResponseMap.find(map_id)&lt;br /&gt;
      assignment = map.response_assignment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150138</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150138"/>
		<updated>2023-04-25T18:33:34Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Issue 3 and 4 solutions updated&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Update for resubmission==&lt;br /&gt;
We have verified the design doc with the professor and made a few changes:&lt;br /&gt;
* Added function name for issue 4&lt;br /&gt;
* Fixed typos&lt;br /&gt;
* Verified the solutions and visual aid&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Changes made / Solutions==&lt;br /&gt;
===Issue 3:==&lt;br /&gt;
We have made the volume_of_review_comments method to be an instance method. We have also created a module called MetricHelper to make more sense and removed the ReviewCommentMixin&lt;br /&gt;
&lt;br /&gt;
  # frozen_string_literal: true&lt;br /&gt;
&lt;br /&gt;
  module MetricHelper&lt;br /&gt;
&lt;br /&gt;
    # Determine the average size of review comments in each round&lt;br /&gt;
    # the first entry in the returned list is the overall average&lt;br /&gt;
    # word count.&lt;br /&gt;
    def volume_of_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      comments, counter,&lt;br /&gt;
        @comments_in_round, @counter_in_round = Response.get_all_review_comments(assignment_id, reviewer_id)&lt;br /&gt;
      # Index 0 is a nil element that can be ignored in the round count&lt;br /&gt;
      num_rounds = @comments_in_round.count - 1&lt;br /&gt;
  &lt;br /&gt;
      overall_avg_vol = (Lingua::EN::Readability.new(comments).num_words / (counter.zero? ? 1 : counter)).round(0)&lt;br /&gt;
      review_comments_volume = []&lt;br /&gt;
      review_comments_volume.push(overall_avg_vol)&lt;br /&gt;
      (1..num_rounds).each do |round|&lt;br /&gt;
        num = Lingua::EN::Readability.new(@comments_in_round[round]).num_words&lt;br /&gt;
        den = (@counter_in_round[round].zero? ? 1 : @counter_in_round[round])&lt;br /&gt;
        avg_vol_in_round = (num / den).round(0)&lt;br /&gt;
        review_comments_volume.push(avg_vol_in_round)&lt;br /&gt;
      end&lt;br /&gt;
      review_comments_volume&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4:==&lt;br /&gt;
* 1) We have decided not to address this part of the issue (See the Issue 4) after discussing with the professor. The reason behind it is that the lines involved in this section is only 2 lines long and hence it does not have to be moved to a method.&lt;br /&gt;
&lt;br /&gt;
* 2) We looked at a lot of solutions to make this. The issue is that using just a 'where' method will not ensure that the Active Record Association returned will be in the same order of that of the list of ids given.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
  ids = [4,1,2,7]&lt;br /&gt;
  answers = Answer.where(id: ids)&lt;br /&gt;
  return answers.map(&amp;amp;:id)&lt;br /&gt;
&lt;br /&gt;
This example snippet would return 1,2,4,7&lt;br /&gt;
&lt;br /&gt;
Hence, to ensure the order of the return values(thereby ensuring the correct working) and to improve the performance, we have used a small gem 'find_with_order'. The main reason why we decided to add this gem over a custom solution is portability / ease of modification. A custom solution would have required us to write SQL queries in where clauses and this would be tied to the mysql(current database used). This gem is compatible with popular databases and hence this would be automatically taken care of even if the database is changed.&lt;br /&gt;
&lt;br /&gt;
Hence, our final solution is:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def calculate_total_score&lt;br /&gt;
      # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
      # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
&lt;br /&gt;
      sum = 0&lt;br /&gt;
      question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
&lt;br /&gt;
      # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
      questions = Question.find_with_order(question_ids)&lt;br /&gt;
&lt;br /&gt;
      scores.each_with_index do |score, idx|&lt;br /&gt;
        sum += score.answer * questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      sum&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  def maximum_score&lt;br /&gt;
    # Only count the scorable questions, only when the answer is not nil (we accept nil as&lt;br /&gt;
    # answer for scorable questions, and they will not be counted towards the total score)&lt;br /&gt;
    total_weight = 0&lt;br /&gt;
    question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
&lt;br /&gt;
    # We use find with order here to ensure that the list of questions we get is in the same order as that of question_ids&lt;br /&gt;
    questions = Question.find_with_order(question_ids)&lt;br /&gt;
&lt;br /&gt;
    scores.each_with_index do |score, idx|&lt;br /&gt;
      total_weight += questions[idx].weight unless score.answer.nil? || !questions[idx].is_a?(ScoredQuestion)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire = if scores.empty?&lt;br /&gt;
                      questionnaire_by_answer(nil)&lt;br /&gt;
                    else&lt;br /&gt;
                      questionnaire_by_answer(scores.first)&lt;br /&gt;
                    end&lt;br /&gt;
    total_weight * questionnaire.max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
* 3) Usage of visitor pattern to avoid checking class types: We solved this by adding a method '''response_assignment''' to ReviewResponseMap and ResponseMap classes.&lt;br /&gt;
&lt;br /&gt;
In response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return Participant.find(self.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
In review_response_map.rb&lt;br /&gt;
&lt;br /&gt;
  # returns the assignment related to the review response map&lt;br /&gt;
  def response_assignment&lt;br /&gt;
    return self.assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Hence, the '''questionnaire_by_answer''' method in '''ScorableHelper''' will have the following snippet:&lt;br /&gt;
&lt;br /&gt;
      map = ResponseMap.find(map_id)&lt;br /&gt;
      assignment = map.response_assignment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150137</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=150137"/>
		<updated>2023-04-25T18:18:48Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Update for resubmission==&lt;br /&gt;
We have verified the design doc with the professor and made a few changes:&lt;br /&gt;
* Added function name for issue 4&lt;br /&gt;
* Fixed typos&lt;br /&gt;
* Verified the solutions and visual aid&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
==== Proposed Solution====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Changes made / Solutions==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149726</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149726"/>
		<updated>2023-04-13T03:46:10Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Update for resubmission==&lt;br /&gt;
We have verified the design doc with the professor and made a few changes:&lt;br /&gt;
* Added function name for issue 4&lt;br /&gt;
* Fixed typos&lt;br /&gt;
* Verified the solutions and visual aid&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* 1) Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* 2) Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* 3) Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* 1) The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 2) Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 3) To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149600</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149600"/>
		<updated>2023-04-13T00:13:11Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Update - Method name added&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.  This will allow the client class to use an Iterator pattern to fetch one review at a time from the Response class rather than iterate over that data structure that get_all_review_comments returned to it.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weights. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method named '''response_assignment''' in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return its assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin (see Issue 4).&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149251</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149251"/>
		<updated>2023-04-08T01:30:18Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactoring significant_difference&lt;br /&gt;
* Make the method in ReviewCommentMixin to be an instance method&lt;br /&gt;
* Simplify + Refactor methods in ScorableMixin&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin(See Issue 4)&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149250</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149250"/>
		<updated>2023-04-08T01:26:54Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin(See Issue 4)&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
Since this project is tied to the Response class, all the test cases will be added to response_spec.rb&lt;br /&gt;
&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023&amp;diff=149247</id>
		<title>CSC/ECE 517 Spring 2023</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023&amp;diff=149247"/>
		<updated>2023-04-08T01:26:00Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== OSS Projects ==&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 -E2306. Refactor user_controller.rb, user.rb and its child classes]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2320. Reimplement the Question hierarchy]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2312 + E2313. Reimplement response.rb and responses_controller.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - NTNX-1. Support provisioning MongoDb via NDB Kubernetes Operator]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2316. Reimplement sign_up_sheet_controller.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2315. Reimplement signed_up_team.rb, sign_up_topic.rb, sign_up_sheet.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2340. Reimplement signed_up_team.rb, sign_up_topic.rb, sign_up_sheet.rb (Project 4)]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2323. Refactor DueDate functionality from assignment.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - E2317: Reimplement participant.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - E2314. Reimplement the response map hierarchy]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - E2321. Reimplement QuestionnairesController and QuestionsController]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - E2305. Grading audit trail]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - E2329. Grading audit trail(Project 4)]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - E2309. Refactor Node model and its subclasses]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - NTNX-2. Support provisioning mySQL databases via NDB Kubernetes Operator]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - E2300. Refactor E1858. Github metrics integration]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Sping 2023 - E2322: Refactor Questionnaire View to display Bookmark Rating]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - NTNX-3. Refactor models to keep profiles (software, compute, network, etc) as optional and use default if not specified]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE_517_Spring_2023_-_E2301._Refactor_review_maping_helper]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE_517_Spring_2023_-_E2337._Reimplement_node_hierarchy]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE_517_Spring_2023_-E2339._Reimplement signed_up_teams_controller and sign_up_topics_controller.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE_517_Spring_2023_-_E2320._Reimplement the Question Hierarchy]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE_517_Spring_2023 - E2341. UI for Participants.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb]]&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149245</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149245"/>
		<updated>2023-04-08T01:23:12Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Question class, Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin(See Issue 4)&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
* Issue 1,2,4: Modify tests in response_spec.rb&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
* Test comments for review - We also want to test the scenario that involves seeing the comments for a review.&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149237</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149237"/>
		<updated>2023-04-08T01:14:57Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
This project is a continuation to E2312. The current project is to make design changes to the Question class, Response class, ReviewCommentMixin module, ScorableMixin module and add comments to test cases. We explain the issue with respect to the current implementation of each of these entities as subsections below.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Issues==&lt;br /&gt;
&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
  def self.avg_scores_and_count_for_prev_reviews(existing_responses, current_response)&lt;br /&gt;
    scores_assigned = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    existing_responses.each do |existing_response|&lt;br /&gt;
      unless existing_response.id == current_response.id # the current_response is also in existing_responses array&lt;br /&gt;
        count += 1&lt;br /&gt;
        scores_assigned &amp;lt;&amp;lt; existing_response.aggregate_questionnaire_score.to_f / existing_response.maximum_score&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    [scores_assigned.sum / scores_assigned.size.to_f, count]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
  # compare the current response score with other scores on the same artifact, and test if the difference&lt;br /&gt;
  # is significant enough to notify instructor.&lt;br /&gt;
  # Precondition: the response object is associated with a ReviewResponseMap&lt;br /&gt;
  ### &amp;quot;map_class.assessments_for&amp;quot; method need to be refactored&lt;br /&gt;
  def significant_difference?&lt;br /&gt;
    map_class = map.class&lt;br /&gt;
    existing_responses = map_class.assessments_for(map.reviewee)&lt;br /&gt;
    average_score_on_same_artifact_from_others, count = Response.avg_scores_and_count_for_prev_reviews(existing_responses, self)&lt;br /&gt;
    # if this response is the first on this artifact, there's no grade conflict&lt;br /&gt;
    return false if count.zero?&lt;br /&gt;
    # This score has already skipped the unfilled scorable question(s)&lt;br /&gt;
    score = aggregate_questionnaire_score.to_f / maximum_score&lt;br /&gt;
    questionnaire = questionnaire_by_answer(scores.first)&lt;br /&gt;
    assignment = map.assignment&lt;br /&gt;
    assignment_questionnaire = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: questionnaire.id)&lt;br /&gt;
    # notification_limit can be specified on 'Rubrics' tab on assignment edit page.&lt;br /&gt;
    allowed_difference_percentage = assignment_questionnaire.notification_limit.to_f&lt;br /&gt;
    # the range of average_score_on_same_artifact_from_others and score is [0,1]&lt;br /&gt;
    # the range of allowed_difference_percentage is [0, 100]&lt;br /&gt;
    (average_score_on_same_artifact_from_others - score).abs * 100 &amp;gt; allowed_difference_percentage&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
  assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
    map.assignment&lt;br /&gt;
  else&lt;br /&gt;
    Participant.find(map.reviewer_id).assignment&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
  def calculate_total_weight()&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
  end&lt;br /&gt;
  And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
  question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
  questions = Question.where(question_id: question_ids)&lt;br /&gt;
  &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Design Principles==&lt;br /&gt;
This section aims to act as a consolidation of the design principles that we will implement as part of this project&lt;br /&gt;
* DRY: We will DRY the code that we handle as part of this project. An example of this would be the change that we make as part of the ScorableMixin(See Issue 4)&lt;br /&gt;
&lt;br /&gt;
* Visitor Pattern: Having to check the class of an object and making calls based on that is an example of a bad design pattern. An example of this is ScorableMixin. We will adopt a loose implementation of the Visitor pattern to overcome this by adding a method in ResponseMap and ReviewResponseMap and just call that method in the object we receive. See Issue 4 section for a detailed explanation.&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
* Issue 4: Modify tests in response_spec.rb&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;br /&gt;
&lt;br /&gt;
==Future Work==&lt;br /&gt;
The issues pointed out in this documentation will be implemented once the design gets approved. Further changes will be updated in this section as we make progress with the project.&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* David Brain (mdbrain2)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149214</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149214"/>
		<updated>2023-04-08T00:50:51Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
    ```&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
      question = Question.find(s.question_id)&lt;br /&gt;
      &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
    end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
          assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
                         map.assignment&lt;br /&gt;
                       else&lt;br /&gt;
                         Participant.find(map.reviewer_id).assignment&lt;br /&gt;
                       end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        def calculate_total_weight()&lt;br /&gt;
          scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
    And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
        questions = Question.where(question_id: question_ids)&lt;br /&gt;
        &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
    ```&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
* Issue 4: Modify tests in response_spec.rb&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:ScorableMixin4.png&amp;diff=149213</id>
		<title>File:ScorableMixin4.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:ScorableMixin4.png&amp;diff=149213"/>
		<updated>2023-04-08T00:50:01Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:ScorableMixin3.png&amp;diff=149211</id>
		<title>File:ScorableMixin3.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:ScorableMixin3.png&amp;diff=149211"/>
		<updated>2023-04-08T00:47:02Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149210</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149210"/>
		<updated>2023-04-08T00:46:50Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 2: Refactoring significant_difference?===&lt;br /&gt;
While the significant_difference method does not necessarily need to be refactored, it is the only method with a call for the avg_scores_and_count_for_prev_reviews method, which does need to be refactored it both is a class method and returns a data structure.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Because of this we decided that we should simply refactor significant_difference instead and simply remove avg_scores_and_count_for_prev_reviews.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
    ```&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
      question = Question.find(s.question_id)&lt;br /&gt;
      &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
    end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
          assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
                         map.assignment&lt;br /&gt;
                       else&lt;br /&gt;
                         Participant.find(map.reviewer_id).assignment&lt;br /&gt;
                       end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        def calculate_total_weight()&lt;br /&gt;
          scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
    And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
        questions = Question.where(question_id: question_ids)&lt;br /&gt;
        &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
    ```&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
A visual representation of this change would look like this. The snippet uses an 'OR' condition to pick the appropriate assignment. Our change would remove the need for this check.&lt;br /&gt;
[[File:ScorableMixin3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
* Issue 4: Modify tests in response_spec.rb&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149206</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149206"/>
		<updated>2023-04-08T00:32:02Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
    ```&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
      question = Question.find(s.question_id)&lt;br /&gt;
      &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
    end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
          assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
                         map.assignment&lt;br /&gt;
                       else&lt;br /&gt;
                         Participant.find(map.reviewer_id).assignment&lt;br /&gt;
                       end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        def calculate_total_weight()&lt;br /&gt;
          scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
    And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
        questions = Question.where(question_id: question_ids)&lt;br /&gt;
        &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
    ```&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
[[File:ScorableMixin2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
* Issue 4: Modify tests in response_spec.rb&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:ScorableMixin2.png&amp;diff=149204</id>
		<title>File:ScorableMixin2.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:ScorableMixin2.png&amp;diff=149204"/>
		<updated>2023-04-08T00:30:19Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:ScorableMixin.png&amp;diff=149201</id>
		<title>File:ScorableMixin.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:ScorableMixin.png&amp;diff=149201"/>
		<updated>2023-04-08T00:27:32Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149186</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149186"/>
		<updated>2023-04-07T23:56:28Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
    ```&lt;br /&gt;
    scores.each do |s|&lt;br /&gt;
      question = Question.find(s.question_id)&lt;br /&gt;
      &amp;lt;Other lines irrelevant to this case &amp;gt;&lt;br /&gt;
    end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
          assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
                         map.assignment&lt;br /&gt;
                       else&lt;br /&gt;
                         Participant.find(map.reviewer_id).assignment&lt;br /&gt;
                       end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        def calculate_total_weight()&lt;br /&gt;
          scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
    And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
        questions = Question.where(question_id: question_ids)&lt;br /&gt;
        &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
    ```&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
* Issue 4: Modify tests in response_spec.rb&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149185</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149185"/>
		<updated>2023-04-07T23:53:03Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
    ```&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
          assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
                         map.assignment&lt;br /&gt;
                       else&lt;br /&gt;
                         Participant.find(map.reviewer_id).assignment&lt;br /&gt;
                       end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        def calculate_total_weight()&lt;br /&gt;
          scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
    And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
        questions = Question.where(question_id: question_ids)&lt;br /&gt;
        &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
    ```&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
As part of this section, we track the set of files that are to be changed for tests and scenarios that are to be tested.&lt;br /&gt;
===Files===&lt;br /&gt;
* Issue 3: Write tests for '''volume_of_review_comments''' method&lt;br /&gt;
* Issue 4: Modify tests in response_spec.rb&lt;br /&gt;
===Scenarios===&lt;br /&gt;
* Test the score calculation cases - maximum possible total score for all scores, average score across all of the instances and total score (weighted sum). Ideally average score calculation should cover all the scenarios, that is, if the maximum score is zero, it uses the total score(weighted).&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149184</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149184"/>
		<updated>2023-04-07T23:47:18Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
    ```&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
          assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
                         map.assignment&lt;br /&gt;
                       else&lt;br /&gt;
                         Participant.find(map.reviewer_id).assignment&lt;br /&gt;
                       end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
====Solutions====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        def calculate_total_weight()&lt;br /&gt;
          scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
    And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
        questions = Question.where(question_id: question_ids)&lt;br /&gt;
        &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
    ```&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149183</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=149183"/>
		<updated>2023-04-07T23:46:36Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Mixin issues&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Project Overview==&lt;br /&gt;
There are several issues we plan to address as part of this project.&lt;br /&gt;
&lt;br /&gt;
* Refactor the get_all_review_comments method as an instance method in response.rb file&lt;br /&gt;
* Refactor the self.get_all_responses method as an instance method in response_map.rb file. &lt;br /&gt;
* Add more descriptive comments for the tests.&lt;br /&gt;
&lt;br /&gt;
===Issue 1: Refactor the self.get_all_review_comments method===&lt;br /&gt;
Currently, the get_all_review_comments method is a class method that returns a data structure containing all the review comments for all responses. This violates the principles of good design as it creates a large data structure that the calling method needs to break apart, leading to poor performance and increased complexity.&lt;br /&gt;
&lt;br /&gt;
[[File:get_all_review_comments.png|700px]]&lt;br /&gt;
&lt;br /&gt;
To address this issue and come up with a much more elegant design, we plan to refactor as a new implementation that should convert the get_all_review_comments method into an instance method that takes a review object as an argument and returns the comments for that review only.&lt;br /&gt;
&lt;br /&gt;
===Issue 3: Make the method in ReviewCommentMixin to be an instance method ===&lt;br /&gt;
====Problems:==== &lt;br /&gt;
* The ReviewCommentMixin has one single method '''volume_of_review_comments''' and it is currently present as a class method. There is no need for it to be a class method and it should instead be an instance method. &lt;br /&gt;
* Volume of review comments is a metric, which belongs in metrics.rb, not in a mixin for responses.&lt;br /&gt;
&lt;br /&gt;
====Solution:====&lt;br /&gt;
The change is quite straight forward. We should move this method to be an instance method in '''metrics.rb''' file. A side note is that, this method calls the '''get_all_review_comments''' method from '''response.rb'''. This method will be changed as part of this project (See above points). Any change to that function's method call must be reflected here too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 4: Simplify + Refactor methods in ScorableMixin ===&lt;br /&gt;
&lt;br /&gt;
This mixin has methods that are related to scoring. https://expertiza.csc.ncsu.edu/index.php/Scoring_%26_Grading_Methods_(Fall_%2721) is a good resource to understand how scoring and grading works.&lt;br /&gt;
&lt;br /&gt;
====Problems:====&lt;br /&gt;
* Not DRY : The following piece of code is used in 2 functions - calculate_total_score and maximum_score&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          &amp;lt; Perform a calculation &amp;gt;&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
Basically, we iterate through the score and calculate something. The common calculation between both these functions is total_weight calculation.&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' If the logic to calculate the total_weight changes in the future, we will have to make changes to both these places. A developer who is relatively new to the project might miss out one changing it in a location. It is always good to have a single point of change by 'DRY'ing our code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Not optimized: Another change related to the above snippet has to do with how the queries are made and the number of queries. &lt;br /&gt;
    ```&lt;br /&gt;
    question = Question.find(s.question_id)&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
'''What problem does this cause?''' This is done inside a loop, meaning we perform a select query to each time -&amp;gt; N queries for N objects.  Our solution aims to optimize this.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Checking the class type of an object to perform some business logic&lt;br /&gt;
&lt;br /&gt;
This issue is present in the questionnaire_by_answer method. The following snippet shows it:&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
          assignment = if map.is_a? ReviewResponseMap&lt;br /&gt;
                         map.assignment&lt;br /&gt;
                       else&lt;br /&gt;
                         Participant.find(map.reviewer_id).assignment&lt;br /&gt;
                       end&lt;br /&gt;
    ```&lt;br /&gt;
&lt;br /&gt;
====Solutions:====&lt;br /&gt;
* The common function would involve calculating the sum of weight. &lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        def calculate_total_weight()&lt;br /&gt;
          scores.each do |s|&lt;br /&gt;
          question = Question.find(s.question_id)&lt;br /&gt;
          total_weight += question.weight unless s.answer.nil? || !question.is_a?(ScoredQuestion)&lt;br /&gt;
        end&lt;br /&gt;
    ```&lt;br /&gt;
    And this can be called inside the calculate_total_score and maximum_score.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Instead of calling Question.find inside the loop, we can get the set of question_ids and use a where function.&lt;br /&gt;
&lt;br /&gt;
    ```&lt;br /&gt;
        question_ids = scores.map(&amp;amp;:question_id)&lt;br /&gt;
        questions = Question.where(question_id: question_ids)&lt;br /&gt;
        &amp;lt; Make the calculation here &amp;gt;&lt;br /&gt;
    ```&lt;br /&gt;
  This way, we only make one database call instead of N calls.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* To solve checking the object type, we can write a method in ResponseMap and ReviewResponseMap. This way, we can just call that method in the object and depending on what object it is, we would get the corresponding answer.&lt;br /&gt;
   * In ReviewResponseMap, the method should return it's assignment&lt;br /&gt;
   * In ResponseMap, the method should return Participant.find(map.reviewer_id).assignment&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Issue 5: Refactor the self.get_all_responses method===&lt;br /&gt;
&lt;br /&gt;
The self.get_all_responses method which is currently in the response.rb file would be much cleaner as an instance method in response_map.rb. We plan to refactor the self.get_all_responses method in response.rb to an instance method in response_map.rb.&lt;br /&gt;
&lt;br /&gt;
[[File:Get all responses.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response map class.png|700px]]&lt;br /&gt;
&lt;br /&gt;
===Issue 6: Add more descriptive comments for the tests===&lt;br /&gt;
&lt;br /&gt;
The existing tests need more descriptive comments. We plan to add more detailed comments for all the test cases.&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 1.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 2.png|700px]]&lt;br /&gt;
&lt;br /&gt;
[[File:Response tests 3.png|700px]]&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=148898</id>
		<title>CSC/ECE 517 Spring 2023 - E2336. Reimplement response.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2336._Reimplement_response.rb&amp;diff=148898"/>
		<updated>2023-04-07T00:15:48Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Page creation&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148665</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148665"/>
		<updated>2023-03-28T03:54:16Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
Design Doc Link: https://docs.google.com/document/d/1Drglvj0b487qIACeIW1jYG83uRtJtl7fIchM6B1H9Bk/edit?usp=sharing&lt;br /&gt;
This is the document that we are actively using. All the details in the design document are already written here in a more clear way.&lt;br /&gt;
&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
'''UPDATE: We have now received access and have added as much code as possible within the stipulated time. We have added a 'Resubmission progress' section to inform you about the changes we have done now. We have also updated the Future Implementation section accordingly.'''&lt;br /&gt;
&lt;br /&gt;
==Resubmission progress==&lt;br /&gt;
* We have received the access now, and have added a bit of code after that. We have analyzed the JSON request that is needed to be sent to provision a database and created a struct of that type in database_types.go.&lt;br /&gt;
* We have stuck to the DRY principle of re-using an existing variable's fields by inheriting from it. CloudDatabaseProvisionRequest inherits from DatabaseProvisionRequest.&lt;br /&gt;
* Added logic to create a CloudDatabaseProvisionRequest object if the remoteType is 'cloud' - This is because the request to this requires extra fields&lt;br /&gt;
* We have also now added the remoteType field to be passed as a variable for it to be available in the controller helper logic.&lt;br /&gt;
* We fixed the test cases after adding this logic.&lt;br /&gt;
* We have added a google drive link with a video of the tests running.&lt;br /&gt;
* Updated the implementation section with what is left.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# '''Kubernetes Cluster'''&lt;br /&gt;
# '''Kubernetes Service''' that almost acts like a gateway to the provisioned database - The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
# '''Nutanix Database Service''' - The operator pod running in the K8 cluster communicates with this service to provision, de-provision, obtain status of the database etc.&lt;br /&gt;
# '''Cloud platform''' (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: WE HAVE NOW RECEIVED ACCESS TO THE INSTANCE AND HAVE MADE AS MANY CHANGES AS POSSIBLE WITHIN THIS TIME. HAVE A LOOK AT THE RESUBMISSION PROGRESS SECTION FOR THE UPDATE'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
'''UPDATE: This has now been implemented&lt;br /&gt;
'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The new request structure is as follows:&lt;br /&gt;
type CloudDatabaseProvisionRequest struct {&lt;br /&gt;
	DatabaseProvisionRequest&lt;br /&gt;
	AwsKeyName string `json:&amp;quot;awsKeyName&amp;quot;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
The logic to build the appropriate request structure depending on the remoteType:&lt;br /&gt;
[[File:Cloudcode.png|500px|]]&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: We have now received access and will implemented the previous steps. This will be the next step to do'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance (DONE)&lt;br /&gt;
* Construct provision request for provisioning DB in cloud (DONE)&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType''' - We are currently passing this value as function parameter. This will be refactored into a helper method later.&lt;br /&gt;
&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
Test video link: https://drive.google.com/file/d/1eRMvXjzatbz9W8xYPcg3V72pZyZBfVmi/view?usp=sharing&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
* Krunal Jhaveri&lt;br /&gt;
* Manav Rajvanshi&lt;br /&gt;
* Krishna Saurabh Vankadaru&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148664</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148664"/>
		<updated>2023-03-28T03:53:55Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
Design Doc Link: https://docs.google.com/document/d/1Drglvj0b487qIACeIW1jYG83uRtJtl7fIchM6B1H9Bk/edit?usp=sharing&lt;br /&gt;
This is the document that we are actively using. All the details in the design document are already written here in a more clear way.&lt;br /&gt;
&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
'''UPDATE: We have now received access and have added as much code as possible within the stipulated time. We have added a 'Resubmission progress' section to inform you about the changes we have done now. We have also updated the Future Implementation section accordingly.'''&lt;br /&gt;
&lt;br /&gt;
==Resubmission progress==&lt;br /&gt;
* We have received the access now, and have added a bit of code after that. We have analyzed the JSON request that is needed to be sent to provision a database and created a struct of that type in database_types.go.&lt;br /&gt;
* We have stuck to the DRY principle of re-using an existing variable's fields by inheriting from it. CloudDatabaseProvisionRequest inherits from DatabaseProvisionRequest.&lt;br /&gt;
* Added logic to create a CloudDatabaseProvisionRequest object if the remoteType is 'cloud' - This is because the request to this requires extra fields&lt;br /&gt;
* We have also now added the remoteType field to be passed as a variable for it to be available in the controller helper logic.&lt;br /&gt;
* We fixed the test cases after adding this logic.&lt;br /&gt;
* We have added a google drive link with a video of the tests running.&lt;br /&gt;
* Updated the implementation section with what is left.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# '''Kubernetes Cluster'''&lt;br /&gt;
# '''Kubernetes Service''' that almost acts like a gateway to the provisioned database - The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
# '''Nutanix Database Service''' - The operator pod running in the K8 cluster communicates with this service to provision, de-provision, obtain status of the database etc.&lt;br /&gt;
# '''Cloud platform''' (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: WE HAVE NOW RECEIVED ACCESS TO THE INSTANCE AND HAVE MADE AS MANY CHANGES AS POSSIBLE WITHIN THIS TIME. HAVE A LOOK AT THE RESUBMISSION PROGRESS SECTION FOR THE UPDATE'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
'''UPDATE: This has now been implemented&lt;br /&gt;
'''&lt;br /&gt;
The new request structure is as follows:&lt;br /&gt;
type CloudDatabaseProvisionRequest struct {&lt;br /&gt;
	DatabaseProvisionRequest&lt;br /&gt;
	AwsKeyName string `json:&amp;quot;awsKeyName&amp;quot;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
The logic to build the appropriate request structure depending on the remoteType:&lt;br /&gt;
[[File:Cloudcode.png|500px|]]&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: We have now received access and will implemented the previous steps. This will be the next step to do'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance (DONE)&lt;br /&gt;
* Construct provision request for provisioning DB in cloud (DONE)&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType''' - We are currently passing this value as function parameter. This will be refactored into a helper method later.&lt;br /&gt;
&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
Test video link: https://drive.google.com/file/d/1eRMvXjzatbz9W8xYPcg3V72pZyZBfVmi/view?usp=sharing&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
* Krunal Jhaveri&lt;br /&gt;
* Manav Rajvanshi&lt;br /&gt;
* Krishna Saurabh Vankadaru&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Cloudcode.png&amp;diff=148663</id>
		<title>File:Cloudcode.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Cloudcode.png&amp;diff=148663"/>
		<updated>2023-03-28T03:51:04Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148661</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148661"/>
		<updated>2023-03-28T03:48:24Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
Design Doc Link: https://docs.google.com/document/d/1Drglvj0b487qIACeIW1jYG83uRtJtl7fIchM6B1H9Bk/edit?usp=sharing&lt;br /&gt;
This is the document that we are actively using. All the details in the design document are already written here in a more clear way.&lt;br /&gt;
&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
'''UPDATE: We have now received access and have added as much code as possible within the stipulated time. We have added a 'Resubmission progress' section to inform you about the changes we have done now. We have also updated the Future Implementation section accordingly.'''&lt;br /&gt;
&lt;br /&gt;
==Resubmission progress==&lt;br /&gt;
* We have received the access now, and have added a bit of code after that. We have analyzed the JSON request that is needed to be sent to provision a database and created a struct of that type in database_types.go.&lt;br /&gt;
* We have stuck to the DRY principle of re-using an existing variable's fields by inheriting from it. CloudDatabaseProvisionRequest inherits from DatabaseProvisionRequest.&lt;br /&gt;
* Added logic to create a CloudDatabaseProvisionRequest object if the remoteType is 'cloud' - This is because the request to this requires extra fields&lt;br /&gt;
* We have also now added the remoteType field to be passed as a variable for it to be available in the controller helper logic.&lt;br /&gt;
* We fixed the test cases after adding this logic.&lt;br /&gt;
* We have added a google drive link with a video of the tests running.&lt;br /&gt;
* Updated the implementation section with what is left.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# '''Kubernetes Cluster'''&lt;br /&gt;
# '''Kubernetes Service''' that almost acts like a gateway to the provisioned database - The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
# '''Nutanix Database Service''' - The operator pod running in the K8 cluster communicates with this service to provision, de-provision, obtain status of the database etc.&lt;br /&gt;
# '''Cloud platform''' (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: WE HAVE NOW RECEIVED ACCESS TO THE INSTANCE AND HAVE MADE AS MANY CHANGES AS POSSIBLE WITHIN THIS TIME. HAVE A LOOK AT THE RESUBMISSION PROGRESS SECTION FOR THE UPDATE'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
UPDATE: This has now been implemented&lt;br /&gt;
&lt;br /&gt;
type CloudDatabaseProvisionRequest struct {&lt;br /&gt;
	DatabaseProvisionRequest&lt;br /&gt;
	AwsKeyName string `json:&amp;quot;awsKeyName&amp;quot;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: This can only be implemented after obtaining the access. We have now received access and will implement it'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance (DONE)&lt;br /&gt;
* Construct provision request for provisioning DB in cloud (DONE)&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType''' - We are currently passing this value as function parameter. This will be refactored into a helper method later.&lt;br /&gt;
&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
Test video link: https://drive.google.com/file/d/1eRMvXjzatbz9W8xYPcg3V72pZyZBfVmi/view?usp=sharing&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
* Krunal Jhaveri&lt;br /&gt;
* Manav Rajvanshi&lt;br /&gt;
* Krishna Saurabh Vankadaru&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148642</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148642"/>
		<updated>2023-03-28T03:32:59Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
Design Doc Link: https://docs.google.com/document/d/1Drglvj0b487qIACeIW1jYG83uRtJtl7fIchM6B1H9Bk/edit?usp=sharing&lt;br /&gt;
This is the document that we are actively using. All the details in the design document are already written here in a more clear way.&lt;br /&gt;
&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
'''UPDATE: We have now received access and have added as much code as possible within the stipulated time. We have added a 'Resubmission progress' section to inform you about the changes we have done now. We have also updated the Future Implementation section accordingly.'''&lt;br /&gt;
&lt;br /&gt;
==Resubmission progress==&lt;br /&gt;
* We have received the access now, and have added a bit of code after that. We have analyzed the JSON request that is needed to be sent to provision a database and created a struct of that type in database_types.go.&lt;br /&gt;
* We have stuck to the DRY principle of re-using an existing variable's fields by inheriting from it. CloudDatabaseProvisionRequest inherits from DatabaseProvisionRequest.&lt;br /&gt;
* We have also now added the remoteType field to be passed as a variable for it to be available in the controller helper logic.&lt;br /&gt;
* We fixed the test cases after adding this logic.&lt;br /&gt;
* We have added a google drive link with a video of the tests running.&lt;br /&gt;
* Updated the implementation section with what is left.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# '''Kubernetes Cluster'''&lt;br /&gt;
# '''Kubernetes Service''' that almost acts like a gateway to the provisioned database - The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
# '''Nutanix Database Service''' - The operator pod running in the K8 cluster communicates with this service to provision, de-provision, obtain status of the database etc.&lt;br /&gt;
# '''Cloud platform''' (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: WE HAVE NOW RECEIVED ACCESS TO THE INSTANCE AND HAVE MADE AS MANY CHANGES AS POSSIBLE WITHIN THIS TIME. HAVE A LOOK AT THE RESUBMISSION PROGRESS SECTION FOR THE UPDATE'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
UPDATE: This has now been implemented&lt;br /&gt;
&lt;br /&gt;
type CloudDatabaseProvisionRequest struct {&lt;br /&gt;
	DatabaseProvisionRequest&lt;br /&gt;
	AwsKeyName string `json:&amp;quot;awsKeyName&amp;quot;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: This can only be implemented after obtaining the access. We have now received access and will implement it'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance (DONE)&lt;br /&gt;
* Construct provision request for provisioning DB in cloud (DONE)&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType''' - We are currently passing this value as function parameter. This will be refactored into a helper method later.&lt;br /&gt;
&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
Test video link: https://drive.google.com/file/d/1eRMvXjzatbz9W8xYPcg3V72pZyZBfVmi/view?usp=sharing&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
* Krunal Jhaveri&lt;br /&gt;
* Manav Rajvanshi&lt;br /&gt;
* Krishna Saurabh Vankadaru&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148639</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148639"/>
		<updated>2023-03-28T03:30:03Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Resubmission update changes&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
Design Doc Link: https://docs.google.com/document/d/1Drglvj0b487qIACeIW1jYG83uRtJtl7fIchM6B1H9Bk/edit?usp=sharing&lt;br /&gt;
This is the document that we are actively using. All the details in the design document are already written here in a more clear way.&lt;br /&gt;
&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
UPDATE: We have now received access and have added as much code as possible within the stipulated time. Following are the changes we have implemented regarding this project and the submission:&lt;br /&gt;
&lt;br /&gt;
==Resubmission progress==&lt;br /&gt;
* We have received the access now, and have added a bit of code after that. We have analyzed the JSON request that is needed to be sent to provision a database and created a struct of that type in database_types.go.&lt;br /&gt;
* We have stuck to the DRY principle of re-using an existing variable's fields by inheriting from it. CloudDatabaseProvisionRequest inherits from DatabaseProvisionRequest.&lt;br /&gt;
* We have also now added the remoteType field to be passed as a variable for it to be available in the controller helper logic.&lt;br /&gt;
* We fixed the test cases after adding this logic.&lt;br /&gt;
* We have added a google drive link with a video of the tests running.&lt;br /&gt;
* Updated the implementation section with what is left.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# '''Kubernetes Cluster'''&lt;br /&gt;
# '''Kubernetes Service''' that almost acts like a gateway to the provisioned database - The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
# '''Nutanix Database Service''' - The operator pod running in the K8 cluster communicates with this service to provision, de-provision, obtain status of the database etc.&lt;br /&gt;
# '''Cloud platform''' (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: WE HAVE NOW RECEIVED ACCESS TO THE INSTANCE AND HAVE MADE AS MANY CHANGES AS POSSIBLE WITHIN THIS TIME. HAVE A LOOK AT THE RESUBMISSION PROGRESS SECTION FOR THE UPDATE'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
UPDATE: This has now been implemented&lt;br /&gt;
&lt;br /&gt;
type CloudDatabaseProvisionRequest struct {&lt;br /&gt;
	DatabaseProvisionRequest&lt;br /&gt;
	AwsKeyName string `json:&amp;quot;awsKeyName&amp;quot;`&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: This can only be implemented after obtaining the access. We have now received access and will implement it'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance (DONE)&lt;br /&gt;
* Construct provision request for provisioning DB in cloud (DONE)&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType''' - We are currently passing this value as function parameter. This will be refactored into a helper method later.&lt;br /&gt;
&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
Test video link: https://drive.google.com/file/d/1eRMvXjzatbz9W8xYPcg3V72pZyZBfVmi/view?usp=sharing&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
* Krunal Jhaveri&lt;br /&gt;
* Manav Rajvanshi&lt;br /&gt;
* Krishna Saurabh Vankadaru&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148312</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=148312"/>
		<updated>2023-03-24T14:02:04Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
Design Doc Link: https://docs.google.com/document/d/1Drglvj0b487qIACeIW1jYG83uRtJtl7fIchM6B1H9Bk/edit?usp=sharing&lt;br /&gt;
This is the document that we are actively using. All the details in the design document are already written here in a more clear way.&lt;br /&gt;
&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# '''Kubernetes Cluster'''&lt;br /&gt;
# '''Kubernetes Service''' that almost acts like a gateway to the provisioned database - The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
# '''Nutanix Database Service''' - The operator pod running in the K8 cluster communicates with this service to provision, de-provision, obtain status of the database etc.&lt;br /&gt;
# '''Cloud platform''' (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: MOST OF THESE CHANGES HAVE NOT BEEN MADE NOW SINCE OUR TEAM HAS NOT OBTAINED ACCESS TO CONNECT THE SERVICE WITH EC2 INSTANCE FROM THE NUTANIX TEAM'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
'''Note: We have created this file, but currently have template values. After getting access, we will fill them accordingly'''&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: This can only be implemented after obtaining the access. We are still waiting on it and will implement it as soon as we obtain it.'''&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance&lt;br /&gt;
* Construct provision request for provisioning DB in cloud&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType'''&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
&lt;br /&gt;
==Mentors==&lt;br /&gt;
* Prof. Edward F. Gehringer&lt;br /&gt;
* Krunal Jhaveri&lt;br /&gt;
* Manav Rajvanshi&lt;br /&gt;
* Krishna Saurabh Vankadaru&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
* Arvind Srinivas Subramanian (asubram9)&lt;br /&gt;
* Dhanya Sri Dasari (ddasari)&lt;br /&gt;
* Zhihao Wang (zwang238@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023&amp;diff=147720</id>
		<title>CSC/ECE 517 Spring 2023</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023&amp;diff=147720"/>
		<updated>2023-03-22T01:39:47Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: /* OSS Projects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== OSS Projects ==&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2320. Reimplement the Question hierarchy]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2312 + E2313. Reimplement response.rb and responses_controller.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - CSC517. Support provisioning MongoDb via NDB Kubernetes Operator]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2316. Reimplement sign_up_sheet_controller.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2315. Reimplement signed_up_team.rb, sign_up_topic.rb, sign_up_sheet.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring_2023 - E2323. Refactor DueDate functionality from assignment.rb]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023 - E2314. Reimplement the response map hierarchy]]&lt;br /&gt;
&lt;br /&gt;
[[CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws]]&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147719</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147719"/>
		<updated>2023-03-22T01:37:33Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# '''Kubernetes Cluster'''&lt;br /&gt;
# '''Kubernetes Service''' that almost acts like a gateway to the provisioned database - The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
# '''Nutanix Database Service''' - The operator pod running in the K8 cluster communicates with this service to provision, de-provision, obtain status of the database etc.&lt;br /&gt;
# '''Cloud platform''' (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: MOST OF THESE CHANGES HAVE NOT BEEN MADE NOW SINCE OUR TEAM HAS NOT OBTAINED ACCESS TO CONNECT THE SERVICE WITH EC2 INSTANCE FROM THE NUTANIX TEAM'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
'''Note: We have created this file, but currently have template values. After getting access, we will fill them accordingly'''&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: This can only be implemented after obtaining the access. We are still waiting on it and will implement it as soon as we obtain it.'''&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance&lt;br /&gt;
* Construct provision request for provisioning DB in cloud&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType'''&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
Arvind Srinivas Subramanian (asubram9)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147717</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147717"/>
		<updated>2023-03-22T01:36:34Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Architecture explanation&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# Kubernetes Cluster&lt;br /&gt;
# Kubernetes Service that almost acts like a gateway to the provisioned database&lt;br /&gt;
The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
# Nutanix Database Service&lt;br /&gt;
The operator pod running in the K8 cluster communicates with this service to provision, de-provision, obtain status of the database etc.&lt;br /&gt;
# Any of the cloud platforms (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: MOST OF THESE CHANGES HAVE NOT BEEN MADE NOW SINCE OUR TEAM HAS NOT OBTAINED ACCESS TO CONNECT THE SERVICE WITH EC2 INSTANCE FROM THE NUTANIX TEAM'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
'''Note: We have created this file, but currently have template values. After getting access, we will fill them accordingly'''&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: This can only be implemented after obtaining the access. We are still waiting on it and will implement it as soon as we obtain it.'''&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance&lt;br /&gt;
* Construct provision request for provisioning DB in cloud&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType'''&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
Arvind Srinivas Subramanian (asubram9)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147716</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147716"/>
		<updated>2023-03-22T01:34:53Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Note to the reviewer==&lt;br /&gt;
A major part of the logic can only be written after we get access to the keys of an EC2 instance that we can connect to the Nutanix Database Service. This differs from other NTNX teams since we need to provision the database on AWS. Hence, we cannot use the Test Nutanix endpoint to create clusterID or Secret's name. We have added a '''Note''' section in the Implementation section to inform the parts which fall under this category. We have also added this information collectively under the '''Future Implementation''' section.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Currently, using the NDB operator, you can only provision a database on the Nutanix Cloud Infrastructure(on-prem).This project aims to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on cloud - specifically on AWS EC2. We aim to do this in a scalable way so that changing the cloud provider to say Azure or GCP would require changes only to the configuration file(CRD Manifest).&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
The important components here are:&lt;br /&gt;
# Kubernetes Cluster&lt;br /&gt;
# Kubernetes Service that almost acts like a gateway to the provisioned database.&lt;br /&gt;
# Any of the cloud platforms (AWS in our case)&lt;br /&gt;
&lt;br /&gt;
The application pods that want to communicate with the database do so through the K8 service and endpoint that is created as part of the last step of provisioning. The workflow is explained in detail in the next section.&lt;br /&gt;
 &lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: MOST OF THESE CHANGES HAVE NOT BEEN MADE NOW SINCE OUR TEAM HAS NOT OBTAINED ACCESS TO CONNECT THE SERVICE WITH EC2 INSTANCE FROM THE NUTANIX TEAM'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
'''Note: We have created this file, but currently have template values. After getting access, we will fill them accordingly'''&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
'''Note: This can only be implemented after obtaining the access. We are still waiting on it and will implement it as soon as we obtain it.'''&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
'''Note: This is the last part of the serial steps. The previous steps must be completed in order to write this logic.'''&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future Implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance&lt;br /&gt;
* Construct provision request for provisioning DB in cloud&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType'''&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
Arvind Srinivas Subramanian (asubram9)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147707</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147707"/>
		<updated>2023-03-22T01:21:37Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Second Draft&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Efficiently manage hundreds to&lt;br /&gt;
thousands of databases. We need to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single&lt;br /&gt;
Instance databases on AWS EC2. &lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle '''remoteType''' : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created ''''remoteType'''' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: MOST OF THESE CHANGES HAVE NOT BEEN MADE NOW SINCE OUR TEAM HAS NOT OBTAINED ACCESS TO CONNECT THE SERVICE WITH EC2 INSTANCE FROM THE NUTANIX TEAM'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
'''Note: We have created this file, but currently have template values. After getting access, we will fill them accordingly'''&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
   ...&lt;br /&gt;
   type NDBClient struct {&lt;br /&gt;
	   username   string&lt;br /&gt;
	   password   string&lt;br /&gt;
	   url        string&lt;br /&gt;
	   remoteType string&lt;br /&gt;
	   client     *http.Client&lt;br /&gt;
   }&lt;br /&gt;
   ...&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem). These functions constitute the primary part of the provisioning workflow.&lt;br /&gt;
&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud. Adding fields with appropriate data type is required here to serialize and deserialize requests properly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud. This method should take care of a helper method logic to determine the correct request data structure and endpoint to be used. We have currently implemented a part of this logic as part of the ndbClient. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud '''remoteType''' too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance&lt;br /&gt;
* Construct provision request for provisioning DB in cloud&lt;br /&gt;
* Add logic to select proper endpoint and request based on the '''remoteType'''&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
Arvind Srinivas Subramanian (asubram9)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147700</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147700"/>
		<updated>2023-03-22T01:15:01Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: First draft&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Efficiently manage hundreds to&lt;br /&gt;
thousands of databases. We need to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single&lt;br /&gt;
Instance databases on AWS EC2. &lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
[[File:Ntnx4arch.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
[[File:Workflowntnx4.png|1000px|]]&lt;br /&gt;
&lt;br /&gt;
* Apply the instance CRD manifest file to create an instance of NDB resource in the K8 cluster&lt;br /&gt;
* The operator controller runs its logic to build a provisioning request and sends it to the Nutanix Database Service endpoint pertaining to provision in cloud(AWS in our case)&lt;br /&gt;
* The reconcile function loops to take actions depending on the status of provisioning - Empty, Provisioning and Ready&lt;br /&gt;
* Once the database is provisioned in AWS and the status is in Ready, we set up a Kubernetes networking service and an endpoint with the same name in the cluster. Any application pod in this cluster will communicate with this endpoint rather than communicating directly with the database. &lt;br /&gt;
&lt;br /&gt;
==Design==&lt;br /&gt;
===Single Responsibility Principle(SRP)===&lt;br /&gt;
SRP is adopted here to separate the business logic of the operator from the reconciliation loop.&lt;br /&gt;
&lt;br /&gt;
* The logic to handle remoteType : cloud is handled separately from the client (ndbClient) through helper methods - This ensures that the client is responsible for only making get/post requests to the specified endpoint without worrying about whether it is for on-prem or cloud. &lt;br /&gt;
&lt;br /&gt;
* The reconciliation loop is responsible for ensuring that the state of the system matches the desired state declared by the operator. It listens to events from the Kubernetes API server and performs the necessary actions to reconcile the system to the desired state. The business logic of the operator, on the other hand, is responsible for implementing the higher-level functionality of the operator, such as managing the deployment of a complex application or managing a database cluster.&lt;br /&gt;
&lt;br /&gt;
* Even within the reconciliation logic, there are clear indications of following SRP. We have a separate file utils/secret.go to implement GetDataFromSecret and GetAllDataFromSecret - logic to get data from the resource denoted by the name/namespace combination.&lt;br /&gt;
&lt;br /&gt;
===Operator Pattern===&lt;br /&gt;
The operator pattern is based on the idea of declarative configuration management. Rather than writing imperative code to perform a series of steps to reach a desired state, an operator declares the desired state of the system and uses the Kubernetes API to perform the necessary actions to reconcile the system to that desired state.&lt;br /&gt;
&lt;br /&gt;
We continue to follow this pattern for our implementation. We have a manifest file for a NDB custom resource responsible for provisioning and managing PostgreSQL in AWS and the logic for it is handled in the controller code (controllers/database_controllers.go)&lt;br /&gt;
&lt;br /&gt;
  EXAMPLE MANIFEST FILE&lt;br /&gt;
   ...&lt;br /&gt;
   &lt;br /&gt;
   apiVersion: ndb.nutanix.com/v1alpha1&lt;br /&gt;
   kind: Database&lt;br /&gt;
   metadata:&lt;br /&gt;
     name: db&lt;br /&gt;
   spec:&lt;br /&gt;
     ndb:&lt;br /&gt;
       remoteType: &amp;quot;cloud&amp;quot;&lt;br /&gt;
       clusterId: &amp;quot;Nutanix Cluster Id&amp;quot;&lt;br /&gt;
       credentialSecret : ndb-secret-name&lt;br /&gt;
       server: https://[NDB IP]:8443/era/v1.0&lt;br /&gt;
       skipCertificateVerification: true&lt;br /&gt;
     databaseInstance:&lt;br /&gt;
       databaseInstanceName: &amp;quot;Database Instance Name&amp;quot;&lt;br /&gt;
       databaseNames:&lt;br /&gt;
         - database_one&lt;br /&gt;
         - database_two&lt;br /&gt;
         - database_three&lt;br /&gt;
       credentialSecret: db-instance-secret-name&lt;br /&gt;
       size: 10&lt;br /&gt;
       timezone: &amp;quot;UTC&amp;quot;&lt;br /&gt;
       type: postgres&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
The controller logic and related helper code will use the newly created 'remoteType' field to select the NDS endpoint and construct provisionRequest appropriately. We have distinguished the configuration(Manifest file) and the logic(controller code) this way.&lt;br /&gt;
&lt;br /&gt;
==Implementation related to this project==&lt;br /&gt;
&lt;br /&gt;
This section will explain the changes that are made/to-be-made as part of this project.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: MOST OF THESE CHANGES HAVE NOT BEEN MADE NOW SINCE OUR TEAM HAS NOT OBTAINED ACCESS TO CONNECT THE SERVICE WITH EC2 INSTANCE FROM THE NUTANIX TEAM'''&lt;br /&gt;
'''We will tell the changes that have not been made and they will be made in the future'''&lt;br /&gt;
&lt;br /&gt;
===Generate Secrets manifest and set name field + credentials in it===&lt;br /&gt;
This secrets.yaml manifest file is to create a Secret resource instance that is will have the data regarding the NDB instance name and database credentials.&lt;br /&gt;
&lt;br /&gt;
'''Note: We have created this file, but currently have template values. After getting access, we will fill them accordingly'''&lt;br /&gt;
&lt;br /&gt;
===Modify database_types.go to have allow remote types===&lt;br /&gt;
We have added a '''remoteType''' field to the NDB struct along with validation markers&lt;br /&gt;
...&lt;br /&gt;
   // +kubebuilder:validation:Required;Enum=cloud;on-prem&lt;br /&gt;
   RemoteType string `json:&amp;quot;remoteType&amp;quot;`&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Add helper method to figure out endpoint depending on remote type===&lt;br /&gt;
We currently have this logic as part of the ndbClient and should be moved to a separate helper method. The purpose of this function is to determine if the provisioning is for on-prem or cloud.&lt;br /&gt;
&lt;br /&gt;
===Modify helper methods to generate request, provision database and setup connectivity===&lt;br /&gt;
Below are the functions that are core to the provisioning logic. They are written for provisioning a database in Nutanix cloud infrastructure(on-prem)&lt;br /&gt;
====GenerateProvisioningRequest(database_reconciler_helpers.go)====&lt;br /&gt;
This data structure should be modified/extended to include fields that are required to be part of the JSON request for the NDS endpoint that pertains to provisioning on cloud.&lt;br /&gt;
====ProvisionDatabase(database_reconciler_helpers.go)====&lt;br /&gt;
This method takes care of posting a request to the NDS endpoint to provision database. Should be modified to allow the same on cloud.&lt;br /&gt;
====SetupConnectivity(database_reconciler_helpers.go)====&lt;br /&gt;
This function checks and creates a new service (without label selectors) if it does not exists and also sets up the database as the owner for the created service. This also checks and creates an endpoints object for the service if it does not already exists.&lt;br /&gt;
&lt;br /&gt;
==Test plan==&lt;br /&gt;
===Runing tests===&lt;br /&gt;
Pull the code, cd into the directory and run:&lt;br /&gt;
 &lt;br /&gt;
   make test&lt;br /&gt;
&lt;br /&gt;
This runs all the required tests as specified in the Makefile.&lt;br /&gt;
&lt;br /&gt;
===Test Scenarios===&lt;br /&gt;
# Testing construction of provision request - All test cases for this that are currently written for on-prem must work for cloud remoteType too. &lt;br /&gt;
# Additional tests for fields that are mandatory for requests constructed for provisioning in cloud&lt;br /&gt;
# Verify that the correct API endpoints are being hit based on the remote type specified - On-Prem should hit 0.9 API and cloud should hit 1.0 API&lt;br /&gt;
# Test the connectivity between the K8 service and the deployed DB on EC2. &lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before Manual testing===&lt;br /&gt;
Manual testing would require you to get access to the Nutanix Database Service&lt;br /&gt;
&lt;br /&gt;
# Obtain access to Nutanix Database Service SASS and access keys for the AWS EC2 instance.&lt;br /&gt;
&lt;br /&gt;
# Add the appropriate credentials to the secrets.yaml file.&lt;br /&gt;
&lt;br /&gt;
# Apply your manifest instance using :&lt;br /&gt;
   kubectl apply -f &amp;lt;your manifest instance file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can verify the status of provisioning in your NDB dashboard.&lt;br /&gt;
&lt;br /&gt;
==Future implementation==&lt;br /&gt;
* Obtain EC2 keys and connect NDS to the EC2 instance&lt;br /&gt;
* Construct provision request for provisioning DB in cloud&lt;br /&gt;
* Add logic to select proper endpoint and request based on the remoteType&lt;br /&gt;
* Add logic to de-provision the database.&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
Repo(Public): https://github.com/arvindsrinivas1/ndb-operator&lt;br /&gt;
Pull Request: https://github.com/nutanix-cloud-native/ndb-operator/pull/73&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;br /&gt;
Arvind Srinivas Subramanian (asubram9)&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Workflowntnx4.png&amp;diff=147654</id>
		<title>File:Workflowntnx4.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Workflowntnx4.png&amp;diff=147654"/>
		<updated>2023-03-21T23:48:26Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Workflow of how NDB is used to provision database&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Workflow of how NDB is used to provision database&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147653</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147653"/>
		<updated>2023-03-21T23:47:45Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Architecture&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Efficiently manage hundreds to&lt;br /&gt;
thousands of databases. We need to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single&lt;br /&gt;
Instance databases on AWS EC2. &lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
[[File:Ntnx4arch.png|1200px|]]&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
&lt;br /&gt;
==Implementation==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before testing===&lt;br /&gt;
&lt;br /&gt;
===Things to test===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Future implementation==&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Ntnx4arch.png&amp;diff=147650</id>
		<title>File:Ntnx4arch.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Ntnx4arch.png&amp;diff=147650"/>
		<updated>2023-03-21T23:43:31Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Architecture for the ntnx4 project&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Architecture for the ntnx4 project&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Architecture.png&amp;diff=147649</id>
		<title>File:Architecture.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Architecture.png&amp;diff=147649"/>
		<updated>2023-03-21T23:35:58Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Architecture of the entire system, mentioning all the components involved in its working&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Architecture of the entire system, mentioning all the components involved in its working&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147648</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147648"/>
		<updated>2023-03-21T23:35:06Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Problem Statement&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
Nutanix Database Service is the only hybrid cloud database-as-a-service for Microsoft SQL&lt;br /&gt;
Server, Oracle Database, PostgreSQL, MongoDB, and MySQL. Efficiently manage hundreds to&lt;br /&gt;
thousands of databases. We need to extend the Nutanix Database Service Kubernetes operator to provision Postgres Single&lt;br /&gt;
Instance databases on AWS EC2. &lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
&lt;br /&gt;
==Implementation==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before testing===&lt;br /&gt;
&lt;br /&gt;
===Things to test===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Future implementation==&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147608</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147608"/>
		<updated>2023-03-21T21:38:07Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Topics&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;br /&gt;
&lt;br /&gt;
==Problem Statement==&lt;br /&gt;
&lt;br /&gt;
==Goal of this Topic==&lt;br /&gt;
&lt;br /&gt;
==Architecture==&lt;br /&gt;
&lt;br /&gt;
==Workflow==&lt;br /&gt;
==Implementation==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Test Plan==&lt;br /&gt;
&lt;br /&gt;
===Actions to be taken before testing===&lt;br /&gt;
&lt;br /&gt;
===Things to test===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Future implementation==&lt;br /&gt;
&lt;br /&gt;
==Github==&lt;br /&gt;
&lt;br /&gt;
==Contributors==&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147584</id>
		<title>CSC/ECE 517 Spring 2023- NTNX-4. Extend NDB operator provision postregresql aws</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023-_NTNX-4._Extend_NDB_operator_provision_postregresql_aws&amp;diff=147584"/>
		<updated>2023-03-21T19:30:29Z</updated>

		<summary type="html">&lt;p&gt;Asubram9: Created page with &amp;quot;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==NTNX-4. Extend Nutanix Database Service Kubernetes operator to provision Postgres Single Instance databases on AWS EC2==&lt;/div&gt;</summary>
		<author><name>Asubram9</name></author>
	</entry>
</feed>