-
Notifications
You must be signed in to change notification settings - Fork 80
ScholAIstic Integration #8035
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
+3,126
−47
Merged
ScholAIstic Integration #8035
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
1f0761b
chore(babel): add `t` for i18n message extraction
purfectliterature 1d061bb
feat(condition): support custom condition display name
purfectliterature fa2dec4
fix: don't use dynamic values for Descriptor
purfectliterature 592a3db
feat(controllers/course/admin): support `publicly_accessible?` actions
purfectliterature 81727e5
feat(scholaistic): add settings, course linking
purfectliterature d3d95b1
feat(sidebar): support `exact` for exact `activePath` matching
purfectliterature cacafb5
feat: add scholaistic integration
purfectliterature 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
81 changes: 81 additions & 0 deletions
81
app/controllers/components/course/scholaistic_component.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,81 @@ | ||
# frozen_string_literal: true | ||
class Course::ScholaisticComponent < SimpleDelegator | ||
include Course::ControllerComponentHost::Component | ||
include Course::Scholaistic::Concern | ||
|
||
def self.display_name | ||
I18n.t('components.scholaistic.name') | ||
end | ||
|
||
def self.enabled_by_default? | ||
false | ||
end | ||
|
||
def sidebar_items | ||
main_sidebar_items + settings_sidebar_items | ||
end | ||
|
||
private | ||
|
||
def main_sidebar_items | ||
return [] unless scholaistic_course_linked? | ||
|
||
student_sidebar_items + admin_sidebar_items | ||
end | ||
|
||
def student_sidebar_items | ||
[ | ||
{ | ||
key: :scholaistic_assessments, | ||
icon: :chatbot, | ||
title: settings.assessments_title || I18n.t('course.scholaistic.assessments'), | ||
weight: 4, | ||
path: course_scholaistic_assessments_path(current_course) | ||
} | ||
] + assistant_sidebar_items | ||
end | ||
|
||
def assistant_sidebar_items | ||
ScholaisticApiService.assistants!(current_course).map do |assistant| | ||
{ | ||
key: "scholaistic_assistant_#{assistant[:id]}", | ||
icon: :chatbot, | ||
title: assistant[:sidebar_title] || assistant[:title], | ||
weight: 4.5, | ||
path: course_scholaistic_assistant_path(current_course, assistant[:id]) | ||
} | ||
end | ||
rescue StandardError => e | ||
Rails.logger.error("Failed to load Scholaistic assistants: #{e.message}") | ||
raise e unless Rails.env.production? | ||
|
||
[] | ||
end | ||
|
||
def admin_sidebar_items | ||
return [] unless can?(:manage_scholaistic_assistants, current_course) | ||
|
||
[ | ||
{ | ||
key: :scholaistic_assistants, | ||
type: :admin, | ||
icon: :chatbot, | ||
title: I18n.t('components.scholaistic.manage_assistants'), | ||
weight: 9, | ||
path: course_scholaistic_assistants_path(current_course), | ||
exact: true | ||
} | ||
] | ||
end | ||
|
||
def settings_sidebar_items | ||
[ | ||
{ | ||
type: :settings, | ||
title: I18n.t('components.scholaistic.name'), | ||
weight: 5, | ||
path: course_admin_scholaistic_path(current_course) | ||
} | ||
] | ||
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,11 @@ | ||
# frozen_string_literal: true | ||
module Course::Scholaistic::Concern | ||
extend ActiveSupport::Concern | ||
|
||
private | ||
|
||
def scholaistic_course_linked? | ||
current_course.component_enabled?(Course::ScholaisticComponent) && | ||
current_course.settings(:course_scholaistic_component)&.integration_key.present? | ||
end | ||
end |
5 changes: 5 additions & 0 deletions
5
app/controllers/course/achievement/condition/scholaistic_assessments_controller.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 @@ | ||
# frozen_string_literal: true | ||
class Course::Achievement::Condition::ScholaisticAssessmentsController < | ||
Course::Condition::ScholaisticAssessmentsController | ||
include Course::AchievementConditionalConcern | ||
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
74 changes: 74 additions & 0 deletions
74
app/controllers/course/admin/scholaistic_settings_controller.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,74 @@ | ||
# frozen_string_literal: true | ||
class Course::Admin::ScholaisticSettingsController < Course::Admin::Controller | ||
skip_forgery_protection only: :confirm_link_course | ||
skip_authorize_resource :course, only: :confirm_link_course | ||
|
||
def edit | ||
render_settings | ||
end | ||
|
||
def update | ||
if @settings.update(params) && current_course.save | ||
render_settings | ||
else | ||
render json: { errors: @settings.errors }, status: :bad_request | ||
end | ||
end | ||
|
||
def confirm_link_course | ||
key = ScholaisticApiService.parse_link_course_callback_request(request, params) | ||
head :bad_request and return if key.blank? | ||
|
||
@settings.update(integration_key: key, last_synced_at: nil) && current_course.save | ||
end | ||
|
||
def link_course | ||
head :bad_request and return if @settings.integration_key.present? | ||
|
||
render json: { | ||
redirectUrl: ScholaisticApiService.link_course_url!( | ||
course_title: current_course.title, | ||
course_url: course_url(current_course), | ||
callback_url: course_admin_scholaistic_confirm_link_course_url(current_course) | ||
) | ||
} | ||
end | ||
|
||
def unlink_course | ||
head :ok and return if @settings.integration_key.blank? | ||
|
||
ActiveRecord::Base.transaction do | ||
ScholaisticApiService.unlink_course!(@settings.integration_key) | ||
|
||
raise ActiveRecord::Rollback unless current_course.scholaistic_assessments.destroy_all | ||
|
||
@settings.update(integration_key: nil, last_synced_at: nil) | ||
current_course.save! | ||
end | ||
|
||
render_settings | ||
rescue ActiveRecord::Rollback | ||
render json: { errors: @settings.errors }, status: :bad_request | ||
end | ||
|
||
protected | ||
|
||
def publicly_accessible? | ||
action_name.to_sym == :confirm_link_course | ||
end | ||
|
||
private | ||
|
||
def scholaistic_settings_params | ||
params.require(:settings_scholaistic_component).permit(:assessments_title) | ||
end | ||
|
||
def component | ||
current_component_host[:course_scholaistic_component] | ||
end | ||
|
||
def render_settings | ||
@ping_result = ScholaisticApiService.ping_course(@settings.integration_key) if @settings.integration_key.present? | ||
render 'edit' | ||
end | ||
end |
5 changes: 5 additions & 0 deletions
5
app/controllers/course/assessment/condition/scholaistic_assessments_controller.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 @@ | ||
# frozen_string_literal: true | ||
class Course::Assessment::Condition::ScholaisticAssessmentsController < | ||
Course::Condition::ScholaisticAssessmentsController | ||
include Course::AssessmentConditionalConcern | ||
end |
56 changes: 56 additions & 0 deletions
56
app/controllers/course/condition/scholaistic_assessments_controller.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,56 @@ | ||
# frozen_string_literal: true | ||
class Course::Condition::ScholaisticAssessmentsController < Course::ConditionsController | ||
load_resource :scholaistic_assessment_condition, class: Course::Condition::ScholaisticAssessment.name, parent: false | ||
before_action :set_course_and_conditional, only: [:create] | ||
authorize_resource :scholaistic_assessment_condition, class: Course::Condition::ScholaisticAssessment.name | ||
|
||
def index | ||
render_available_scholaistic_assessments | ||
end | ||
|
||
def show | ||
render_available_scholaistic_assessments | ||
end | ||
|
||
def create | ||
try_to_perform @scholaistic_assessment_condition.save | ||
end | ||
|
||
def update | ||
try_to_perform @scholaistic_assessment_condition.update(scholaistic_assessment_condition_params) | ||
end | ||
|
||
def destroy | ||
try_to_perform @scholaistic_assessment_condition.destroy | ||
end | ||
|
||
private | ||
|
||
def render_available_scholaistic_assessments | ||
scholaistic_assessments = current_course.scholaistic_assessments | ||
existing_conditions = @conditional.specific_conditions - [@scholaistic_assessment_condition] | ||
@available_assessments = (scholaistic_assessments - existing_conditions.map(&:dependent_object)).sort_by(&:title) | ||
render 'available_scholaistic_assessments' | ||
end | ||
|
||
def try_to_perform(operation_succeeded) | ||
if operation_succeeded | ||
success_action | ||
else | ||
render json: { errors: @scholaistic_assessment_condition.errors }, status: :bad_request | ||
end | ||
end | ||
|
||
def scholaistic_assessment_condition_params | ||
params.require(:condition_scholaistic_assessment).permit(:scholaistic_assessment_id) | ||
end | ||
|
||
def set_course_and_conditional | ||
@scholaistic_assessment_condition.course = current_course | ||
@scholaistic_assessment_condition.conditional = @conditional | ||
end | ||
|
||
def component | ||
current_component_host[:course_scholaistic_component] | ||
end | ||
end |
24 changes: 24 additions & 0 deletions
24
app/controllers/course/scholaistic/assistants_controller.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,24 @@ | ||
# frozen_string_literal: true | ||
class Course::Scholaistic::AssistantsController < Course::Scholaistic::Controller | ||
def index | ||
authorize! :manage_scholaistic_assistants, current_course | ||
|
||
@embed_src = ScholaisticApiService.embed!( | ||
current_course_user, | ||
ScholaisticApiService.assistants_path, | ||
request.origin | ||
) | ||
end | ||
|
||
def show | ||
authorize! :read_scholaistic_assistants, current_course | ||
|
||
@assistant_title = ScholaisticApiService.assistant!(current_course, params[:id])[:title] | ||
|
||
@embed_src = ScholaisticApiService.embed!( | ||
current_course_user, | ||
ScholaisticApiService.assistant_path(params[:id]), | ||
request.origin | ||
) | ||
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,16 @@ | ||
# frozen_string_literal: true | ||
class Course::Scholaistic::Controller < Course::ComponentController | ||
include Course::Scholaistic::Concern | ||
|
||
before_action :not_found_if_scholaistic_course_not_linked | ||
|
||
private | ||
|
||
def component | ||
current_component_host[:course_scholaistic_component] | ||
end | ||
|
||
def not_found_if_scholaistic_course_not_linked | ||
head :not_found unless scholaistic_course_linked? | ||
end | ||
end |
105 changes: 105 additions & 0 deletions
105
app/controllers/course/scholaistic/scholaistic_assessments_controller.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,105 @@ | ||
# frozen_string_literal: true | ||
class Course::Scholaistic::ScholaisticAssessmentsController < Course::Scholaistic::Controller | ||
load_and_authorize_resource :scholaistic_assessment, through: :course, class: Course::ScholaisticAssessment.name | ||
|
||
before_action :sync_scholaistic_assessments!, only: [:index, :show, :edit] | ||
|
||
def index | ||
submissions_status_hash = ScholaisticApiService.submissions!( | ||
@scholaistic_assessments.map(&:upstream_id), | ||
current_course_user | ||
) | ||
|
||
@assessments_status = @scholaistic_assessments.to_h do |assessment| | ||
submission_status = submissions_status_hash[assessment.upstream_id]&.[](:status) | ||
|
||
[assessment.id, | ||
if submission_status == :graded | ||
:submitted | ||
elsif submission_status.present? | ||
submission_status | ||
else | ||
(can?(:attempt, assessment) && (assessment.start_at <= Time.zone.now)) ? :open : :unavailable | ||
end] | ||
end | ||
end | ||
|
||
def new | ||
@embed_src = ScholaisticApiService.embed!( | ||
current_course_user, | ||
ScholaisticApiService.new_assessment_path, | ||
request.origin | ||
) | ||
end | ||
|
||
def show | ||
upstream_id = @scholaistic_assessment.upstream_id | ||
|
||
@embed_src = | ||
ScholaisticApiService.embed!( | ||
current_course_user, | ||
if can?(:update, @scholaistic_assessment) | ||
ScholaisticApiService.edit_assessment_path(upstream_id) | ||
else | ||
ScholaisticApiService.assessment_path(upstream_id) | ||
end, | ||
request.origin | ||
) | ||
end | ||
|
||
def edit | ||
@embed_src = ScholaisticApiService.embed!( | ||
current_course_user, | ||
ScholaisticApiService.edit_assessment_details_path(@scholaistic_assessment.upstream_id), | ||
request.origin | ||
) | ||
end | ||
|
||
def update | ||
if @scholaistic_assessment.update(update_params) | ||
head :ok | ||
else | ||
render json: { errors: @scholaistic_assessment.errors.full_messages.to_sentence }, status: :bad_request | ||
end | ||
end | ||
|
||
private | ||
|
||
def update_params | ||
params.require(:scholaistic_assessment).permit(:base_exp) | ||
end | ||
|
||
def sync_scholaistic_assessments! | ||
purfectliterature marked this conversation as resolved.
Show resolved
Hide resolved
|
||
response = ScholaisticApiService.assessments!(current_course) | ||
|
||
# TODO: The SQL queries will scale proportionally with `response[:assessments].size`, | ||
# but we won't always have to sync all assessments since there's `last_synced_at`. | ||
# In the future, we can optimise this, but it's not easy because there are multiple | ||
# relations to `Course::ScholaisticAssessment` that need to be updated. | ||
ActiveRecord::Base.transaction do | ||
response[:assessments].map do |assessment| | ||
current_course.scholaistic_assessments.find_or_initialize_by( | ||
upstream_id: assessment[:upstream_id] | ||
).tap do |scholaistic_assessment| | ||
scholaistic_assessment.start_at = assessment[:start_at] | ||
scholaistic_assessment.end_at = assessment[:end_at] | ||
scholaistic_assessment.title = assessment[:title] | ||
scholaistic_assessment.description = assessment[:description] | ||
scholaistic_assessment.published = assessment[:published] | ||
end.save! | ||
end | ||
|
||
if response[:deleted].present? && !current_course.scholaistic_assessments. | ||
where(upstream_id: response[:deleted]).destroy_all | ||
raise ActiveRecord::Rollback | ||
end | ||
|
||
current_course.settings(:course_scholaistic_component).public_send('last_synced_at=', response[:last_synced_at]) | ||
|
||
current_course.save! | ||
end | ||
end | ||
|
||
def submission_status(assessment) | ||
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.