bulk_ops 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/images/bulk_ops/github_logo.png +0 -0
  3. data/app/assets/javascripts/bulk_ops.js +14 -0
  4. data/app/assets/javascripts/bulk_ops/selections.js +24 -0
  5. data/app/assets/javascripts/selections.js +38 -0
  6. data/app/assets/javascripts/work_search.js +64 -0
  7. data/app/assets/stylesheets/bulk_ops.scss +99 -0
  8. data/app/controllers/bulk_ops/application_controller.rb +13 -0
  9. data/app/controllers/bulk_ops/github_authorization_controller.rb +33 -0
  10. data/app/controllers/bulk_ops/operations_controller.rb +481 -0
  11. data/app/jobs/bulk_ops/application_job.rb +4 -0
  12. data/app/mailers/bulk_ops/application_mailer.rb +6 -0
  13. data/app/models/bulk_ops/application_record.rb +5 -0
  14. data/app/views/bulk_ops/_bulk_ops_sidebar_widget.html.erb +15 -0
  15. data/app/views/bulk_ops/_github_auth_widget.html.erb +13 -0
  16. data/app/views/bulk_ops/operations/_bulk_ops_header.html.erb +4 -0
  17. data/app/views/bulk_ops/operations/_choose_fields.html.erb +22 -0
  18. data/app/views/bulk_ops/operations/_choose_notifications.html.erb +22 -0
  19. data/app/views/bulk_ops/operations/_git_message.html.erb +7 -0
  20. data/app/views/bulk_ops/operations/_ingest_options.html.erb +42 -0
  21. data/app/views/bulk_ops/operations/_operation_options.html.erb +38 -0
  22. data/app/views/bulk_ops/operations/_show_authorize.html.erb +13 -0
  23. data/app/views/bulk_ops/operations/_show_complete.html.erb +31 -0
  24. data/app/views/bulk_ops/operations/_show_draft.html.erb +20 -0
  25. data/app/views/bulk_ops/operations/_show_new.html.erb +2 -0
  26. data/app/views/bulk_ops/operations/_show_pending.html.erb +58 -0
  27. data/app/views/bulk_ops/operations/_show_running.html.erb +56 -0
  28. data/app/views/bulk_ops/operations/_show_verifying.html.erb +8 -0
  29. data/app/views/bulk_ops/operations/_show_waiting.html.erb +9 -0
  30. data/app/views/bulk_ops/operations/_update_draft_work_list.html.erb +45 -0
  31. data/app/views/bulk_ops/operations/_update_draft_work_search.html.erb +59 -0
  32. data/app/views/bulk_ops/operations/_update_options.html.erb +9 -0
  33. data/app/views/bulk_ops/operations/index.html.erb +51 -0
  34. data/app/views/bulk_ops/operations/new.html.erb +36 -0
  35. data/app/views/bulk_ops/operations/show.html.erb +7 -0
  36. data/config/routes.rb +25 -0
  37. data/db/migrate/20180926190757_create_github_credentials.rb +13 -0
  38. data/db/migrate/20181017180436_create_bulk_ops_tables.rb +40 -0
  39. data/lib/bulk_ops.rb +15 -0
  40. data/lib/bulk_ops/create_spreadsheet_job.rb +43 -0
  41. data/lib/bulk_ops/create_work_job.rb +14 -0
  42. data/lib/bulk_ops/delete_file_set_job.rb +15 -0
  43. data/lib/bulk_ops/engine.rb +6 -0
  44. data/lib/bulk_ops/error.rb +141 -0
  45. data/lib/bulk_ops/github_access.rb +284 -0
  46. data/lib/bulk_ops/github_credential.rb +3 -0
  47. data/lib/bulk_ops/operation.rb +358 -0
  48. data/lib/bulk_ops/relationship.rb +79 -0
  49. data/lib/bulk_ops/search_builder_behavior.rb +80 -0
  50. data/lib/bulk_ops/templates/configuration.yml +5 -0
  51. data/lib/bulk_ops/templates/readme.md +1 -0
  52. data/lib/bulk_ops/update_work_job.rb +14 -0
  53. data/lib/bulk_ops/verification.rb +210 -0
  54. data/lib/bulk_ops/verification_job.rb +23 -0
  55. data/lib/bulk_ops/version.rb +3 -0
  56. data/lib/bulk_ops/work_job.rb +104 -0
  57. data/lib/bulk_ops/work_proxy.rb +466 -0
  58. data/lib/generators/bulk_ops/install/install_generator.rb +27 -0
  59. data/lib/generators/bulk_ops/install/templates/config/github.yml.example +28 -0
  60. metadata +145 -0
