Skip to content
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

Add admin view to copy courses from production instance of dashboard (#5528) #5528

22 changes: 22 additions & 0 deletions app/controllers/copy_course_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

#= Controller for Copy Course tool
class CopyCourseController < ApplicationController
respond_to :html
before_action :require_admin_permissions
def index; end

def copy
service = CopyCourse.new(url: params[:url])
response = service.make_copy
if response[:error].present?
redirect_to(copy_course_path,
flash: { error: "Course not created: #{response[:error]}" })
else
course = response[:course]
redirect_to copy_course_path,
notice: "Course #{course.title} was created."\
"&nbsp;<a href=\"/courses/#{course.slug}\">Go to course</a>"
end
end
end
98 changes: 98 additions & 0 deletions app/services/copy_course.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

#= Copy course from another server
class CopyCourse
def initialize(url:)
@url = url
end

def make_copy
@course_data = retrieve_course_data
copy_main_course_data
add_tracked_wikis
@cat_data = retrieve_categories_data
copy_tracked_categories_data
@users_data = retrieve_users_data
copy_users_data
return { course: @course, error: nil }
rescue ActiveRecord::RecordInvalid, StandardError => e
return { course: nil, error: e.message }
end

private

def copy_main_course_data
# Extract the attributes we want to copy
params_to_copy = %w[school title term description start end subject slug timeline_start
timeline_end type flags]
copied_data = {}
params_to_copy.each { |p| copied_data[p] = @course_data[p] }
@home_wiki = Wiki.get_or_create(language: @course_data['home_wiki']['language'],
project: @course_data['home_wiki']['project'])
copied_data['home_wiki_id'] = @home_wiki.id
copied_data['passcode'] = GeneratePasscode.call # set a random passcode
if copied_data['flags'].key?('update_logs')
copied_data['flags']['update_logs'] =
fix_update_logs_parsing(copied_data['flags']['update_logs'])
end
# Create the course
@course = Course.create!(copied_data)
end

# When parsing update_logs from flags, keys are set as strings instead of integers
# This causes problems, so we need to force the keys to be integers.
def fix_update_logs_parsing(update_logs)
update_logs.transform_keys(&:to_i)
end

def add_tracked_wikis
@course_data['wikis'].each do |wiki_hash|
wiki = Wiki.get_or_create(language: wiki_hash['language'], project: wiki_hash['project'])
next if wiki.id == @home_wiki.id # home wiki was automatically added already
@course.wikis << wiki
end
end

def copy_tracked_categories_data
@cat_data.each do |cat_hash|
wiki = Wiki.get_or_create(language: cat_hash['wiki']['language'],
project: cat_hash['wiki']['project'])
cat = Category.find_or_create_by!(
depth: cat_hash['depth'],
source: cat_hash['source'],
name: cat_hash['name'],
wiki:
)
@course.categories << cat
end
end

def copy_users_data
@users_data.each do |user_hash|
user = User.find_or_create_by!(username: user_hash['username'])
CoursesUsers.create!(user_id: user.id, role: user_hash['role'], course_id: @course.id)
end
end

def get_request(path)
uri = URI(@url + path)
response = Net::HTTP.get_response(uri)
raise "Error getting data from #{uri}" unless response.is_a?(Net::HTTPSuccess)
response
end

def retrieve_course_data
response = get_request('/course.json')
JSON.parse(response.body)['course']
end

def retrieve_categories_data
response = get_request('/categories.json')
JSON.parse(response.body)['course']['categories']
end

def retrieve_users_data
response = get_request('/users.json')
JSON.parse(response.body)['course']['users']
end
end
16 changes: 9 additions & 7 deletions app/views/admin/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@
%h2 Useful links
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ordered the links alphabetically

%ul
%li
%a{href: '/article_finder'} Article Finder
%a{href: '/faq/new'} Add new FAQ
%li
%a{href: '/usage'} Usage
%a{href: '/article_finder'} Article Finder
%li
%a{href: '/sidekiq'} Job queue
%a{href: '/copy_course'} Copy course from another server
%li
%a{href: 'https://dashboard-testing.wikiedu.org/rails/mailers'} Email templates
%li
%a{href: '/faq/new'} Add new FAQ
%li
%a{href: '/faq_topics'} FAQ Topics
%li
%a{href: '/unsubmitted_courses'} Unsubmitted Courses
%li
%a{href: '/feedback_form_responses'} Feedback Form Responses
%li
%a{href: '/sidekiq'} Job queue
%li
%a{href: '/unsubmitted_courses'} Unsubmitted Courses
%li
%a{href: '/update_username'} Update username
%li
%a{href: '/usage'} Usage

16 changes: 16 additions & 0 deletions app/views/copy_course/index.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
- content_for :before_title, 'Admin - '

.container.dashboard
%header
%h1
= 'Copy course from another server'

%section
.container
%hr

.container
= form_tag copy_course_path, class: 'explore-courses', method: :post do
= text_field_tag(:url, '', placeholder: 'Copy course URL here...')
%button{type: 'submit'}
%i.icon.icon-check
2 changes: 1 addition & 1 deletion app/views/shared/_flash.html.haml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
- if flash[:notice]
.notification= flash[:notice]
.notification= flash[:notice].html_safe
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this change for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I cherry-picked this from the previous PR.
We are using flash[:notice] for rendering the success message in the controller, and that message has a link to the new course (which is added using <a> tag). It looks like we need to add the html_safe to indicate that the string should be considered safe HTML content and should not be escaped when rendered in a view. Without the html_safe, the link doesn't render properly.


- unless ENV['sitenotice'].blank?
.notification.sitenotice= raw ENV['sitenotice'].dup.force_encoding("UTF-8")
Expand Down
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
get '/update_username' => 'update_username#index'
post '/update_username' => 'update_username#update'

# Copy course from another server
get 'copy_course' => 'copy_course#index'
post 'copy_course' => 'copy_course#copy'

# Self-enrollment: joining a course by entering a passcode or visiting a url
get 'courses/:course_id/enroll/(:passcode)' => 'self_enrollment#enroll_self',
constraints: { course_id: /.*/ }
Expand Down
52 changes: 0 additions & 52 deletions setup/copy_course_from_production.rb

This file was deleted.

69 changes: 69 additions & 0 deletions spec/controllers/copy_course_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require 'rails_helper'

describe CopyCourseController, type: :request do
describe '#index' do
let(:user) { create(:user) }
let(:admin) { create(:admin) }

context 'when the user is an admin' do
before do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(admin)
end

it 'shows the feature to copy the course' do
get copy_course_path
expect(response.status).to eq(200)
end
end

context 'when the user is not an admin' do
before do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user)
end

it 'returns a 401 error' do
get copy_course_path
expect(response.status).to eq(401)
end
end
end

describe '#copy' do
let(:admin) { create(:admin) }
let(:subject) { post copy_course_path, params: { url: 'someurl.com' } }

context 'when the copy fails for some reason' do
before do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(admin)
allow_any_instance_of(CopyCourse).to receive(:make_copy).and_return(
{ course: nil, error: 'An interesting error happened' }
)
end

it 'renders the error' do
subject
expect(response).to redirect_to(copy_course_path)
expect(flash[:error]).to eq('Course not created: An interesting error happened')
end
end

context 'when the copy is successful' do
before do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(admin)
allow_any_instance_of(CopyCourse).to receive(:make_copy).and_return(
{ course: create(:basic_course), error: nil }
)
end

it 'renders the success message' do
subject
expect(response).to redirect_to(copy_course_path)
expect(flash[:notice]).to eq('Course Black life matters was created.'\
'&nbsp;<a href="/courses/none/Black_life_'\
'matters_(none)">Go to course</a>')
end
end
end
end
Loading
Loading