kgrift 1.3.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/KGrift/Gemfile +22 -0
  3. data/KGrift/README.md +66 -0
  4. data/KGrift/bin/kgrift +11 -0
  5. data/KGrift/grifter.yml +224 -0
  6. data/KGrift/internal_test_graphs/basic_test_graph_definition.yml +2915 -0
  7. data/KGrift/internal_test_graphs/unicode_test_graph_definition.yml +3070 -0
  8. data/KGrift/knewton_grifts/analytics_grifts.rb +103 -0
  9. data/KGrift/knewton_grifts/async_helper_grifts.rb +63 -0
  10. data/KGrift/knewton_grifts/authenticator_grifts.rb +46 -0
  11. data/KGrift/knewton_grifts/basic_grifts.rb +29 -0
  12. data/KGrift/knewton_grifts/batch_grifts.rb +14 -0
  13. data/KGrift/knewton_grifts/content_collection_grifts.rb +204 -0
  14. data/KGrift/knewton_grifts/content_collection_v1_grifts.rb +521 -0
  15. data/KGrift/knewton_grifts/content_eid_grifts.rb +41 -0
  16. data/KGrift/knewton_grifts/copy_grifts.rb +151 -0
  17. data/KGrift/knewton_grifts/deprecated_graph_and_taxonomy_grifts.rb +353 -0
  18. data/KGrift/knewton_grifts/goal_grifts.rb +203 -0
  19. data/KGrift/knewton_grifts/graph_and_taxonomy_grifts.rb +136 -0
  20. data/KGrift/knewton_grifts/graph_create_grifts.rb +34 -0
  21. data/KGrift/knewton_grifts/graph_query_grifts.rb +448 -0
  22. data/KGrift/knewton_grifts/graph_tools_grifts.rb +151 -0
  23. data/KGrift/knewton_grifts/graph_validation_grifts.rb +447 -0
  24. data/KGrift/knewton_grifts/helper_grifts.rb +92 -0
  25. data/KGrift/knewton_grifts/jmeter_data_grifts.rb +56 -0
  26. data/KGrift/knewton_grifts/learning_instance_grifts.rb +46 -0
  27. data/KGrift/knewton_grifts/looper_grifts.rb +34 -0
  28. data/KGrift/knewton_grifts/moxy_grifts.rb +64 -0
  29. data/KGrift/knewton_grifts/oauth_grifts.rb +182 -0
  30. data/KGrift/knewton_grifts/partner_grifts.rb +70 -0
  31. data/KGrift/knewton_grifts/partner_support_grifts.rb +85 -0
  32. data/KGrift/knewton_grifts/recommendation_setup_grifts.rb +215 -0
  33. data/KGrift/knewton_grifts/registration_grifts.rb +159 -0
  34. data/KGrift/knewton_grifts/registration_info_grifts.rb +23 -0
  35. data/KGrift/knewton_grifts/report_grifts.rb +122 -0
  36. data/KGrift/knewton_grifts/shell_command_grifts.rb +21 -0
  37. data/KGrift/knewton_grifts/student_flow_grifts.rb +560 -0
  38. data/KGrift/knewton_grifts/tag_grifts.rb +41 -0
  39. data/KGrift/knewton_grifts/test_data_grifts.rb +328 -0
  40. data/KGrift/knewton_grifts/test_user_grifts.rb +264 -0
  41. data/KGrift/lib/dtrace.rb +20 -0
  42. data/KGrift/lib/kgrift.rb +7 -0
  43. data/KGrift/test_data_generators/basic_book_and_taxonomies.rb +35 -0
  44. data/KGrift/test_data_generators/lo_test_graph.rb +34 -0
  45. data/KGrift/test_data_generators/partner_owned_book_and_taxonomies.rb +28 -0
  46. data/KGrift/test_data_generators/partner_owned_book_and_taxonomies_unicode.rb +28 -0
  47. data/KGrift/test_data_generators/sandcastle_book_and_taxonomies.rb +13 -0
  48. data/KGrift/test_data_generators/sandcastle_book_and_taxonomies.yml +3709 -0
  49. data/KGrift/test_data_generators/sandcastle_graph.rb +8 -0
  50. data/KGrift/test_data_generators/sandcastle_graph_definition.json +4483 -0
  51. data/KGrift/test_data_generators/sandcastle_graph_full.rb +7 -0
  52. data/KGrift/test_data_generators/sandcastle_taxonomies.yml +378 -0
  53. data/KGrift/test_data_generators/sandcastle_with_taxons.rb +56 -0
  54. data/KGrift/test_data_generators/sandcastle_with_taxons.yml +3994 -0
  55. data/KGrift/test_data_generators/test_users_and_partners.rb +76 -0
  56. data/kgrift.gemspec +43 -0
  57. metadata +144 -0
