command_proposal 1.0.14 → 1.0.15

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49fcf371fa04144d6c3a47f4cf8170bd30c1571c61f44d636df41591227f4d66
4
- data.tar.gz: 990a726d0441c95757ec17679e495b1300f4de2e5ac284df842bf82180c6912f
3
+ metadata.gz: 321b3627d2af7cf183da1cd1071775e7f13907b0fda6101acce253f9649f8a42
4
+ data.tar.gz: c9a30e58a53bb372807e05e765a7b92368433ced38325e8d7800e9f6b983eeb8
5
5
  SHA512:
6
- metadata.gz: 3d9d03fbe537ce4da08d6229829148ef8dbdbe1433ae56db6b08f97313a6329d41b2f63c8f433a197f34559521e37a652bad931f9be3a2eb0f4b12265184f1bf
7
- data.tar.gz: 24ba8d9ad1147fa9d6ee21298d547e774fd444ee0370bc79e629c1ad63fefb9d022aa95d9d78e56b1f58db7cf8d2e191731dd4970b88fd60484395c9ec376ccb
6
+ metadata.gz: ddcd08648f107fb5e50c57ae46a7ff93ddb1f7e79ea900e7a052afd6fb31574fd85fae1bf4a2214313a6fae8b46d79e4406d2be6756714319d928c259b612557
7
+ data.tar.gz: 629c1bc1f60e363396a7672122dc0a945c042baed8c43339fbc5ae7d80cb71aa645de0d3c199709d923cc1c1376e3d305b273ef3f71275ae53215899e59e3ef1
@@ -142,14 +142,16 @@ cmdDocReady(function() {
142
142
 
143
143
  console_input.textContent = ""
144
144
 
145
- var result = document.createElement("div")
146
- result.classList.add("result")
145
+ if (!(/^[\s\n]*$/.test(line.textContent))) {
146
+ var result = document.createElement("div")
147
+ result.classList.add("result")
147
148
 
148
- var spinner = document.createElement("i")
149
- spinner.className = "fa fa-circle-o-notch fa-spin cmd-icon-grey"
150
- result.append(spinner)
149
+ var spinner = document.createElement("i")
150
+ spinner.className = "fa fa-circle-o-notch fa-spin cmd-icon-grey"
151
+ result.append(spinner)
151
152
 
152
- line.appendChild(result)
153
+ line.appendChild(result)
154
+ }
153
155
 
154
156
  lines.appendChild(line)
155
157
  stored_entry = undefined
@@ -176,41 +178,77 @@ cmdDocReady(function() {
176
178
  body: JSON.stringify(params),
177
179
  done: function(res, status, req) {
178
180
  if (status == 200) {
179
- var json = JSON.parse(res)
180
- line.querySelector(".result").remove()
181
-
182
- var result = document.createElement("div")
183
- result.classList.add("result")
184
-
185
- if (json.error) {
186
- result.classList.add("cmd-error")
187
- result.textContent = json.error
188
- } else {
189
- var truncate = 2000
190
- if (json.result.length > truncate-3) {
191
- result.textContent = json.result.slice(0, truncate-3) + "..."
192
- var encoded = encodeURIComponent(json.result)
193
-
194
- var download = document.createElement("a")
195
- download.classList.add("cmd-truncated-download")
196
- download.setAttribute("href", "data:application/txt," + encoded)
197
- download.setAttribute("download", "result.txt")
198
- download.textContent = "Output truncated. Click here to download full result."
199
-
200
- line.insertAdjacentElement("afterend", download)
201
- } else {
202
- result.textContent = json.result
203
- }
204
- }
205
-
206
- line.appendChild(result)
181
+ handleSuccessfulCommand(evt, line, JSON.parse(res))
207
182
  } else {
208
183
  console.log("Error: ", res, req);
209
184
  }
210
- evt.finish()
211
185
  }
212
186
  })
213
187
  })
214
188
  }
