Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs.openc3.com/docs/guides/scripting-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ The following API methods have been removed from COSMOS v7. Since WITH_UNITS wer
| wait_tolerance_raw | api_shared | Removed, use [wait_tolerance](#wait_tolerance) and pass type |
| wait_check_tolerance_raw | api_shared | Removed, use [wait_check_tolerance](#wait_check_tolerance) and pass type |

The following API methods now return `COSMOS__CANCEL` instead of `Cancel` when the Cancel button is pushed in Script Runner: `ask`, `ask_string`, `message_box`, `vertical_message_box`, `combo_box`, `check_box`, `prompt`, `prompt_for_hazardous`, `prompt_for_critical_cmd`, `metadata_input`, `open_file_dialog`, `open_files_dialog`, `open_bucket_dialog`. Unless you are _explicitly_ checking the return value for the word 'Cancel' there are no changes required.
The following API methods now use `COSMOS__CANCEL` instead of `Cancel` as the answer recorded when the Cancel button is pushed in Script Runner: `ask`, `ask_string`, `message_box`, `vertical_message_box`, `combo_box`, `check_box`, `prompt`, `prompt_for_hazardous`, `prompt_for_critical_cmd`, `metadata_input`, `open_file_dialog`, `open_files_dialog`, `open_bucket_dialog`. The script is paused at the prompt line and nothing is returned to it, so no changes are required. `COSMOS__CANCEL` only appears in the script output and the Script Runner internals.

### Migration from COSMOS v5 to v6

Expand Down Expand Up @@ -175,7 +175,7 @@ These methods allow the user to enter values that are needed by the script.

<span class="badge badge--secondary since-heading">Since 5.0.0</span>

Prompts the user for input with a question. User input is automatically converted from a string to the appropriate data type. For example if the user enters "1", the number 1 as an integer will be returned.
Prompts the user for input with a question. User input is automatically converted from a string to the appropriate data type. For example if the user enters "1", the number 1 as an integer will be returned. If the user clicks Cancel, the script is paused but remains at the prompt line. Pressing Go re-displays the prompt.

<Tabs groupId="script-language">
<TabItem value="python" label="Python Syntax">
Expand Down Expand Up @@ -228,7 +228,7 @@ password = ask("Enter your password", false, true)

<span class="badge badge--secondary since-heading">Since 5.0.0</span>

Prompts the user for input with a question. User input is always returned as a string. For example if the user enters "1", the string "1" will be returned.
Prompts the user for input with a question. User input is always returned as a string. For example if the user enters "1", the string "1" will be returned. If the user clicks Cancel, the script is paused but remains at the prompt line. Pressing Go re-displays the prompt.

<Tabs groupId="script-language">
<TabItem value="python" label="Python Syntax">
Expand Down Expand Up @@ -292,7 +292,7 @@ password = ask_string("Enter your password", false, true)

<span class="badge badge--secondary since-heading">Since 7.0.0</span>

The message_box, vertical_message_box, combo_box and check_box methods create a message box with buttons / selections / checkboxes that the user can click. The text of the button / selection / checkbox is returned.
The message_box, vertical_message_box, combo_box and check_box methods create a message box with buttons / selections / checkboxes that the user can click. The text of the button / selection / checkbox is returned. If the user clicks Cancel, the script is paused but remains at the prompt line. Pressing Go re-displays the prompt.

<Tabs groupId="script-language">
<TabItem value="python" label="Python Syntax">
Expand Down Expand Up @@ -379,7 +379,7 @@ end

<span class="badge badge--secondary since-heading">Since 5.0.0</span>

The open_file_dialog and open_files_dialog methods create a file dialog box so the user can select a single or multiple files. The selected file(s) is returned.
The open_file_dialog and open_files_dialog methods create a file dialog box so the user can select a single or multiple files. The selected file(s) is returned. If the user clicks Cancel, the script is paused but remains at the prompt line. Pressing Go re-displays the prompt.

Note: COSMOS 5 has deprecated the save_file_dialog and open_directory_dialog methods. save_file_dialog can be replaced by put_target_file if you want to write a file back to the target. open_directory_dialog doesn't make sense in new architecture so you must request individual files.

Expand Down Expand Up @@ -452,7 +452,7 @@ end

<span class="badge badge--secondary since-heading">Since 7.0.0</span>

The open_bucket_dialog method creates a dialog box that allows the user to browse S3 bucket files and select one. It presents the available buckets (similar to Bucket Explorer) and allows navigating directories within the selected bucket. The selected file is downloaded and returned as a file object, similar to open_file_dialog.
The open_bucket_dialog method creates a dialog box that allows the user to browse S3 bucket files and select one. It presents the available buckets (similar to Bucket Explorer) and allows navigating directories within the selected bucket. The selected file is downloaded and returned as a file object, similar to open_file_dialog. If the user clicks Cancel, the script is paused but remains at the prompt line. Pressing Go re-displays the prompt.

The returned file object also provides `filename`, `bucket`, and `bucket_path` (methods in Python) for the selected file's name, the bucket it came from (e.g. `OPENC3_CONFIG_BUCKET`), and its full path within that bucket (e.g. `DEFAULT/targets/INST/procedures/test.rb`). These are only available when running in Script Runner.

Expand Down Expand Up @@ -10321,7 +10321,7 @@ metadata_update({ 'key' => 'value' })

<span class="badge badge--secondary since-heading">Since 5.2.0</span>

Prompts the user to set existing metadata values or create new a new one.
Prompts the user to set existing metadata values or create a new one. If the user clicks Cancel, the script is paused but remains at the prompt line. Pressing Go re-displays the prompt.

<Tabs groupId="script-language">
<TabItem value="python" label="Python Example">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,9 @@ export default {
async scriptComplete() {
// Completion can also be reached through status refreshes or failed run
// requests rather than a queued 'complete' event.
// Stop the waiting timer on every path: the 'complete' message sets a
// terminal state without going through processLine()
this.clearWaiting()
this.queueReceivedOutputLines()
this.flushOutputLines(true)
// Supersede any scriptStart still awaiting its subscription. Must
Expand Down Expand Up @@ -2755,6 +2758,12 @@ export default {
this.results.text = data.report
this.results.show = true
}
// A crash in the script process itself (not the script thread)
// never publishes a terminal state with a line event, so take
// the final state from the 'complete' message
if (data.state) {
this.state = data.state
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
// Set before the teardown below: 'complete' is terminal even if
// scriptComplete() throws, and the catch would otherwise let the
// loop run on against a half-torn-down session.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,13 @@
end
end

if pid.nil? or not stopped
# Also need to cleanup the status model in this case because the process will not die and cleanup
running_script.end_time = Time.now.utc.iso8601
# Re-fetch because the process may have updated the model while stopping.
# If the process was killed by a signal (or never died) it never moved
# itself from running to completed so do that here. Setting state is a
# no-op if the process already reached a complete state.
running_script = OpenC3::ScriptStatusModel.get_model(name: params[:id], scope: params[:scope]) || running_script
if not running_script.is_complete?

Check warning on line 122 in openc3-cosmos-script-runner-api/app/controllers/running_script_controller.rb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `unless` instead of `if !` for simple negative conditions.

See more on https://sonarcloud.io/project/issues?id=OpenC3_cosmos&issues=AaGARS_lg9rDkFlZU_AF&open=AaGARS_lg9rDkFlZU_AF&pullRequest=4175
running_script.end_time ||= Time.now.utc.iso8601
running_script.state = "killed"
running_script.update
end
Expand Down
5 changes: 1 addition & 4 deletions openc3-cosmos-script-runner-api/scripts/run_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,7 @@ def run_script_log(script_id, message, color="BLACK", message_log=True):
f"Multiple input: {running_script.user_input}",
)
elif parsed_cmd["method"] == "open_bucket_dialog":
answer = parsed_cmd["answer"]
if isinstance(answer, str):
answer = json.loads(answer)
running_script.user_input = answer
running_script.user_input = parsed_cmd["answer"]
run_script_log(
script_id,
f"Bucket file: {running_script.user_input}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@
script_model = double("ScriptModel")
allow(script_model).to receive(:filename).and_return("INST/procedures/test.rb")
allow(script_model).to receive(:pid).and_return("12345")
allow(script_model).to receive(:is_complete?).and_return(false)
allow(script_model).to receive(:end_time).and_return(nil)
allow(script_model).to receive(:end_time=)
allow(script_model).to receive(:state=)
allow(script_model).to receive(:update)
allow(OpenC3::ScriptStatusModel).to receive(:get_model).and_return(script_model)
end

Expand All @@ -229,6 +234,48 @@
expect(response.status).to eq(200)
end

it "moves the script to completed if the process died without cleaning up" do
allow(Process).to receive(:getpgid).and_raise(Errno::ESRCH)
allow_any_instance_of(RunningScriptController).to receive(:running_script_publish)
script_model = OpenC3::ScriptStatusModel.get_model(name: "1", scope: "DEFAULT")
expect(script_model).to receive(:state=).with("killed")
expect(script_model).to receive(:update)

delete :delete, params: {id: "1", scope: "DEFAULT"}

expect(response.status).to eq(200)
end

it "leaves the script alone if the process already completed it" do
allow(Process).to receive(:getpgid).and_raise(Errno::ESRCH)
allow_any_instance_of(RunningScriptController).to receive(:running_script_publish)
script_model = OpenC3::ScriptStatusModel.get_model(name: "1", scope: "DEFAULT")
allow(script_model).to receive(:is_complete?).and_return(true)
expect(script_model).not_to receive(:update)

delete :delete, params: {id: "1", scope: "DEFAULT"}

expect(response.status).to eq(200)
end

it "re-fetches the model to see a completion written while stopping" do
allow(Process).to receive(:getpgid).and_raise(Errno::ESRCH)
allow_any_instance_of(RunningScriptController).to receive(:running_script_publish)
# The initial fetch sees the script running, then the process
# moves itself to completed before it exits
running_model = OpenC3::ScriptStatusModel.get_model(name: "1", scope: "DEFAULT")
completed_model = double("CompletedScriptModel")
allow(completed_model).to receive(:filename).and_return("INST/procedures/test.rb")
allow(completed_model).to receive(:is_complete?).and_return(true)
allow(OpenC3::ScriptStatusModel).to receive(:get_model).and_return(running_model, completed_model)
expect(running_model).not_to receive(:update)
expect(completed_model).not_to receive(:update)

delete :delete, params: {id: "1", scope: "DEFAULT"}

expect(response.status).to eq(200)
end

it "uses SIGINT if stop command doesn't work" do
# First call to Process.getpgid returns true (process still running)
# Second call raises ESRCH (process stopped after SIGINT)
Expand Down Expand Up @@ -257,6 +304,8 @@
script_model = double("ScriptModel")
allow(script_model).to receive(:filename).and_return("INST/procedures/test.rb")
allow(script_model).to receive(:pid).and_return("12345")
allow(script_model).to receive(:is_complete?).and_return(false)
allow(script_model).to receive(:end_time).and_return(nil)
allow(script_model).to receive(:end_time=) do |value|
@end_time = value
end
Expand All @@ -281,6 +330,8 @@
script_model = double("ScriptModel")
allow(script_model).to receive(:filename).and_return("INST/procedures/test.rb")
allow(script_model).to receive(:pid).and_return(nil)
allow(script_model).to receive(:is_complete?).and_return(false)
allow(script_model).to receive(:end_time).and_return(nil)
allow(script_model).to receive(:end_time=)
allow(script_model).to receive(:state=)
allow(script_model).to receive(:update)
Expand Down
Loading
Loading