@@ -0,0 +1,203 @@
1
+ require 'set'
2
+
3
+ def get_goals learning_instance_id, params={}
4
+ Log.info "Getting all goals for learning instance: '#{learning_instance_id}' , with params '#{params.keys.join(', ')}'"
5
+ kapi.get "/v0/learning-instances/#{learning_instance_id}/goals?" + URI.encode_www_form(params)
6
+ end
7
+
8
+ def get_goal learning_instance_id, goal_id
9
+ Log.info "Getting goal '#{goal_id}' for learning instance '#{learning_instance_id}'"
10
+ kapi.get "/v0/learning-instances/#{learning_instance_id}/goals/#{goal_id}"
11
+ end
12
+ alias :get_single_goal :get_goal
13
+
14
+ def get_realized_goal learning_instance_id, goal_id
15
+ Log.info "Getting realized goal '#{goal_id}' for learning instance '#{learning_instance_id}'"
16
+ kapi.get "/v0/learning-instances/#{learning_instance_id}/realized-goals/#{goal_id}"
17
+ end
18
+
19
+ def validate_goal goal_id, learning_instance_id
20
+ Log.info "Validating a goal with goal id #{goal_id} on learning_instance #{learning_instance_id}"
21
+ kapi.get "/v0/learning-instances/#{learning_instance_id}/goals/#{goal_id}/validate"
22
+ end
23
+
24
+
25
+ # Updating a goal does a full over-writing PUT. Anything not included from the original goal
26
+ # will be removed
27
+ def update_goal learning_instance_id, goal_id, overrides={}, params={}
28
+ Log.info "Updating a goal for learning instance: '#{learning_instance_id}' and goal: '#{goal_id}' , with params '#{params.keys.join(', ')}'"
29
+ kapi.put "/v0/learning-instances/#{learning_instance_id}/goals/#{goal_id}?" + URI.encode_www_form(params), overrides
30
+ end
31
+
32
+ #create goal will wait twice.
33
+ #First it will wait for the goal post to succeed
34
+ #We need to do this, because a learning instance must write to kraphs
35
+ #before we can make a goal off it, and that can take up to 60 seconds
36
+ #Second it will wait for the goal to write. Again kraphs asynchronously
37
+ #writes goals, so we have to wait for that to happen before our tests can depend on it
38
+ def create_goal learning_instance_id, overrides={}, params={}
39
+ Log.info "Creating a goal for learning instance: '#{learning_instance_id}' , with params '#{params.keys.join(', ')}'"
40
+ kapi.post "/v0/learning-instances/#{learning_instance_id}/goals?" + URI.encode_www_form(params), {
41
+ 'target_modules' => [],
42
+ 'recommendable_modules' => [],
43
+ 'max_recommendation_size' => 3,
44
+ 'start_date' => '2013-04-12T17:00:00.000Z'
45
+ }.merge(overrides)
46
+ end
47
+
48
+ def create_goal_non_waiting learning_instance_id, overrides={}, params={}
49
+ Log.info "Creating a goal for learning instance: '#{learning_instance_id}' , with params '#{params.keys.join(', ')}'"
50
+ kapi.post "/v0/learning-instances/#{learning_instance_id}/goals?" + URI.encode_www_form(params), {
51
+ 'target_modules' => [],
52
+ 'recommendable_modules' => [],
53
+ 'max_recommendation_size' => 3,
54
+ 'start_date' => '2013-04-12T17:00:00.000Z'
55
+ }.merge(overrides)
56
+ end
57
+
58
+
59
+ #this convenience method builds out the module lists from a graph
60
+ #which avoids building complicated payloads in tests that are not
61
+ # explicitly concerned with the complicated bits of the payload
62
+ def create_goal_from_graph graph, learning_instance_id=nil, overrides={}, params={}
63
+ Log.info "Making a goal with random modules picked from a graph"
64
+ nodes = graph['nodes'].clone
65
+ Log.info nodes
66
+
67
+ module_ids = Set.new find_all_assessment_atoms(graph)
68
+ modules = nodes.select{|n| module_ids.member?n['id']}
69
+
70
+ Log.info modules
71
+ payload_overrides = {
72
+ 'target_modules' => [
73
+ { "module_id" => modules.shift['id'],
74
+ "target_score" => 1.0,
75
+ "target_date" => days_from_now(1)
76
+ },
77
+ { "module_id" => modules.shift['id'],
78
+ "target_score" => 1.0,
79
+ "target_date" => days_from_now(2)
80
+ },
81
+ ],
82
+ 'recommendable_modules' => modules.map{|m| { "module_id" => m['id'] } }[0,5],
83
+ 'max_recommendation_size' => 2,
84
+ }.merge(overrides)
85
+
86
+ unless learning_instance_id
87
+ learning_instance_id = create_learning_instance 'graph_id' => graph['id']
88
+ end
89
+
90
+ create_goal learning_instance_id, payload_overrides, params
91
+ end
92
+
93
+ def create_goals_from_graph_yaml yaml_file_path
94
+ #Load yaml file
95
+ file_data = YAML.load_file(yaml_file_path)
96
+
97
+ #Cache graph locally
98
+ graph = get_graph_and_cache_it file_data['graph_id']
99
+
100
+ #Loop over all goals
101
+ output = Hash.new()
102
+ goals = file_data['goals']
103
+ goals.each do |goal_name, goal_def|
104
+ Log.info "Processing goal: #{goal_name}"
105
+
106
+ #Initialize output variable
107
+ output[goal_name] = Hash.new()
108
+
109
+ #Extract input parameters
110
+ target_module_ids = goal_def.fetch('target_module_ids',[])
111
+ recommendable_taxon_ids = goal_def.fetch('recommendable_taxon_ids',[])
112
+ recommendable_module_ids = goal_def.fetch('recommendable_module_ids',[])
113
+
114
+ #Define target module
115
+ if target_module_ids.empty?
116
+ target_module_ids = []
117
+ target_concept = find_prerequisite_concept graph
118
+ target_module_ids << find_associated_assessing_bndl(graph,target_concept)
119
+ target_module_ids << find_associated_assessing_atom(graph,target_concept)
120
+ end
121
+ Log.info "Target modules: #{target_module_ids}"
122
+
123
+ #Construct target modules hash
124
+ target_modules = []
125
+ target_modules = target_module_ids.map do |mod|
126
+ {
127
+ 'module_id' => mod,
128
+ 'target_date' => days_from_now(30),
129
+ 'target_score' => 0.9,
130
+ }
131
+ end
132
+
133
+ #Define recommendable taxons and modules
134
+ Log.info "Setting recommendable modules"
135
+ rec_modules = recommendable_taxon_ids.map{ |taxon_id| {'taxon_id' => taxon_id} }
136
+ rec_modules += recommendable_module_ids.map{ |module_id| {'module_id' => module_id} }
137
+ if rec_modules.empty?
138
+ Log.info "No recommendable modules or taxons define; setting all modules as recommendable"
139
+ all_modules = find_all_recommendable_module_ids graph
140
+ rec_modules = all_modules.map{|m| {'module_id' => m}}
141
+ end
142
+
143
+ #Define goal object
144
+ goal_obj = {
145
+ 'target_modules' => target_modules,
146
+ 'max_recommendation_size' => (rec_modules.length < 3 ? rec_modules.length : 3),
147
+ 'start_date' => datetime,
148
+ 'recommendable_modules' => rec_modules,
149
+ }
150
+ Log.info "Creating goal with parameters #{goal_obj}"
151
+
152
+ #Create learning instance and goal
153
+ output[goal_name]['learning_instance_id'] = (create_learning_instance 'graph_id' => graph['id'])['id']
154
+ output[goal_name]['goal_id'] = (create_goal output[goal_name]['learning_instance_id'], goal_obj)['id']
155
+ end
156
+
157
+ #Output learning instance IDs and goal IDs to screen/file
158
+ output_file = File.join(File.dirname(yaml_file_path), File.basename(yaml_file_path, '.yml')+'_output'+'.yml')
159
+ File.open(output_file, 'w') {|f| f.write(output.to_yaml) }
160
+ end
161
+
162
+ # GROUP GOALS
163
+
164
+ def create_group_goal learning_instance_id, params
165
+ Log.debug "Creating a group goal for learning instance: #{learning_instance_id}"
166
+ kapi.post "/v0/learning-instances/#{learning_instance_id}/group-goals", params
167
+ end
168
+
169
+ def update_group_goal learning_instance_id, goal_id, params
170
+ Log.debug "Updating a group goal for learning instance: #{learning_instance_id} goal #{goal_id}"
171
+ kapi.put "/v0/learning-instances/#{learning_instance_id}/group-goals/#{goal_id}", params
172
+ end
173
+
174
+ def create_random_valid_group_goal_object graph, options={}
175
+
176
+ options = {
177
+ target_type: :lref,
178
+ num_targets: 1,
179
+ }.merge(options)
180
+
181
+ # find all all the recommendable modules that have a name
182
+ potential_target_values = if options[:target_type].to_sym == :mref
183
+ modules = find_all_assessment_modules_full(graph)
184
+ # pull the mref eids out of the nodes
185
+ modules.map{|m| m['temp_id'] =~ /^.ref-/ ? m['temp_id'] : "mref-#{m['temp_id']}" }
186
+ else
187
+ los = find_all_learning_objectives(graph)
188
+ los.map{|lo| lo['temp_id'] =~ /^.ref-/ ? lo['temp_id'] : "lref-#{lo['temp_id']}" }
189
+ end
190
+
191
+ # pick a random set of target modules/learning objectives
192
+ targets = potential_target_values.shuffle[0...options[:num_targets]]
193
+
194
+ # make the goal object
195
+ goal_obj = {
196
+ "name" => "TestGroupGoal #{random_string}",
197
+ "targets" => targets,
198
+ "scope" => {},
199
+ }
200
+
201
+ goal_obj
202
+ end
203
+
@@ -0,0 +1,136 @@
1
+
2
+
3
+ # New get taxonomies endpoint
4
+ def get_taxonomies_for_graph (graph_id, overrides={})
5
+ Log.info "Getting taxonomies for graph id: '#{graph_id}'."
6
+ resp = nil
7
+ do_as_knewton_partner overrides.delete('use_current_user') do
8
+ resp = kapi.get "/v0/taxonomies/graph-id/#{graph_id}"
9
+ end
10
+ resp
11
+ end
12
+
13
+ # get get a taxonomy by graph id + taxonomy id
14
+ def get_taxonomy_for_graph (graph_id, taxonomy_id, overrides={})
15
+ Log.info "Getting taxonomy '#{taxonomy_id}' for graph id: '#{graph_id}'."
16
+ ts = get_taxonomies_for_graph graph_id
17
+ ts.find{ |t| t['id'] == taxonomy_id}
18
+ end
19
+
20
+ def get_graph_summary id, overrides={}
21
+ Log.info "getting graph by id: '#{id}'"
22
+ kapi.get "/v0/graphs/#{id}/summary"
23
+ end
24
+
25
+ def get_all_graphs overrides={}
26
+ Log.info "Getting all graphs with params '#{overrides.keys.join(', ')}'"
27
+ kapi.get '/v0/graphs?' + URI.encode_www_form(overrides)
28
+ end
29
+
30
+ def get_all_graphs_for_partner partner_id, overrides={}
31
+ overrides.merge!'partner_id' => partner_id
32
+ get_all_graphs overrides
33
+ end
34
+
35
+ def get_internal_graph_id partner_id, graph_name
36
+ graphs = get_all_graphs_for_partner partner_id
37
+ graph = graphs.find{|g| g['name'] == graph_name}
38
+ raise "Couldn't find graph for partner #{partner_id} with name #{graph_name}" unless graph
39
+ graph['name']
40
+ end
41
+
42
+ def translate_graph_gref_into_internal_id partner_id, gref
43
+ partner_graphs = get_all_graphs_for_partner partner_id
44
+ wanted_name = gref.sub(/^gref-/,'')
45
+ found = partner_graphs.find{|g| g['name'] == wanted_name}
46
+ raise "No such graph with name '#{wanted_name}' for partner: #{partner_id}" unless found
47
+ found['id']
48
+ end
49
+
50
+ def get_graph id, overrides={}
51
+ Log.info "getting graph by id: '#{id}'"
52
+ do_as_knewton_partner overrides.delete('use_current_user') do
53
+ kapi.get "/v0/graphs/#{id}", timeout: 18000
54
+ end
55
+ end
56
+
57
+ GRAPH_CACHE_DIR = File.join(Dir.home,'.kgrift/graph_cache')
58
+ def get_graph_and_cache_it id, overrides={}
59
+ FileUtils::mkdir_p GRAPH_CACHE_DIR
60
+ cache_file_name = File.join(GRAPH_CACHE_DIR, id + '.json')
61
+ if File.exist?(cache_file_name)
62
+ return JSON.parse(IO.read(cache_file_name))
63
+ else
64
+ graph = get_graph id, overrides
65
+ File.open(cache_file_name, 'w'){|f| f.write JSON.pretty_generate(graph)}
66
+ return graph
67
+ end
68
+ end
69
+
70
+ def get_graphs_map_of_assessing_bundles_for_post_requisite_concepts_and_cache_it graph, overrides={}
71
+ id = graph['id']
72
+ FileUtils::mkdir_p GRAPH_CACHE_DIR
73
+ cache_file_name = File.join(GRAPH_CACHE_DIR, id + '_postreqs_to_assessing_bndls.json')
74
+ if File.exist?(cache_file_name)
75
+ return JSON.parse(IO.read(cache_file_name))
76
+ else
77
+ assessing_bundles_for_postreq = get_map_of_assessing_bundles_for_post_requisite_concepts graph
78
+ File.open(cache_file_name, 'w'){|f| f.write JSON.pretty_generate(assessing_bundles_for_postreq)}
79
+ return assessing_bundles_for_postreq
80
+ end
81
+ end
82
+
83
+ def graph_yaml id
84
+ graph = get_graph_and_cache_it id
85
+ File.open("#{id}.yml", 'w') {|f| f.write graph.to_yaml}
86
+ end
87
+
88
+ def get_graph_by_name name, overrides={}
89
+ Log.info "getting graph by name: '#{name}'"
90
+ do_as_knewton_partner overrides.delete('use_current_user') do
91
+ kapi.get "/v0/graphs?name=#{URI.encode name}"
92
+ end
93
+ end
94
+
95
+ def graph_exists? id, overrides={}
96
+ Log.info "Checking if graph exists with ID: #{id}"
97
+ do_as_knewton_partner overrides.delete('use_current_user') do
98
+ kapi.head "/v0/graphs/#{id}"
99
+ end
100
+ return true
101
+ rescue RequestException => e
102
+ return false if e.code == 404
103
+ raise e
104
+ end
105
+
106
+ def do_as_knewton_partner use_current_user=false, &blk
107
+ if use_current_user
108
+ yield
109
+ else
110
+ as_account :knerd do
111
+ yield
112
+ end
113
+ end
114
+ end
115
+
116
+
117
+ def get_node graph_id, node_id, overrides={}
118
+ Log.info "Getting a node from graph: #{graph_id} -> #{node_id}"
119
+ kapi.get "/v0/graphs/#{graph_id}/nodes/#{node_id}"
120
+ end
121
+
122
+ def get_learning_objective_node graph_id, learning_objective_id, overrides={}
123
+ Log.info "Getting a learning objective node from graph: #{graph_id} -> #{learning_objective_id}"
124
+ kapi.get "/v0/graphs/#{graph_id}/learningObjectives/#{learning_objective_id}"
125
+ end
126
+
127
+ def get_edge graph_id, start_node_id, end_node_id, edge_type, overrides={}
128
+ Log.info "Getting an edge from graph: #{graph_id} -> #{start_node_id}_#{edge_type}_#{end_node_id}"
129
+ kapi.get "/v0/graphs/#{graph_id}/edges/#{start_node_id}_#{edge_type}_#{end_node_id}"
130
+ end
131
+
132
+ def get_edges graph_id, node_id, overrides={}
133
+ Log.info "Getting a edges from graph #{graph_id}, starting from #{node_id}"
134
+ kapi.get "/v0/graphs/#{graph_id}/nodes/#{node_id}/edges"
135
+ end
136
+
@@ -0,0 +1,34 @@
1
+ """
2
+ This file is all about creating graphs and taxonomies.
3
+
4
+ This is based on the POST /v0/book_and_taxonomies endpoint that Bivins implements.
5
+ This allows for making a graph and taxonomies in a way that is not Klique.
6
+ This is mostly intended for use by Bivins localmode integration tests.
7
+
8
+ Generally most tests should not use the create_book_and_taxonomies method, instead
9
+ they should use the KnewtonGraph object that allows for making test graphs through
10
+ Klique, from spreadsheets.
11
+
12
+ """
13
+
14
+ # SUPPORTED / CURRENT METHODS
15
+
16
+ def create_book_and_taxonomies book_and_taxonomies, partner_id=nil
17
+ params = partner_id.nil? ? {} : { 'partner_id' => partner_id }
18
+ kapi.post '/v0/graphs/book_and_taxonomies?' + URI.encode_www_form(params),
19
+ book_and_taxonomies
20
+ end
21
+
22
+ def basic_book_and_taxonomies_post_object unicode=false
23
+ file_base = unicode ? 'unicode_test_graph_definition.yml' : 'basic_test_graph_definition.yml'
24
+ graph_definition_file = File.join(File.dirname(File.dirname(__FILE__)), 'internal_test_graphs', file_base)
25
+ obj = YAML.load_file graph_definition_file
26
+ obj['graph']['name'] = "#{obj['graph']['name']}_#{random_string}"
27
+ obj
28
+ end
29
+
30
+ def create_basic_book_and_taxonomies partner_id=nil, unicode=false
31
+ response = create_book_and_taxonomies basic_book_and_taxonomies_post_object(unicode), partner_id
32
+ # return the graph id
33
+ response['objectId']
34
+ end
@@ -0,0 +1,448 @@
1
+
2
+ def get_node_by_id graph, node_id
3
+ graph['nodes'].find{|n| n['id'] == node_id}
4
+ end
5
+
6
+ def get_node_by_name graph, node_name
7
+ graph['nodes'].find{|n| n['name'] == node_name}
8
+ end
9
+
10
+ def get_nodes_by_name_expr graph, node_name_expr
11
+ graph['nodes'].select{|n| n['name'] =~ node_name_expr}
12
+ end
13
+
14
+ def find_random_atom_id graph
15
+ graph['nodes'].select{|n| n['type']=='module' and n['subtype']=='atom'}.sample['id']
16
+ end
17
+
18
+ # this will ensure module_id is an internal id
19
+ def internalize_module_id graph, module_id
20
+ if module_id =~ /^mref-/
21
+ found = graph['nodes'].find{|n| n['temp_id'] == module_id}
22
+ raise "No module with mref '#{module_id}' in graph: #{graph['id']}" unless found
23
+ found['id']
24
+ else
25
+ module_id
26
+ end
27
+ end
28
+
29
+ #returns one of concept, atom, bndl, pool, unknown
30
+ def get_node_type graph, module_id
31
+ node = get_node_by_id graph, module_id
32
+ node_type = case node['type']
33
+ when 'concept'
34
+ 'concept'
35
+ when 'module'
36
+ node['subtype']
37
+ else
38
+ raise "Unable to determine node type for node: #{module_id} - graph: #{graph['id']}"
39
+ end
40
+ Log.debug "get_node_type: type: #{node_type} - node: #{module_id} - graph: #{graph['id']}"
41
+ return node_type
42
+ end
43
+
44
+ def find_all_recommendable_modules graph
45
+ graph['nodes'].select{|node| node['type']=='module' and (node['subtype']=='atom' or node['subtype']=='bndl')}
46
+ end
47
+
48
+ def find_all_recommendable_module_ids graph
49
+ find_all_recommendable_modules(graph).map{|node| node['id']}
50
+ end
51
+
52
+ def find_all_learning_objectives graph
53
+ graph['nodes'].select{|n| n['type'] == 'learning_objective'}
54
+ end
55
+
56
+ def find_all_module_ids_contained_by_learning_objective_list graph, lref_list
57
+ all_learning_objectives = find_all_learning_objectives(graph)
58
+ expected_mrefs = lref_list.map do |lref|
59
+ lo_id = all_learning_objectives.find{|lo| lo['temp_id'] == lref[5..-1]}['id']
60
+ atom_ids = find_atoms_contained_by_bndl @graph, lo_id
61
+ atom_mrefs = atom_ids.map{|iid| get_node_by_id(@graph, iid)['temp_id']}
62
+ atom_mrefs
63
+ end.flatten.uniq
64
+ expected_mrefs
65
+ end
66
+
67
+ def find_all_taxon_ids graph
68
+ nodes = graph['nodes'].select{|node| node['type']=='module' and (node['subtype']=='atom' or node['subtype']=='bndl')}
69
+ nodes.map{|node| node['taxon_ids']}.flatten.uniq
70
+ end
71
+
72
+ def find_all_assessment_atoms graph
73
+ pool_ids = Set.new(graph['edges'].select{|edge| edge['type']=='assessed_by'}.map{|edge| edge['end']})
74
+ graph['edges'].select{|edge| pool_ids.include? edge['start']}.map{|edge| edge['end']}.uniq
75
+ end
76
+
77
+ def find_all_assessment_modules graph
78
+ @assess_mods_cache ||= Hash.new do |cache, graph|
79
+ cache[graph] = begin
80
+ Log.debug "Calculating assessment modules for graph: #{graph['id']}"
81
+ pool_ids = graph['nodes'].select{|n| n['subtype']=='pool'}.map{|n| n['id']}
82
+ assessment_pool_ids = graph['edges'].select{|e| e['type']=='assessed_by' and
83
+ pool_ids.include?(e['end'])
84
+ }.map{|e| e['end']}
85
+ assessment_atom_ids = graph['edges'].select{|e| assessment_pool_ids.include?(e['start']) and
86
+ e['type']=='contains'
87
+ }.map{|e| e['end']}.uniq
88
+ assessment_bndl_ids = graph['edges'].select{|e| assessment_atom_ids.include?(e['end']) and not
89
+ pool_ids.include?(e['start']) and
90
+ e['type']=='contains'
91
+ }.map{|e| e['start']}.uniq
92
+ # we need to filter out learning_objectives... so we do this
93
+ assessment_bndl_ids.select!{|nid| node = graph['nodes'].find{|n| n['id'] == nid}; node['subtype'] == 'bndl'}
94
+ assessment_atom_ids+assessment_bndl_ids
95
+ end
96
+ end
97
+ @assess_mods_cache[graph]
98
+ end
99
+
100
+ # above find_all_assessment_modules returns just modules IDs
101
+ # this method returns full module objects
102
+ def find_all_assessment_modules_full graph
103
+ mod_ids = find_all_assessment_modules(graph)
104
+ mod_ids.map{|mid| graph['nodes'].find{|n| n['id'] == mid}}
105
+ end
106
+
107
+ def find_all_instructional_modules graph
108
+ pool_ids = graph['nodes'].select{|n| n['subtype']=='pool'}.map{|n| n['id']}
109
+ instructional_pool_ids = graph['edges'].select{|e| e['type']=='taught_by' and
110
+ pool_ids.include?(e['end'])
111
+ }.map{|e| e['end']}
112
+ instructional_atom_ids = graph['edges'].select{|e| instructional_pool_ids.include?(e['start']) and
113
+ e['type']=='contains'
114
+ }.map{|e| e['end']}.uniq
115
+ instructional_bndl_ids = graph['edges'].select{|e| instructional_atom_ids.include?(e['end']) and not
116
+ pool_ids.include?(e['start']) and
117
+ e['type']=='contains'
118
+ }.map{|e| e['start']}.uniq
119
+ return instructional_atom_ids+instructional_bndl_ids
120
+ end
121
+
122
+ def find_atoms_contained_by_bndl graph, bndl_id
123
+ graph['edges'].
124
+ select{|e| e['start'] == bndl_id and e['type'] == 'contains' }.
125
+ map{|e| e['end']}
126
+ end
127
+
128
+ def find_all_bndl_ids graph
129
+ graph['nodes'].select{|node| node['type']=='module' and node['subtype']=='bndl'}.map{|node| node['id']}
130
+ end
131
+
132
+ def find_all_concept_ids graph
133
+ graph['nodes'].select{|node| node['type'] == 'concept'}.map{|node| node['id']}
134
+ end
135
+
136
+ def find_all_atom_ids graph
137
+ graph['nodes'].select{|node| node['subtype']=='atom' }.map{|node| node['id']}
138
+ end
139
+
140
+ def find_random_concept graph
141
+ graph['nodes'].select{|n| n['type']=='concept'}.sample['id']
142
+ end
143
+
144
+
145
+ def find_concept_by_name graph, name
146
+ graph['nodes'].select{|node| node['type'] == 'concept'}.select{|node| node['name'] == name}.first
147
+ end
148
+
149
+ def find_next_concept graph, concept_id
150
+ prereq_edges = graph['edges'].select{|e| e['type'] == 'prerequisite' and e['start'] == concept_id}
151
+ return nil if prereq_edges.empty?
152
+ prereq_edges.first['end']
153
+ end
154
+
155
+ def find_post_requisite_concepts graph
156
+ Log.debug "finding post requisites for graph: #{graph['id']}"
157
+ #get all concepts
158
+ concepts = graph['nodes'].select{|n| n['type'] == 'concept'}
159
+
160
+ #filter it to concepts for which no edge starts with them,
161
+ #but have edges that end with them
162
+ pre_req_edges = graph['edges'].select{|e| e['type']=='prerequisite'}
163
+ post_req_concepts = concepts.select{|c| !pre_req_edges.any?{|e| e['start']==c['id']} and pre_req_edges.any?{|e| e['end']==c['id']} }
164
+ post_req_concepts.map{|c| c['id']}
165
+ end
166
+
167
+ def find_post_requisite_concept graph
168
+ #pick a random one and return it
169
+ find_post_requisite_concepts(graph).sample
170
+ end
171
+
172
+ def find_prerequisite_concept graph
173
+ #get all concepts
174
+ concepts = graph['nodes'].select{|n| n['type'] == 'concept'}
175
+
176
+ #filter it to concepts for which no edge starts with them,
177
+ #but have edges that end with them
178
+ pre_req_edges = graph['edges'].select{|e| e['type']=='prerequisite'}
179
+ post_req_concepts = concepts.select{|c|
180
+ pre_req_edges.any?{|e| e['start']==c['id']} and
181
+ !pre_req_edges.any?{|e| e['end']==c['id']} }
182
+ #pick a random one and return it
183
+ post_req_concepts.sample['id']
184
+ end
185
+
186
+ def find_associated_assessing_bndls graph, concept_id
187
+ Log.debug "Finding assessing bundles for concept #{concept_id}"
188
+ #find the 'assessed_by' edge
189
+ assessed_by_pool_id = graph['edges'].find{|e| e['type']=='assessed_by' and e['start'] == concept_id}['end']
190
+
191
+ #find everything contained by the assessed by edge
192
+ assessing_atom_ids= graph['edges'].select{|e| e['type']=='contains' and e['start'] == assessed_by_pool_id}.map{|e| e['end']}
193
+
194
+ #find all the edges that contain an assessing atom
195
+ edges_that_end_in_assessing_atom = graph['edges'].select{|e| e['type'] == 'contains' and assessing_atom_ids.include?(e['end'])}
196
+
197
+ assessing_bndls = edges_that_end_in_assessing_atom.map{|e| e['start']}.select{|nid| graph['nodes'].find{|n| n['id']==nid}['subtype']=='bndl'}
198
+
199
+ assessing_bndls.uniq!
200
+ Log.debug "Concept: #{concept_id} has assessing bundles: #{assessing_bndls.join(', ')}"
201
+ assessing_bndls
202
+ end
203
+
204
+ def find_associated_assessing_bndl graph, concept_id
205
+ find_associated_assessing_bndls(graph, concept_id).sample
206
+ end
207
+
208
+ def get_map_of_assessing_bundles_for_post_requisite_concepts graph
209
+ Log.debug "Building map of all assessing bundles for all postrequisite concepts in #{graph['id']}"
210
+ assessing_bundles_for_postreq = {}
211
+ postreqs = find_post_requisite_concepts graph
212
+ postreqs.each do |postreq|
213
+ bundles = find_associated_assessing_bndls graph, postreq
214
+ assessing_bundles_for_postreq[postreq] = bundles
215
+ end
216
+ Log.debug "Done building map of all assessing bundles for all postrequisite concepts in #{graph['id']}"
217
+ assessing_bundles_for_postreq
218
+ end
219
+
220
+ # Returns a list containing all concepts that are attached through assessed_by or taught_by pools.
221
+ # takes atoms or bundles. when given a bundle it looks up all the atoms contained and looks for
222
+ #
223
+ def find_associated_concept_ids graph, module_id
224
+
225
+ atom_ids = case(get_node_by_id(graph, module_id)['subtype'])
226
+ when 'atom'
227
+ [module_id]
228
+ when 'bndl'
229
+ find_atoms_contained_by_bndl(graph, module_id)
230
+ else
231
+ raise "Not an atom or bndl"
232
+ end
233
+
234
+ # Get the pools that contain the atom
235
+ pool_ids = graph['edges'].select{|e| e['type'] == 'contains' and atom_ids.include?(e['end'])}.map{|e| e['start']}.uniq
236
+
237
+ # Some edges here may be nil, so make the map to start node a separate step to avoid errors.
238
+ edges_to_concepts = pool_ids.map{|p| graph['edges'].select{|e| (e['type'] == 'assessed_by' or e['type'] == 'taught_by') and e['end'] == p}}.flatten.uniq
239
+
240
+ # Get the concepts that are assessed or taught by the pools -- ignore any pools that didn't map to concepts
241
+ concepts = edges_to_concepts.select{|p| p != nil}.map{|p| p['start']}.uniq
242
+ end
243
+
244
+ #Gets full information for the first concept an atom is a member of
245
+ def find_associated_concept graph, atom_id
246
+ get_node_by_id graph, (find_associated_concept_ids graph, atom_id).first
247
+ end
248
+
249
+ def find_associated_atoms graph, concept_id
250
+ Log.info "Finding all atoms for concept #{concept_id}"
251
+ (find_associated_assessing_atoms graph, concept_id).concat(find_associated_instructional_atoms graph, concept_id)
252
+ end
253
+
254
+ def find_associated_assessing_atoms graph, concept_id
255
+ Log.info "Finding assessing atoms for concept #{concept_id}"
256
+
257
+ #find the 'assessed_by' edge
258
+ assessed_by_pool_id = graph['edges'].find{|e| e['type']=='assessed_by' and e['start'] == concept_id}['end']
259
+
260
+ #find everything contained by the assessed by edge
261
+ assessing_atom_ids= graph['edges'].select{|e| e['type']=='contains' and e['start'] == assessed_by_pool_id}.map{|e| e['end']}.uniq
262
+ end
263
+
264
+ def find_associated_assessing_atom graph, concept_id
265
+ (find_associated_assessing_atoms graph, concept_id).sample
266
+ end
267
+
268
+ def find_associated_instructional_bndls graph, concept_id
269
+ Log.info "Finding instructional bundles for concept #{concept_id}"
270
+
271
+ #find the 'taught_by' edge
272
+ taught_by_pool_id = graph['edges'].find{|e| e['type']=='taught_by' and e['start'] == concept_id}['end']
273
+
274
+ #find everything contained by the taught by edge
275
+ teaching_atom_ids= graph['edges'].select{|e| e['type']=='contains' and e['start'] == taught_by_pool_id}.map{|e| e['end']}
276
+
277
+ #find all the edges that contain an teaching atom
278
+ edges_that_end_in_teaching_atom = graph['edges'].select{|e| e['type'] == 'contains' and teaching_atom_ids.include?(e['end'])}
279
+
280
+ teaching_bndls = edges_that_end_in_teaching_atom.map{|e| e['start']}.select{|nid| graph['nodes'].find{|n| n['id']==nid}['subtype']=='bndl'}
281
+
282
+ teaching_bndls.uniq
283
+ end
284
+
285
+ def find_associated_instructional_atoms graph, concept_id
286
+ Log.info "Finding instructional atoms for concept #{concept_id}"
287
+
288
+ #find the 'taught_by' edge
289
+ taught_by_pool_id = graph['edges'].find{|e| e['type']=='taught_by' and e['start'] == concept_id}['end']
290
+
291
+ #find everything contained by the taught by edge
292
+ teaching_atom_ids= graph['edges'].select{|e| e['type']=='contains' and e['start'] == taught_by_pool_id}.map{|e| e['end']}.uniq
293
+ end
294
+
295
+
296
+ #return ['instructional'] or ['assessment'] or ['instructional','assessment']
297
+ def get_content_types graph, module_id
298
+
299
+ node_type = get_node_type graph, module_id
300
+ atom_ids = case node_type
301
+ when 'atom'
302
+ [module_id]
303
+ when 'bndl'
304
+ graph['edges'].select{|e| e['type'] == 'contains' and e['start'] == module_id}.map{|e| e['end']}
305
+ end
306
+
307
+ bndl_ids = find_all_bndl_ids graph
308
+
309
+ pool_ids = graph['edges'].select{|e| e['type'] == 'contains' and atom_ids.include?(e['end']) }.map{|e| e['start']}.select{|n| not bndl_ids.include?(n)}
310
+
311
+ types = []
312
+ if graph['edges'].any?{|e| e['type'] == 'assessed_by' and pool_ids.include?(e['end'])}
313
+ types << 'assessment'
314
+ end
315
+ if graph['edges'].any?{|e| e['type'] == 'taught_by' and pool_ids.include?(e['end'])}
316
+ types << 'instructional'
317
+ end
318
+
319
+ types
320
+ end
321
+
322
+
323
+ def build_out_paths paths, path_so_far, prereq_edges
324
+ cur_concept = path_so_far.last
325
+ next_concepts = prereq_edges.select{|e| e['start'] == cur_concept}.map{|e| e['end']}
326
+ if next_concepts.empty?
327
+ paths << path_so_far
328
+ else
329
+ next_concepts.each{|c| build_out_paths paths, path_so_far + [c], prereq_edges}
330
+ end
331
+ end
332
+
333
+ def get_graph_concept_paths graph
334
+ #find the starting concepts
335
+ prereq_edges = graph['edges'].select{|e| e['type'] == 'prerequisite'}
336
+ concept_ids = graph['nodes'].select{|n| n['type'] == 'concept'}.map{|n| n['id']}
337
+ starts = concept_ids.select{|c| not prereq_edges.any?{|e| e['end'] == c}}
338
+
339
+ #recursively build out the paths to ending concepts
340
+ paths = []
341
+ starts.each { |start| build_out_paths paths, [start], prereq_edges}
342
+ paths
343
+ end
344
+
345
+ def get_longest_concept_path graph
346
+ paths = get_graph_concept_paths graph
347
+ paths.group_by(&:size).max.last.last
348
+
349
+ end
350
+
351
+ def find_node_by_ref_label graph, label
352
+ nodes = graph['nodes'].select{ |n| n['temp_id'].split('-')[1] == label }
353
+ nodes[0]['temp_id']
354
+ end
355
+
356
+ def humanize_recommendation graph, rec
357
+ rec['module_names'] = rec['module_ids'].map{ |mid|
358
+ graph['nodes'].find{|n| n['id'] == mid}['name']
359
+ }
360
+ rec
361
+ end
362
+
363
+ def get_node_with_concept_and_content_type graph, module_id
364
+ node = get_node_by_id graph, module_id
365
+ content_type = get_content_types graph, module_id
366
+ if content_type.length == 1
367
+ content_type = content_type.first
368
+ end
369
+ concept = find_associated_concept graph, module_id
370
+ node['concept'] = concept
371
+ node['content_type'] = content_type
372
+
373
+ node
374
+ end
375
+
376
+ #helper function for finding a concept's child atoms and bundles
377
+ def get_associated_modules_by_type graph, concept_id, content_type
378
+ #find associated pool (prioritize assessment pools if multiple)
379
+ pool_id = nil
380
+ pools = []
381
+ graph['edges'].select{|e| e['start']==concept_id}.each do |e|
382
+ if e['type']==content_type and
383
+ !graph['nodes'].select{|n| n['id']==e['end'] and n['subtype']=='bndl'}.any?
384
+ pool_id = e['end']
385
+ end
386
+ pools << e['end']
387
+ end
388
+
389
+ #find associated atoms and bundles
390
+ atoms = graph['edges'].select{|e| e['start']==pool_id}.map{|e| e['end']}
391
+ bndls = graph['edges'].select{|e| atoms.include?(e['end']) and
392
+ !pools.include?(e['start'])
393
+ }.map{|e| e['start']}.uniq
394
+ return atoms + bndls
395
+ end
396
+
397
+ def get_taxon_eids_from_taxonomy taxonomy
398
+ # find the taxon eids... which is a bit harder than modules
399
+ #tref_prefix = "tref-#{taxonomy['name']}:"
400
+ tref_prefix = "tref-"
401
+ taxon_eids = taxonomy['taxons'].map do |taxon|
402
+ tref_suffix = taxon['name']
403
+ while taxon['parent_id']
404
+ taxon = taxonomy['taxons'].find{|t| t['id'] == taxon['parent_id']}
405
+ tref_suffix = "#{taxon['name']}|#{tref_suffix}"
406
+ end
407
+ tref_prefix + tref_suffix
408
+ end
409
+ taxon_eids
410
+ end
411
+
412
+
413
+ # batch_event_payload is a helper grift for making the body
414
+ # for POST /v0/registrations/{reg id}/batch-events
415
+ # it takes a list of module IDs (iid or eid)
416
+ # it figures out for each module if a graded or ungraded event is appropriate
417
+ # and makes the list of events accordingly
418
+ def create_batch_events_payload graph, module_ids
419
+
420
+ internal_module_ids = module_ids.map {|mid| internalize_module_id graph, mid }
421
+
422
+ module_content_types = internal_module_ids.map{|iid| get_content_types graph, iid }
423
+
424
+ batch_events_payload = []
425
+
426
+ module_ids.each_with_index do |mod_id, i|
427
+ event = {
428
+ 'module_id' => mod_id,
429
+ 'duration' => 12345,
430
+ 'is_complete' => true,
431
+ 'interaction_end_time' => days_from_now(-1),
432
+ }
433
+ if module_content_types[i] == ['instructional']
434
+ event.merge!({
435
+ 'type' => 'ungraded-events',
436
+ })
437
+ else
438
+ event.merge!({
439
+ 'type' => 'graded-events',
440
+ 'score' => 1.0,
441
+ 'is_correct' => true,
442
+ })
443
+ end
444
+ batch_events_payload << event
445
+ end
446
+
447
+ batch_events_payload
448
+ end