@@ -0,0 +1,284 @@
1
+ require "octokit"
2
+ require "socket"
3
+ require "securerandom"
4
+ require 'base64'
5
+
6
+ class BulkOps::GithubAccess
7
+
8
+ ROW_OFFSET = 2
9
+ SPREADSHEET_FILENAME = 'metadata.csv'
10
+ OPTIONS_FILENAME = 'configuration.yml'
11
+
12
+ attr_accessor :name
13
+
14
+ def self.auth_url user
15
+ "#{Octokit.web_endpoint}login/oauth/authorize?client_id=#{client_id}&redirect_uri=#{redirect_endpoint(user)}&state=#{state(user)}&scope=repo"
16
+ end
17
+
18
+ def self.username user
19
+ return false unless (cred = BulkOps::GithubCredential.find_by(user_id: user))
20
+ return false unless (token = cred.oauth_token)
21
+ Octokit::Client.new(access_token: token).user[:login] rescue false
22
+ end
23
+
24
+ def self.valid_state?(astate,user_id=nil)
25
+ return (astate === state(user_id))
26
+ end
27
+
28
+ def self.state user_id=nil
29
+ user_id = current_user.id if user_id.nil?
30
+ user_id = user_id.id if user_id.is_a? User
31
+ user_id = user_id.to_i if user_id.is_a? String
32
+ cred = BulkOps::GithubCredential.find_by(user_id: user_id) || BulkOps::GithubCredential.create({user_id: user_id, state: SecureRandom.hex })
33
+ cred.state
34
+ end
35
+
36
+ def self.auth_token user_id=nil
37
+ user_id = current_user.id if user_id.nil?
38
+ user_id = user_id.id if user_id.is_a? User
39
+ user_id = user_id.to_i if user_id.is_a? String
40
+ return false unless (cred = BulkOps::GithubCredential.find_by(user_id: user_id))
41
+ return cred.auth_token || false
42
+ end
43
+
44
+ def self.set_auth_token!(code,user_id=nil)
45
+ user_id = current_user.id if user_id.nil?
46
+ user_id = user_id.id if user_id.is_a? User
47
+ user_id = user_id.to_i if user_id.is_a? String
48
+ cred = BulkOps::GithubCredential.find_by(user_id: user_id)
49
+ result = HTTParty.post("https://github.com/login/oauth/access_token",
50
+ body: {client_id: client_id,
51
+ client_secret: client_secret,
52
+ code: code,
53
+ accept: "json"}.to_json,
54
+ headers: { 'Content-Type' => 'application/json',
55
+ 'Accept' => 'application/json'})
56
+ cred.update(oauth_token: result.parsed_response["access_token"])
57
+ end
58
+
59
+ def self.client_id
60
+ YAML.load_file("#{Rails.root.to_s}/config/github.yml")[Rails.env]["client_id"]
61
+ end
62
+
63
+ def self.client_secret
64
+ YAML.load_file("#{Rails.root.to_s}/config/github.yml")[Rails.env]["client_secret"]
65
+ end
66
+
67
+ def self.webhook_token
68
+ YAML.load_file("#{Rails.root.to_s}/config/github.yml")[Rails.env]["client_secret"]
69
+ end
70
+
71
+ def self.redirect_endpoint user, ssl: false
72
+ host = Socket.gethostname
73
+ host = "localhost" if Rails.env.development? or Rails.env.test?
74
+ protocol = Rails.env.production? ? "https" : "http"
75
+ protocol = "https" if ssl
76
+ "#{protocol}://#{host}/bulk_ops/authorize/#{User.first.id}"
77
+ end
78
+
79
+ def self.create_branch! name
80
+ self.new(name).create_branch!
81
+ end
82
+
83
+ def self.add_file name, file_path, file_name = nil, message: false
84
+ self.new(name).add_file file_path, file_name, message: message
85
+ end
86
+
87
+ def self.add_contents name, file_name, contents, message: false
88
+ self.new(name).add_contents file_name, contents, message
89
+ end
90
+
91
+ def self.load_options name, branch: nil
92
+ self.new(name).load_options branch: branch
93
+ end
94
+
95
+ def self.load_metadata branch:, return_headers: false
96
+ self.new(branch).load_metadata return_headers: return_headers
97
+ end
98
+
99
+ def self.update_options name, options, message: false
100
+ self.new(name).update_options options, message: message
101
+ end
102
+
103
+ def self.list_branches
104
+ self.new.list_branches
105
+ end
106
+
107
+ def self.list_branch_names user
108
+ self.new.list_branch_names
109
+ end
110
+
111
+ def initialize(newname="dummy", user = nil)
112
+ @name = newname.parameterize
113
+ @user = user
114
+ end
115
+
116
+ def create_branch!
117
+ client.create_ref repo, "heads/#{name}", current_master_commit_sha
118
+ end
119
+
120
+ def delete_branch!
121
+ return false unless list_branch_names.include? name
122
+ client.delete_branch repo, name
123
+ end
124
+
125
+ def add_file file_path, file_name = nil, message: nil
126
+ file_name ||= File.basename(file_path)
127
+ # unless (file_name.downcase == "readme.md") || (file_name.downcase.include? "#{name}/")
128
+ file_name = File.join(name, file_name) unless file_name.downcase.include? "#{name.downcase}/"
129
+ message ||= "adding file #{file_name} to github branch #{name}"
130
+ client.create_contents(repo, file_name, message, file: file_path, branch: name)
131
+ end
132
+
133
+ def add_contents file_name, contents, message=false
134
+ message ||= "adding file #{file_name} to github branch #{name}"
135
+ begin
136
+ client.create_contents(repo, file_name, message, contents, branch: name)
137
+ rescue Octokit::UnprocessableEntity
138
+ sha = get_file_sha(file_name)
139
+ client.update_contents(repo, file_name, message, sha, contents, branch: name)
140
+ end
141
+ end
142
+
143
+ def add_new_spreadsheet file, message=false
144
+ if file.is_a? Tempfile
145
+ file.close
146
+ add_file file.path, SPREADSHEET_FILENAME, message: message
147
+ elsif file.is_a?(String) && File.file?(file)
148
+ add_file file, SPREADSHEET_FILENAME, message: message
149
+ elsif file.is_a? String
150
+ add_contents(spreadsheet_path, SPREADSHEET_FILENAME, message: message)
151
+ end
152
+ end
153
+
154
+ def list_branches
155
+ client.branches(repo).select{|branch| branch[:name] != "master"}
156
+ end
157
+
158
+ def list_branch_names
159
+ list_branches.map{|branch| branch[:name]}
160
+ end
161
+
162
+ def update_spreadsheet file, message: false
163
+ message ||= "updating metadata spreadsheet through hyrax browser interface."
164
+ sha = get_file_sha(spreadsheet_path)
165
+ file = File.new(file) if file.is_a?(String) && File.exist?(file)
166
+ client.update_contents(repo, spreadsheet_path, message, sha, file.read, branch: name)
167
+ end
168
+
169
+ def update_options options, message: false
170
+ if options['reference_column_name']
171
+ (options['ignored_columns'] ||= []) << options['reference_column_name']
172
+ (options['reference_identifier'] ||= []) << options['reference_column_name']
173
+ end
174
+
175
+ options['ignored_columns'] = options['ignored_columns'].reject { |c| c.empty? } if options['ignored_columns'].present?
176
+
177
+ message ||= "updating metadata spreadsheet through hyrax browser interface."
178
+ sha = get_file_sha(options_path)
179
+ client.update_contents(repo, options_path, message, sha, YAML.dump(options), branch: name)
180
+ end
181
+
182
+ def load_options branch: nil
183
+ branch ||= name
184
+ YAML.load(Base64.decode64(get_file_contents(options_path, ref: branch)))
185
+ end
186
+
187
+ def spreadsheet_count branch: nil
188
+ branch ||= name
189
+ Base64.decode64(get_file_contents(spreadsheet_path, ref: branch)).lines.count - 1
190
+ end
191
+
192
+ def load_metadata branch: nil, return_headers: false
193
+ branch ||= name
194
+ CSV.parse(Base64.decode64(get_file_contents(spreadsheet_path, ref: branch)), {headers: true, return_headers: return_headers})
195
+ end
196
+
197
+ def log_ingest_event log_level, row_number, event_type, message, commit_sha = nil
198
+ commit_sha ||= current_branch_commit_sha
199
+ #TODO WRITE THIS CODE
200
+ end
201
+
202
+ def create_pull_request message: false
203
+ begin
204
+ message ||= "Apply update #{name} through Hyrax browser interface"
205
+ pull = client.create_pull_request(repo, "master", name, message)
206
+ pull["number"]
207
+ rescue Octokit::UnprocessableEntity
208
+ return false
209
+ end
210
+ end
211
+
212
+ def can_merge?
213
+ return true
214
+ end
215
+
216
+ def merge_pull_request pull_id, message: false
217
+ client.merge_pull_request(repo, pull_id, message)
218
+ end
219
+
220
+ def get_metadata_row row_number
221
+ @current_metadata ||= load_metadata
222
+ @current_metadata[row_number - ROW_OFFSET]
223
+ end
224
+
225
+ def get_past_metadata_row commit_sha, row_number
226
+ past_metadata = Base64.decode64( client.contents(repo, path: filename, ref: commit_sha) )
227
+ past_metadata[row_number - ROW_OFFSET]
228
+ end
229
+
230
+ def get_file filename
231
+ client.contents(repo, path: filename, ref: name)
232
+ end
233
+
234
+ def get_file_contents filename, ref: nil
235
+ ref ||= name
236
+ client.contents(repo, path: filename, ref: ref)[:content]
237
+ end
238
+
239
+ def get_file_sha filename
240
+ client.contents(repo, path: filename, ref: name)[:sha]
241
+ end
242
+
243
+ def repo
244
+ github_config["repo"]
245
+ end
246
+
247
+ def spreadsheet_path
248
+ "#{name}/#{SPREADSHEET_FILENAME}"
249
+ end
250
+
251
+ private
252
+
253
+ def options_path
254
+ "#{name}/#{OPTIONS_FILENAME}"
255
+ end
256
+
257
+ def current_master_commit_sha
258
+ client.branch(repo,"master").commit.sha
259
+ end
260
+
261
+ def current_branch_commit_sha
262
+ client.branch(repo, name).commit.sha
263
+ end
264
+
265
+ def client
266
+ return @client if @client
267
+ return default_client if @user.nil?
268
+ return default_client unless (cred = BulkOps::GithubCredential.find_by(user_id: @user.id))
269
+ return default_client unless (token = cred.oauth_token)
270
+ client ||= Octokit::Client.new(access_token: token)
271
+ return default_client unless client.user[:login].is_a? String
272
+ @client = client
273
+ end
274
+
275
+ def default_client
276
+ @client ||= Octokit::Client.new(login: github_config["default_user"], password: github_config["default_password"])
277
+ end
278
+
279
+ def github_config
280
+ @github_config ||= YAML.load_file("#{Rails.root.to_s}/config/github.yml")[Rails.env]
281
+ end
282
+
283
+
284
+ end
@@ -0,0 +1,3 @@
1
+ class BulkOps::GithubCredential < ActiveRecord::Base
2
+ self.table_name = "bulk_ops_github_credentials"
3
+ end
@@ -0,0 +1,358 @@
1
+ require 'bulk_ops/verification'
2
+ module BulkOps
3
+ class Operation < ActiveRecord::Base
4
+ self.table_name = "bulk_ops_operations"
5
+ belongs_to :user
6
+ has_many :work_proxies, class_name: "::BulkOps::WorkProxy"
7
+
8
+ include BulkOps::Verification
9
+
10
+ attr_accessor :work_type, :visibility, :reference_identifier
11
+
12
+ delegate :can_merge?, :merge_pull_request, to: :git
13
+
14
+ INGEST_MEDIA_PATH = "/dams_ingest"
15
+ TEMPLATE_DIR = "lib/bulk_ops/templates"
16
+ RELATIONSHIP_COLUMNS = ["parent","child","next"]
17
+ SPECIAL_COLUMNS = ["parent",
18
+ "child",
19
+ "order",
20
+ "next",
21
+ "work_type",
22
+ "collection",
23
+ "collection_title",
24
+ "collection_id",
25
+ "visibility",
26
+ "relationship_identifier_type",
27
+ "id",
28
+ "filename",
29
+ "file"]
30
+ IGNORED_COLUMNS = ["ignore","offline_notes"]
31
+ OPTION_REQUIREMENTS = {type: {required: true,
32
+ values:[:ingest,:update]},
33
+ file_method: {required: :true,
34
+ values: [:replace_some,:add_remove,:replace_all]},
35
+ notifications: {required: true}}
36
+
37
+ def self.unique_name name, user
38
+ while BulkOps::Operation.find_by(name: name) || BulkOps::GithubAccess.list_branch_names(user).include?(name) do
39
+ if ['-','_'].include?(name[-2]) && name[-1].to_i > 0
40
+ name = name[0..-2]+(name[-1].to_i + 1).to_s
41
+ else
42
+ name = name + "_1"
43
+ end
44
+ end
45
+ return name
46
+ end
47
+
48
+ def proxy_errors
49
+ work_proxies.reduce([]) do |errors, proxy|
50
+ if proxy.proxy_errors
51
+ errors += proxy.proxy_errors
52
+ elsif proxy.status == "job_error"
53
+ errors += BulkOps::Error.new(type: :job_failure, object_id: proxy.work_id, message: proxy.message)
54
+ end
55
+ end
56
+ end
57
+
58
+ def proxy_states
59
+ states = {}
60
+ work_proxies.each{|proxy| (states[proxy.status] ||= []) << proxy }
61
+ states
62
+ end
63
+
64
+ def type
65
+ operation_type
66
+ end
67
+
68
+ def self.schema
69
+ ScoobySnacks::METADATA_SCHEMA
70
+ end
71
+
72
+ def schema
73
+ self.class.schema
74
+ end
75
+
76
+ def work_type
77
+ options["work_type"] || "work"
78
+ end
79
+
80
+ def reference_identifier
81
+ options["reference_identifier"] || "id/row"
82
+ end
83
+
84
+ def set_stage new_stage
85
+ update(stage: new_stage)
86
+ end
87
+
88
+ def apply!
89
+ status = "#{type}ing"
90
+ update({stage: "running", message: "#{type.titleize} initiated by #{user.name || user.email}"})
91
+ # @stage = "running"
92
+ final_spreadsheet
93
+
94
+ # This commented line currently fails because it doesn't pull from the master branch by default
95
+ # It's usually already verified, but maybe we should fix this for double-checking
96
+ # in the future
97
+ # return unless verify
98
+
99
+ apply_ingest! if ingest?
100
+ apply_update! if update?
101
+ end
102
+
103
+ def apply_ingest!
104
+ #Destroy any existing work proxies (which should not exist for an ingest). Create new proxies from finalized spreadsheet only.
105
+ work_proxies.each{|proxy| proxy.destroy!}
106
+
107
+ #create a work proxy for each row in the spreadsheet
108
+ @metadata.each_with_index do |values,row_number|
109
+ next if values.to_s.gsub(',','').blank?
110
+ work_proxies.create(status: "queued",
111
+ last_event: DateTime.now,
112
+ row_number: row_number,
113
+ message: "created during ingest initiated by #{user.name || user.email}")
114
+ end
115
+ # make sure the work proxies we just created are loaded in memory
116
+ reload
117
+ #loop through the work proxies to create a job for each work
118
+ @metadata.each_with_index do |values,row_number|
119
+ proxy = work_proxies.find_by(row_number: row_number)
120
+ proxy.update(message: "interpreted at #{DateTime.now.strftime("%d/%m/%Y %H:%M")} " + proxy.message)
121
+ data = proxy.interpret_data values
122
+ next unless proxy.proxy_errors.blank?
123
+ BulkOps::CreateWorkJob.perform_later(proxy.work_type || "Work",
124
+ user.email,
125
+ data,
126
+ proxy.id,
127
+ proxy.visibility || "open")
128
+ end
129
+ # If any errors have occurred, make sure they are logged in github and users are notified.
130
+ report_errors!
131
+ end
132
+
133
+ def delete_all
134
+ work_proxies.each do |proxy|
135
+ ActiveFedora::Base.find(proxy.work_id).destroy
136
+ proxy.update(status: "destroyed", message: "The work created by this proxy was destroyed by the user")
137
+ end
138
+ end
139
+
140
+ def check_if_finished
141
+ return unless stage == "running" && !busy?
142
+ update(stage: accumulated_errors.blank? ? "complete" : "errors" )
143
+ report_errors!
144
+ lift_holds
145
+ end
146
+
147
+ def lift_holds
148
+ work_proxies.each { |proxy| proxy.lift_hold}
149
+ end
150
+
151
+ def place_holds
152
+ work_proxies.each { |proxy| proxy.place_hold}
153
+ end
154
+
155
+ def apply_update! spreadsheet
156
+
157
+ # this array will keep track of any current proxies not included in the final spreadsheet
158
+ abandoned_proxies = work_proxies.dup
159
+ # Loop through the final spreadsheet
160
+ final_spreadsheet.each_with_index do |values,row_number|
161
+ # Grab the work id
162
+ work_id = false
163
+ values.each{|field,val| work_id = val if ["id","workid","recordid"].include?(field.downcase.gsub(/-_\s/,''))}
164
+ @operation_errors << BulkOps::Error.new(:no_work_id_field) unless work_id
165
+
166
+ #proxy = BulkOps::WorkProxy.find_by(operation_id: id, work_id: values["work_id"])
167
+ if (proxy = work_proxies.find_by(work_id: work_id))
168
+ abandoned_proxies.delete(proxy)
169
+ proxy.update(status: "updating",
170
+ row_number: row_number,
171
+ message: "update initiated by #{user.name || user.email}")
172
+ else
173
+ # Create a proxy for a work that is in the spreadsheet, but wasn't in the initial draft
174
+ work_proxies.create(status: "queued",
175
+ last_event: DateTime.now,
176
+ row_number: row_number,
177
+ message: "created during update application, which was initiated by #{user.name || user.email}")
178
+ end
179
+ end
180
+
181
+ # Loop through any proxies in the draft that were dropped from the spreadsheet
182
+ abandoned_proxies.each do |dead_proxy|
183
+ dead_proxy.lift_hold
184
+ dead_proxy.destroy!
185
+ end
186
+
187
+ #loop through the work proxies to create a job for each work
188
+ work_proxies.each do |proxy|
189
+ data = proxy.interpret_data final_spreadsheet[proxy.row_number]
190
+ BulkOps::UpdateWorkJob.perform_later(proxy.work_type || "",
191
+ user.email,
192
+ data,
193
+ proxy.id,
194
+ proxy.visibility || "private")
195
+ end
196
+ report_errors!
197
+ end
198
+
199
+ def accumulated_errors
200
+ proxy_errors + (@operation_errors || [])
201
+ # TODO - make sure this captures all operation errors
202
+ end
203
+
204
+ def report_errors!
205
+ error_file_name = BulkOps::Error.write_errors!(accumulated_errors, git)
206
+ notify!(subject: "Errors initializing bulk #{type} in Hycruz", message: "Hycruz encountered some errors while it was setting up your #{type} and preparing to begin. For most types of errors, the individual rows of the spreadsheet with errors will be ignored and the rest will proceed. Please consult the #{type} summary for real time information on the status of the #{type}. Details about these initialization errors can be seen on Github at the following url: https://github.com/#{git.repo}/blob/#{git.name}/#{git.name}/errors/#{error_file_name}") if error_file_name
207
+ end
208
+
209
+ def create_pull_request message: false
210
+ return false unless (pull_num = git.create_pull_request(message: message))
211
+ update(pull_id: pull_num)
212
+ return pull_num
213
+ end
214
+
215
+ def finalize_draft(fields: nil, work_ids: nil)
216
+ create_new_spreadsheet(fields: fields, work_ids: work_ids)
217
+ update(stage: "pending")
218
+ end
219
+
220
+ def create_branch(fields: nil, work_ids: nil, options: nil, operation_type: :ingest)
221
+ git.create_branch!
222
+ bulk_ops_dir = Gem::Specification.find_by_name("bulk_ops").gem_dir
223
+
224
+ #copy template files
225
+ Dir["#{bulk_ops_dir}/#{TEMPLATE_DIR}/*"].each do |file|
226
+ git.add_file file
227
+ end
228
+
229
+ #update configuration options
230
+ unless options.blank?
231
+ full_options = YAML.load_file(File.join(bulk_ops_dir,TEMPLATE_DIR, BulkOps::GithubAccess::OPTIONS_FILENAME))
232
+
233
+ puts "Creating new bulk_ops options"
234
+ puts "new options:"
235
+ puts options.inspect
236
+ puts "standard options:"
237
+ puts full_options.inspect
238
+
239
+ options.each { |option, value| full_options[option] = value }
240
+
241
+ full_options[name] = name
242
+ full_options[type] = type
243
+ full_options[status] = status
244
+
245
+ puts "final options:"
246
+ puts full_options.inspect
247
+
248
+ git.update_options full_options
249
+ end
250
+
251
+ create_new_spreadsheet(fields: fields, work_ids: work_ids) if operation_type == :ingest
252
+ end
253
+
254
+ def get_spreadsheet return_headers: false
255
+ git.load_metadata return_headers: return_headers
256
+ end
257
+
258
+ def spreadsheet_count
259
+ git.spreadsheet_count
260
+ end
261
+
262
+ def final_spreadsheet
263
+ @metadata ||= git.load_metadata branch: "master"
264
+ end
265
+
266
+ def update_spreadsheet file, message: nil
267
+ git.update_spreadsheet(file, message: message)
268
+ end
269
+
270
+ def update_options filename, message=nil
271
+ git.update_options(filename, message: message)
272
+ end
273
+
274
+ def options
275
+ return {} if name.nil?
276
+ return @options if @options
277
+ branch = running? ? "master" : nil
278
+ @options ||= git.load_options(branch: branch)
279
+ end
280
+
281
+ def draft?
282
+ return (stage == 'draft')
283
+ end
284
+
285
+ def running?
286
+ return (stage == 'running')
287
+ end
288
+
289
+ def complete?
290
+ return (stage == 'complete')
291
+ end
292
+
293
+ def busy?
294
+ return true if work_proxies.where(status: "running").count > 0
295
+ return true if work_proxies.where(status: "queued").count > 0
296
+ return true if work_proxies.where(status: "starting").count > 0
297
+ return false
298
+ end
299
+
300
+ def ingest?
301
+ type == "ingest"
302
+ end
303
+
304
+ def update?
305
+ type == "update"
306
+ end
307
+
308
+ def delete_branch
309
+ git.delete_branch!
310
+ end
311
+
312
+ def destroy
313
+ git.delete_branch!
314
+ super
315
+ end
316
+
317
+ def self.default_metadata_fields(labels = true)
318
+ #returns full set of metadata parameters from ScoobySnacks to include in ingest template spreadsheet
319
+ field_names = []
320
+ schema.all_fields.each do |field|
321
+ field_names << field.name
322
+ field_names << "#{field.name} Label" if labels && field.controlled?
323
+ end
324
+ return field_names
325
+ end
326
+
327
+ def ignored_fields
328
+ (options['ignored headers'] || []) + IGNORED_COLUMNS
329
+ end
330
+
331
+
332
+ def error_url
333
+ "https://github.com/#{git.repo}/tree/#{git.name}/#{git.name}/errors"
334
+ end
335
+
336
+ def filename_prefix
337
+ @filename_prefix ||= options['filename_prefix']
338
+ end
339
+
340
+ private
341
+
342
+ def git
343
+ @git ||= BulkOps::GithubAccess.new(name, @user)
344
+ end
345
+
346
+ def create_new_spreadsheet(fields: nil, work_ids: nil)
347
+ work_ids ||= work_proxies.map{|proxy| proxy.work_id}
348
+ fields ||= self.class.default_metadata_fields
349
+ work_ids = [] if work_ids.nil?
350
+ if work_ids.count < 50
351
+ BulkOps::CreateSpreadsheetJob.perform_now(git.name, work_ids, fields, user)
352
+ else
353
+ BulkOps::CreateSpreadsheetJob.perform_later(git.name, work_ids, fields, user)
354
+ end
355
+ end
356
+
357
+ end
358
+ end