189
+
190
+ function handleSuccessfulCommand(evt, line, json) {
191
+ if (json.error) {
192
+ addLineResult(line, json.error, "cmd-error")
193
+ } else if (json.status != "started") {
194
+ addLineResult(line, json.result, json.status == "failed" ? "cmd-error" : "")
195
+ } else {
196
+ return setTimeout(function() { pollIteration(evt, line, json.results_endpoint) }, 2000)
197
+ }
198
+
199
+ evt.finish()
200
+ }
201
+
202
+ function addLineResult(line, text, result_class) {
203
+ line.querySelector(".result").remove()
204
+
205
+ if (!text || /^[\s\n]*$/.test(text)) { return }
206
+
207
+ var result = document.createElement("div")
208
+ result.classList.add("result")
209
+ if (result_class) { result.classList.add(result_class) }
210
+
211
+ var truncate = 2000
212
+ if (text.length > truncate-3) {
213
+ result.textContent = text.slice(0, truncate-3) + "..."
214
+ var encoded = encodeURIComponent(text)
215
+
216
+ var download = document.createElement("a")
217
+ download.classList.add("cmd-truncated-download")
218
+ download.setAttribute("href", "data:application/txt," + encoded)
219
+ download.setAttribute("download", "result.txt")
220
+ download.textContent = "Output truncated. Click here to download full result."
221
+
222
+ line.insertAdjacentElement("afterend", download)
223
+ } else {
224
+ result.textContent = text
225
+ }
226
+
227
+ line.appendChild(result)
228
+ }
229
+
230
+ function pollIteration(evt, line, endpoint) {
231
+ var client = new HttpClient()
232
+ client.get(endpoint, {
233
+ headers: {
234
+ "Content-Type": "application/json",
235
+ "X-CSRF-Token": $.rails.csrfToken()
236
+ },
237
+ done: function(res, status, req) {
238
+ if (status == 200) {
239
+ var json = JSON.parse(res)
240
+ if (json.status == "started") {
241
+ setTimeout(function() { pollIteration(evt, line, endpoint) }, 2000)
242
+ } else {
243
+ addLineResult(line, json.result, json.status == "failed" ? "cmd-error" : "")
244
+ evt.finish()
245
+ }
246
+ } else {
247
+ console.log("Error: ", res, req);
248
+ evt.finish()
249
+ }
250
+ }
251
+ })
252
+ }
215
253
  }
216
254
  })
@@ -75,4 +75,7 @@
75
75
  font-size: 12px;
76
76
  color: grey;
77
77
  }
78
+ &.cmd-past-iterations tbody tr:nth-child(even) {
79
+ background: whitesmoke;
80
+ }
78
81
  }
@@ -9,6 +9,16 @@ class ::CommandProposal::IterationsController < ::CommandProposal::EngineControl
9
9
 
10
10
  layout "application"
11
11
 
12
+ def show
13
+ @iteration = ::CommandProposal::Iteration.find(params[:id])
14
+
15
+ render json: {
16
+ results_endpoint: cmd_path(@iteration),
17
+ result: @iteration.result,
18
+ status: @iteration.status
19
+ }
20
+ end
21
+
12
22
  def create
13
23
  return error!("You do not have permission to run commands.") unless can_command?
14
24
 
@@ -17,32 +27,32 @@ class ::CommandProposal::IterationsController < ::CommandProposal::EngineControl
17
27
  return error!("Can only run commands on type: :console") unless @task.console?
18
28
  return error!("Session has not been approved.") unless has_approval?(@task)
19
29
 
20
- if @task.iterations.many?
21
- runner = ::CommandProposal.sessions["task:#{@task.id}"]
22
- elsif @task.iterations.one?
30
+ if @task.iterations.one?
23
31
  # Track console details in first iteration
24
32
  @task.first_iteration.update(started_at: Time.current, status: :started)
25
- runner = ::CommandProposal::Services::Runner.new
26
- ::CommandProposal.sessions["task:#{@task.id}"] = runner
27
33
  end
28
34
 
29
- return error!("Session has expired. Please start a new session.") if runner.nil?
30
-
31
35
  @task.user = command_user # Separate from update to ensure it's set first
32
36
  @task.update(code: params[:code]) # Creates a new iteration
33
37
  @iteration = @task.current_iteration
34
38
  @iteration.update(status: :approved) # Task was already approved, and this is line-by-line
35
39
 
36
40
  # async, but wait for the job to finish
37
- ::CommandProposal::CommandRunnerJob.perform_later(@iteration.id)
41
+ ::CommandProposal::CommandRunnerJob.perform_later(@iteration.id, "task:#{@task.id}")
42
+
43
+ max_wait_seconds = 3
38
44
  loop do
