-
Notifications
You must be signed in to change notification settings - Fork 5
Create feedback #601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Create feedback #601
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
e3dadb5
WIP: create feedback route
loiswells97 195eaaf
fixing
loiswells97 7e78390
Update feedback.rb
loiswells97 1dc0f11
improving feedback validations and adding factory and model tests
loiswells97 8fa4791
adding abilities
loiswells97 a560c73
testing feedback create operation
loiswells97 56f32da
tidying
loiswells97 1ec8287
initial rubocop fixes
loiswells97 1631cf9
Merge branch 'main' into create-feedback
loiswells97 826e1a4
updating schema version
loiswells97 788b447
remove unneeded test
loiswells97 8a1d61c
some rubocop fixes
loiswells97 054be3f
refactoring feedback factory to please rubocop
loiswells97 c57741d
adding authorisation for feedback creation
loiswells97 40ab7c5
add feedback auditing
loiswells97 7e1ea4a
tidying
loiswells97 3ad056b
tidying and request specs
loiswells97 581e3aa
rubocop fixes
loiswells97 59ecd17
adding comments to explain params
loiswells97 066ee40
removing commented code
loiswells97 5678604
fixing papertrail by adding meta migration to versions table
loiswells97 96c4b92
remove redundant operations directory
loiswells97 a3c612b
factoring out teacher project ids
loiswells97 8875291
factoring feedback template out into a partial
loiswells97 2adbcef
simplify feedback creation error handling
loiswells97 f08ba8c
Merge branch 'main' into create-feedback
loiswells97 1c801ae
refactor error handling slightly to catch non-validation errors
loiswells97 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module Api | ||
| class FeedbackController < ApiController | ||
| before_action :authorize_user | ||
| load_and_authorize_resource :feedback | ||
|
|
||
| def create | ||
| result = Feedback::Create.call(feedback_params: feedback_create_params) | ||
|
|
||
| if result.success? | ||
| @feedback = result[:feedback] | ||
| render :show, formats: [:json], status: :created | ||
| else | ||
| render json: { error: result[:error] }, status: :unprocessable_entity | ||
| end | ||
| end | ||
|
|
||
| # These params are used to authorize the resource with CanCanCan. The project identifier is sent in the URL, | ||
| # but these params need to match the shape of the feedback object whiich is attached to the SchoolProject, | ||
| # not the Project. | ||
| def feedback_params | ||
| school_project = Project.find_by(identifier: base_params[:identifier])&.school_project | ||
| feedback_create_params.except(:identifier).merge( | ||
| school_project_id: school_project&.id | ||
| ) | ||
| end | ||
|
|
||
| # These params are used to create the feedback in the Feedback::Create operation. The project_id parameter, | ||
| # which is automatically named by Rails based on the route structure, is renamed to identifier for readability, | ||
| # as it is actually the human-readable project_identifier, not the project_id. | ||
| def feedback_create_params | ||
| base_params.merge(user_id: current_user.id) | ||
| end | ||
|
|
||
| def url_params | ||
| permitted_params = params.permit(:project_id) | ||
| { identifier: permitted_params[:project_id] } | ||
| end | ||
|
|
||
| def base_params | ||
| params.fetch(:feedback, {}).permit( | ||
| :content | ||
| ).merge(url_params) | ||
| end | ||
| end | ||
| end | ||
danhalson marked this conversation as resolved.
Show resolved
Hide resolved
danhalson marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| class Feedback < ApplicationRecord | ||
| belongs_to :school_project | ||
danhalson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| validates :content, presence: true | ||
| validates :user_id, presence: true | ||
| validate :user_has_the_school_owner_or_school_teacher_role_for_the_school | ||
| validate :parent_project_belongs_to_lesson | ||
| validate :parent_project_belongs_to_school_class | ||
| validate :user_is_the_class_teacher_for_the_school_project | ||
|
|
||
| has_paper_trail( | ||
| meta: { | ||
| meta_school_project_id: ->(f) { f.school_project&.id }, | ||
| meta_school_id: ->(f) { f.school_project&.school_id } | ||
| } | ||
| ) | ||
|
|
||
| private | ||
|
|
||
| def user_has_the_school_owner_or_school_teacher_role_for_the_school | ||
danhalson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| school = school_project&.school | ||
| return unless user_id_changed? && errors.blank? && school | ||
|
|
||
| user = User.new(id: user_id) | ||
| return if user.school_owner?(school) | ||
| return if user.school_teacher?(school) | ||
|
|
||
| msg = "'#{user_id}' does not have the 'school-owner' or 'school-teacher' role for organisation '#{school.id}'" | ||
| errors.add(:user, msg) | ||
| end | ||
|
|
||
| def parent_project_belongs_to_lesson | ||
| parent_project = school_project&.project&.parent | ||
| return if parent_project&.lesson_id.present? | ||
|
|
||
| msg = "Parent project '#{parent_project&.id}' does not belong to a 'lesson'" | ||
| errors.add(:user, msg) | ||
| end | ||
|
|
||
| def parent_project_belongs_to_school_class | ||
| parent_project = school_project&.project&.parent | ||
| return if parent_project&.lesson&.school_class_id.present? | ||
|
|
||
| msg = "Parent project '#{parent_project&.id}' does not belong to a 'school-class'" | ||
| errors.add(:user, msg) | ||
| end | ||
|
|
||
| def user_is_the_class_teacher_for_the_school_project | ||
| return if !school_project || school_class&.teacher_ids&.include?(user_id) | ||
|
|
||
| errors.add(:user, "'#{user_id}' is not the 'school-teacher' for school_project '#{school_project.id}'") | ||
| end | ||
|
|
||
| def school_class | ||
| school_project&.project&.parent&.lesson&.school_class | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| json.call( | ||
| feedback, | ||
| :id, | ||
| :school_project_id, | ||
| :user_id, | ||
| :content, | ||
| :created_at, | ||
| :updated_at | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| json.partial! 'feedback', feedback: @feedback |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| class CreateFeedback < ActiveRecord::Migration[7.2] | ||
| def change | ||
| create_table :feedback, id: :uuid do |t| | ||
| t.references :school_project, foreign_key: true, type: :uuid | ||
| t.text :content | ||
| t.uuid :user_id, null: false | ||
| t.timestamps | ||
| end | ||
| end | ||
| end |
5 changes: 5 additions & 0 deletions
5
db/migrate/20251021162845_add_meta_school_project_id_to_versions.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| class AddMetaSchoolProjectIdToVersions < ActiveRecord::Migration[7.2] | ||
| def change | ||
| add_column :versions, :meta_school_project_id, :string | ||
| end | ||
| end |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
danhalson marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| class Feedback | ||
| class Create | ||
| class << self | ||
| def call(feedback_params:) | ||
| response = OperationResponse.new | ||
| response[:feedback] = build_feedback(feedback_params) | ||
| response[:feedback].save! | ||
| response | ||
| rescue StandardError => e | ||
| Sentry.capture_exception(e) | ||
| if response[:feedback].present? && response[:feedback].errors.any? | ||
| errors = response[:feedback]&.errors&.full_messages&.join(',') | ||
| response[:error] = "Error creating feedback: #{errors}" | ||
| else | ||
| response[:error] = "Error creating feedback: #{e.message}" | ||
| end | ||
| response | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def build_feedback(feedback_hash) | ||
| project = Project.find_by(identifier: feedback_hash[:identifier]) | ||
| school_project = project&.school_project | ||
|
|
||
| # replace identifier with school_project_id | ||
| feedback_hash[:school_project_id] = school_project&.id | ||
| feedback_hash.delete(:identifier) | ||
| Feedback.new(feedback_hash) | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require 'rails_helper' | ||
|
|
||
| RSpec.describe Feedback::Create, type: :unit do | ||
| let(:school) { create(:school) } | ||
| let(:student) { create(:student, school:) } | ||
| let(:teacher) { create(:teacher, school:) } | ||
| let(:school_class) { create(:school_class, teacher_ids: [teacher.id], school:) } | ||
| let(:class_student) { create(:class_student, school_class:, student_id: student.id) } | ||
| let(:lesson) { create(:lesson, school:, school_class:, user_id: teacher.id) } | ||
| let(:teacher_project) { create(:project, user_id: teacher.id, school:, lesson:) } | ||
| let(:student_project) { create(:project, user_id: class_student.student_id, school:, lesson:, parent: teacher_project) } | ||
|
|
||
| let(:feedback_params) do | ||
| { | ||
| content: 'Great job!', | ||
| user_id: teacher.id, | ||
| identifier: student_project.identifier | ||
| } | ||
| end | ||
|
|
||
| context 'when a teacher' do | ||
| before do | ||
| allow(User).to receive(:from_userinfo).with(ids: teacher.id).and_return([teacher]) | ||
| end | ||
|
|
||
| it 'returns a successful operation response' do | ||
| response = described_class.call(feedback_params:) | ||
| expect(response.success?).to be(true) | ||
| end | ||
|
|
||
| it 'creates a piece of feedback' do | ||
| expect { described_class.call(feedback_params:) }.to change(Feedback, :count).by(1) | ||
| end | ||
|
|
||
| it 'returns the feedback in the operation response' do | ||
| response = described_class.call(feedback_params:) | ||
| expect(response[:feedback]).to be_a(Feedback) | ||
| end | ||
|
|
||
| it 'assigns the content' do | ||
| response = described_class.call(feedback_params:) | ||
| expect(response[:feedback].content).to eq('Great job!') | ||
| end | ||
|
|
||
| it 'assigns the user_id' do | ||
| response = described_class.call(feedback_params:) | ||
| expect(response[:feedback].user_id).to eq(teacher.id) | ||
| end | ||
|
|
||
| it 'assigns the school_project_id' do | ||
| response = described_class.call(feedback_params:) | ||
| expect(response[:feedback].school_project_id).to eq(student_project.school_project.id) | ||
| end | ||
| end | ||
|
|
||
| context 'when feedback creation fails' do | ||
| let(:rogue_project) { create(:project, user_id: student.id) } | ||
| let(:feedback_params) do | ||
| { | ||
| content: nil, | ||
| user_id: teacher.id, | ||
| identifier: rogue_project.identifier | ||
| } | ||
| end | ||
|
|
||
| before do | ||
| allow(Sentry).to receive(:capture_exception) | ||
| end | ||
|
|
||
| it 'does not create feedback' do | ||
| expect { described_class.call(feedback_params:) }.not_to change(Feedback, :count) | ||
| end | ||
|
|
||
| it 'returns a failed operation response' do | ||
| response = described_class.call(feedback_params:) | ||
| expect(response.failure?).to be(true) | ||
| end | ||
|
|
||
| it 'returns the error message in the operation response' do | ||
| response = described_class.call(feedback_params:) | ||
| expect(response[:error]).to match(/Error creating feedback/) | ||
| end | ||
|
|
||
| it 'raises school project not found error when no school project' do | ||
| response = described_class.call(feedback_params:) | ||
| expect(response[:error]).to match(/School project must exist/) | ||
| end | ||
|
|
||
| it 'sent the exception to Sentry' do | ||
| described_class.call(feedback_params:) | ||
| expect(Sentry).to have_received(:capture_exception).with(kind_of(StandardError)) | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| FactoryBot.define do | ||
| factory :feedback do | ||
| content { Faker::Lorem.paragraph } | ||
| user_id { nil } | ||
| school_project { nil } | ||
|
|
||
| after(:build) do |feedback, _evaluator| | ||
| school = create(:school) | ||
| teacher = create(:teacher, school: school) | ||
| student = create(:student, school: school) | ||
| school_class = create(:school_class, school: school, teacher_ids: [teacher.id]) | ||
| create(:class_student, school_class: school_class, student_id: student.id) | ||
| parent_project = create(:project, user_id: teacher.id, school: school, lesson: create(:lesson, school_class: school_class, user_id: teacher.id)) | ||
| student_project = create(:project, parent: parent_project, user_id: student.id) | ||
|
|
||
| feedback.user_id ||= teacher.id | ||
| feedback.school_project ||= create(:school_project, school: school, project: student_project) | ||
| end | ||
| end | ||
| end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.