-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #387 from openstax/books_breakdown
Books breakdown
- Loading branch information
Showing
4 changed files
with
2,309 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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 |
---|---|---|
|
@@ -79,7 +79,6 @@ def show | |
render json: tree | ||
end | ||
|
||
|
||
protected | ||
|
||
def abl | ||
|
This file contains 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,98 @@ | ||
namespace :books do | ||
# Creates a CSV file listing the number of Exercises of each type per section of the given book | ||
# Arguments are, in order: | ||
# book_uuid, [filename] | ||
# Example: rake books:breakdown[14fb4ad7-39a1-4eee-ab6e-3ef2482e3e22] | ||
# will create 14fb4ad7-39a1-4eee-ab6e-3ef2482e3e22.csv containing book Exercise info for A&P | ||
desc 'creates a CSV file listing the number of Exercises of each type per section of the given book' | ||
task :breakdown, [:book_uuid, :filename] => :environment do |t, args| | ||
# Output logging info to the console (except in the test environment) | ||
original_logger = Rails.logger | ||
|
||
begin | ||
Rails.logger = ActiveSupport::Logger.new(STDOUT) unless Rails.env.test? | ||
|
||
book_uuid = args[:book_uuid] | ||
filename = args[:filename] || "#{book_uuid}.csv" | ||
|
||
book = OpenStax::Content::Abl.new.approved_books.find { |book| book.uuid == book_uuid } | ||
root_book_part = loop do | ||
begin | ||
break book.root_book_part | ||
rescue StandardError => exception | ||
# Sometimes books in the ABL fail to load | ||
# Retry with an earlier version of archive, if possible | ||
previous_version = book.archive.previous_version | ||
|
||
# break from the loop if there are no more archive versions to try | ||
raise exception if previous_version.nil? | ||
|
||
book = OpenStax::Content::Book.new( | ||
archive: OpenStax::Content::Archive.new(version: previous_version), | ||
uuid: book.uuid, | ||
version: book.version, | ||
slug: book.slug, | ||
style: book.style, | ||
min_code_version: book.min_code_version, | ||
committed_at: book.committed_at | ||
) | ||
|
||
raise exception unless book.valid? | ||
end | ||
end | ||
|
||
def recursive_exercise_counts(book_part, type) | ||
results = book_part.parts.flat_map do |part| | ||
next recursive_exercise_counts(part, 'Unit/Chapter') unless part.is_a?(OpenStax::Content::Page) | ||
|
||
exercises = Exercise.chainable_latest.published.joins(:tags).where( | ||
tags: { name: "context-cnxmod:#{part.uuid}" } | ||
).joins(questions: {stems: :stylings}).distinct | ||
|
||
mc_tf_exercises = exercises.dup.where( | ||
questions: { stems: { stylings: { style: [ Style::MULTIPLE_CHOICE, Style::TRUE_FALSE ] } } } | ||
).pluck(:id) | ||
fr_exercises = exercises.dup.where( | ||
questions: { stems: { stylings: { style: Style::FREE_RESPONSE } } } | ||
).pluck(:id) | ||
|
||
[ | ||
[ | ||
part.uuid, | ||
'Page', | ||
part.book_location.join('.'), | ||
ActionView::Base.full_sanitizer.sanitize(part.title).gsub(/\s+/, ' ').strip, | ||
exercises.count, | ||
(mc_tf_exercises & fr_exercises).size, | ||
(mc_tf_exercises - fr_exercises).size, | ||
(fr_exercises - mc_tf_exercises).size | ||
] | ||
] | ||
end | ||
|
||
direct_child_uuids = book_part.parts.map(&:uuid) | ||
direct_child_results = results.filter { |result| direct_child_uuids.include? result.first } | ||
|
||
[ | ||
[ | ||
book_part.uuid, | ||
type, | ||
book_part.book_location.join('.'), | ||
ActionView::Base.full_sanitizer.sanitize(book_part.title).gsub(/\s+/, ' ').strip | ||
] + direct_child_results.map { |result| result[4..-1] }.transpose.map(&:sum) | ||
] + results | ||
end | ||
|
||
CSV.open(filename, 'w') do |csv| | ||
csv << [ | ||
'UUID', 'Type', 'Number', 'Title', 'Total Exercises', '2-step MC/TF', 'MC/TF only', 'FR only' | ||
] | ||
|
||
recursive_exercise_counts(root_book_part, 'Book').each { |row| csv << row } | ||
end | ||
ensure | ||
# Restore original logger | ||
Rails.logger = original_logger | ||
end | ||
end | ||
end |
2,109 changes: 2,109 additions & 0 deletions
2,109
spec/cassettes/books_breakdown/returns_correct_exercise_counts.yml
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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,102 @@ | ||
require 'vcr_helper' | ||
require 'rake' | ||
|
||
RSpec.describe 'books breakdown', type: :rake, vcr: VCR_OPTS do | ||
before :all do | ||
Rake.application.rake_require 'tasks/books/breakdown' | ||
Rake::Task.define_task :environment | ||
end | ||
|
||
before { Rake::Task['books:breakdown'].reenable } | ||
|
||
let(:book_uuid) { '14fb4ad7-39a1-4eee-ab6e-3ef2482e3e22' } | ||
let(:filename) { "#{Rails.root}/tmp/test-#{book_uuid}.csv" } | ||
|
||
it 'returns correct exercise counts' do | ||
# Disable set_slug_tags! | ||
allow_any_instance_of(Exercise).to receive(:set_slug_tags!) | ||
|
||
ex1 = FactoryBot.create :exercise, tags: [ 'context-cnxmod:ada35081-9ec4-4eb8-98b2-3ce350d5427f' ] | ||
ex1.questions.first.stems.first.stylings.first.update_attribute :style, Style::MULTIPLE_CHOICE | ||
FactoryBot.create :styling, stylable: ex1.questions.first.stems.first, style: Style::FREE_RESPONSE | ||
ex1.publication.publish.save! | ||
|
||
ex2 = FactoryBot.create :exercise, tags: [ 'context-cnxmod:5e1ff6e7-0980-4ae0-bc8a-4b591a7c1760' ] | ||
ex2.questions.first.stems.first.stylings.first.update_attribute :style, Style::TRUE_FALSE | ||
FactoryBot.create :styling, stylable: ex2.questions.first.stems.first, style: Style::FREE_RESPONSE | ||
ex2.publication.publish.save! | ||
|
||
ex3 = FactoryBot.create :exercise, tags: [ 'context-cnxmod:5e1ff6e7-0980-4ae0-bc8a-4b591a7c1760' ] | ||
ex3.questions.first.stems.first.stylings.first.update_attribute :style, Style::MULTIPLE_CHOICE | ||
ex3.publication.publish.save! | ||
|
||
ex4 = FactoryBot.create :exercise, tags: [ 'context-cnxmod:59221da8-5fb6-4b3e-9450-079cd616385b' ] | ||
ex4.questions.first.stems.first.stylings.first.update_attribute :style, Style::TRUE_FALSE | ||
ex4.publication.publish.save! | ||
|
||
ex5 = FactoryBot.create :exercise, tags: [ 'context-cnxmod:59221da8-5fb6-4b3e-9450-079cd616385b' ] | ||
ex5.questions.first.stems.first.stylings.first.update_attribute :style, Style::FREE_RESPONSE | ||
ex5.publication.publish.save! | ||
|
||
ex6 = FactoryBot.create :exercise, tags: [ 'context-cnxmod:59221da8-5fb6-4b3e-9450-079cd616385b' ] | ||
ex6.questions.first.stems.first.stylings.first.update_attribute :style, Style::MULTIPLE_CHOICE | ||
ex6.publication.publish.save! | ||
|
||
Rake.application.invoke_task "books:breakdown[#{book_uuid},#{filename}]" | ||
|
||
rows = CSV.read filename | ||
expect(rows.first).to eq([ | ||
'UUID', 'Type', 'Number', 'Title', 'Total Exercises', '2-step MC/TF', 'MC/TF only', 'FR only' | ||
]) | ||
expect(rows.second).to eq([ | ||
'14fb4ad7-39a1-4eee-ab6e-3ef2482e3e22', 'Book', '', 'Anatomy and Physiology', '6', '2', '3', '1' | ||
]) | ||
expect(rows.third).to eq([ | ||
'7c42370b-c3ad-48ac-9620-d15367b882c6', 'Page', '', 'Preface', '0', '0', '0', '0' | ||
]) | ||
expect(rows.fourth).to eq([ | ||
'd3ad443b-78fa-551e-a67f-182bd0cb4c77', 'Unit/Chapter', '1', 'Unit 1 Levels of Organization', '6', '2', '3', '1' | ||
]) | ||
expect(rows.fifth).to eq([ | ||
'5ae6cc38-7b7b-5e9c-a7a4-5d8251baac7f', | ||
'Unit/Chapter', | ||
'1', | ||
'Chapter 1 An Introduction to the Human Body', | ||
'6', | ||
'2', | ||
'3', | ||
'1' | ||
]) | ||
expect(rows[5]).to eq([ | ||
'ccc4ed14-6c87-408b-9934-7a0d279d853a', 'Page', '', 'Introduction', '0', '0', '0', '0' | ||
]) | ||
expect(rows[6]).to eq([ | ||
'ada35081-9ec4-4eb8-98b2-3ce350d5427f', | ||
'Page', | ||
'1.1', | ||
'1.1 Overview of Anatomy and Physiology', | ||
'1', | ||
'1', | ||
'0', | ||
'0' | ||
]) | ||
expect(rows[7]).to eq([ | ||
'5e1ff6e7-0980-4ae0-bc8a-4b591a7c1760', | ||
'Page', | ||
'1.2', | ||
'1.2 Structural Organization of the Human Body', | ||
'2', | ||
'1', | ||
'1', | ||
'0' | ||
]) | ||
expect(rows[8]).to eq([ | ||
'59221da8-5fb6-4b3e-9450-079cd616385b', 'Page', '1.3', '1.3 Functions of Human Life', '3', '0', '2', '1' | ||
]) | ||
rows[9..-1].each do |row| | ||
expect(row[4..-1]).to eq(['0', '0', '0', '0']) | ||
end | ||
ensure | ||
FileUtils.rm_f filename | ||
end | ||
end |