39
- sleep 0.2
45
+ break unless max_wait_seconds.positive?
46
+
47
+ max_wait_seconds -= sleep 0.2
40
48
 
41
49
  break if @iteration.reload.complete?
42
50
  end
43
51
 
44
52
  render json: {
45
- result: @iteration.result
53
+ results_endpoint: cmd_path(@iteration),
54
+ result: @iteration.result,
55
+ status: @iteration.status
46
56
  }
47
57
  end
48
58
 
@@ -53,12 +53,12 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
53
53
  def create
54
54
  @task = ::CommandProposal::Task.new(task_params.except(:code))
55
55
  @task.user = command_user
56
- @task.skip_approval = true unless approval_required?
56
+ @task.skip_approval = true unless approval_required?(@task.session_type)
57
57
 
58
58
  # Cannot create the iteration until the task is created, so save then update
59
59
  if @task.save && @task.update(task_params)
60
60
  if @task.console?
61
- @task.iterations.create(requester: command_user) # Blank iteration to track approval
61
+ @task.code = nil # Creates a blank iteration to track approval
62
62
  redirect_to cmd_path(@task)
63
63
  else
64
64
  redirect_to cmd_path(:edit, @task)
@@ -72,7 +72,7 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
72
72
  def update
73
73
  @task = ::CommandProposal::Task.find_by!(friendly_id: params[:id])
74
74
  @task.user = command_user
75
- @task.skip_approval = true unless approval_required?
75
+ @task.skip_approval = true unless approval_required?(@task.session_type)
76
76
 
77
77
  if @task.update(task_params)
78
78
  redirect_to cmd_path(@task)
@@ -9,7 +9,7 @@ module CommandProposal
9
9
 
10
10
  def can_approve?(iteration)
11
11
  return false unless permitted_to_use?
12
- return true unless approval_required?
12
+ return true unless approval_required?(iteration.task.session_type)
13
13
  return if iteration.nil?
14
14
 
15
15
  command_user.try("#{cmd_config.role_scope}?") && !current_is_author?(iteration)
@@ -17,13 +17,18 @@ module CommandProposal
17
17
 
18
18
  def has_approval?(task)
19
19
  return false unless permitted_to_use?
20
- return true unless approval_required?
20
+ return true unless approval_required?(task.session_type)
21
21
 
22
22
  task&.approved?
23
23
  end
24
24
 
25
- def approval_required?
26
- cmd_config.approval_required?
25
+ def approval_required?(task_type=nil)
26
+ return false unless cmd_config.approval_required?
27
+ return true if task_type.blank?
28
+
29
+ skips = cmd_config.skip_approval_for_types.presence || []
30
+
31
+ Array.wrap(skips).map(&:to_sym).exclude?(task_type.to_sym)
27
32
  end
28
33
 
29
34
  def current_is_author?(iteration)
@@ -2,10 +2,26 @@ module CommandProposal
2
2
  class CommandRunnerJob < ApplicationJob
3
3
  queue_as :default
4
4
 
5
- def perform(iteration_id)
5
+ def perform(iteration_id, runner_key=nil)
6
6
  iteration = ::CommandProposal::Iteration.find(iteration_id)
7
+ runner = ::CommandProposal.sessions[runner_key] if runner_key.present?
7
8
 
8
- ::CommandProposal::Services::Runner.new.execute(iteration)
9
+ if runner_key.present? && runner.blank?
10
+ if iteration.task.console? && iteration.task.iterations.count > 2 # 1 for init, and the 1 for current running code
11
+ return ::CommandProposal::Services::Runner.new.quick_fail(
12
+ iteration,
13
+ "Session has expired. Please start a new session."
14
+ )
15
+ else
16
+ runner = ::CommandProposal::Services::Runner.new
17
+ end
18
+
19
+ ::CommandProposal.sessions[runner_key] = runner if runner_key.present?
20
+ else
21
+ runner ||= ::CommandProposal::Services::Runner.new
22
+ end
23
+
24
+ runner.execute(iteration)
9
25
  end
10
26
  end
11
27
  end
@@ -23,7 +23,6 @@ class ::CommandProposal::Iteration < ApplicationRecord
23
23
 
24
24
  TRUNCATE_COUNT = 2000
25
25
  # Also hardcoded in JS: app/assets/javascripts/command_proposal/console.js
26
- PAGINATION_PER = 2
27
26
 
28
27
  has_many :comments
29
28
  belongs_to :task
@@ -1,7 +1,7 @@
1
1
  <% if lines.none? && !(skip_empty ||= false) -%><div class="line"></div><% end
2
2
  -%><% lines.each do |iteration| -%>
3
3
  <div class="line"><%= iteration.code
4
- -%><div class="result"><%=
4
+ -%><div class="result <%= 'cmd-error' if iteration.failed? %>"><%=
5
5
  truncate = ::CommandProposal::Iteration::TRUNCATE_COUNT
6
6
  if iteration.result.present?
7
7
  iteration.result.truncate(truncate)
@@ -12,7 +12,7 @@
12
12
  end
13
13
  %></div
14
14
  ></div><%=
15
- if iteration.result.length > truncate
15
+ if iteration.result&.length.to_i > truncate
16
16
  link_to("Output truncated. Click here to download full result.", "data:application/txt,#{ERB::Util.url_encode(iteration.result)}", class: "cmd-truncated-download", download: "result.txt")
17
17
  end
18
18
  %><% end -%>
@@ -1,8 +1,12 @@
1
- <% if @task.iterations.many? %>
2
- <table class="cmd-table">
1
+ <% if @task.iterations.any? %>
2
+ <table class="cmd-table cmd-past-iterations">
3
3
  <thead>
4
4
  <th>Timestamp / Link</th>
5
5
  <th>Status</th>
6
+ <% if @iteration&.params.present? %>
7
+ <th>Params</th>
8
+ <% end %>
9
+ <th>Result</th>
6
10
  <!-- <th># Comments</th> -->
7
11
  <!-- <th>Diff</th> -->
8
12
  </thead>
@@ -15,6 +19,14 @@
15
19
  <%= link_to iteration.created_at.strftime("%b %-d, %Y at %H:%M"), cmd_path(@task, iteration: iteration.id) %>
16
20
  </td>
17
21
  <td><%= iteration.status.capitalize %></td>
22
+ <% if @iteration&.params.present? %>
23
+ <td>
24
+ <% iteration&.args.each do |arg_k, arg_v| %>
25
+ <span class=cmd-arg""><%= arg_k %>=<%= arg_v.to_s.truncate(50) %></span>
26
+ <% end %>
27
+ </td>
28
+ <% end %>
29
+ <td><%= iteration.result.to_s.truncate(200) %></td>
18
30
  <!-- <td><%#= iteration.comments.count %></td> -->
19
31
  <!-- <td><%#= link_to "Diff", cmd_path(@task, iteration: @iteration.id, diff: iteration.id) %></td> -->
20
32
  </tr>
@@ -13,6 +13,7 @@ module CommandProposal
13
13
  :approval_callback,
14
14
  :success_callback,
15
15
  :failed_callback,
16
+ :skip_approval_for_types,
16
17
  )
17
18
 
18
19
  def initialize
@@ -30,6 +31,7 @@ module CommandProposal
30
31
  @approval_callback = nil
31
32
  @success_callback = nil
32
33
  @failed_callback = nil
34
+ @skip_approval_for_types = nil
33
35
  end
34
36
 
35
37
  def user_class
@@ -51,6 +51,19 @@ module CommandProposal
51
51
  proposal
52
52
  end
53
53
 
54
+ def quick_fail(iteration, msg)
55
+ @iteration = iteration
56
+ prepare
57
+
58
+ @iteration.status = :failed
59
+ @iteration.result = msg
60
+
61
+ complete
62
+ proposal = ::CommandProposal::Service::ProposalPresenter.new(@iteration)
63
+ @iteration = nil
64
+ proposal
65
+ end
66
+
54
67
  def quick_run(friendly_id)
55
68
  task = ::CommandProposal::Task.module.find_by!(friendly_id: friendly_id)
56
69
  iteration = task&.primary_iteration
@@ -1,3 +1,3 @@
1
1
  module CommandProposal
2
- VERSION = "1.0.14"
2
+ VERSION = "1.0.15"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_proposal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.14
4
+ version: 1.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rocco Nicholls
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-17 00:00:00.000000000 Z
11
+ date: 2022-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails