Skip to content

Commit

Permalink
Merge pull request #1522 from OpenC3/activity
Browse files Browse the repository at this point in the history
Add uuid to activities
  • Loading branch information
jmthomas authored Sep 25, 2024
2 parents fdb08e1 + a6ac047 commit ebc6cbf
Show file tree
Hide file tree
Showing 18 changed files with 379 additions and 159 deletions.
54 changes: 18 additions & 36 deletions openc3-cosmos-cmd-tlm-api/app/controllers/activity_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ def index
render :json => model.as_json(:allow_nan => true), :status => 200
rescue ArgumentError
render :json => { :status => 'error', :message => 'Invalid date provided. Recommend ISO format' }, :status => 400
rescue OpenC3::ActivityInputError => e
render :json => { :status => 'error', :message => e.message, 'type' => e.class }, :status => 400
rescue StandardError => e
rescue StandardError => e # includes ActivityInputError
render :json => { :status => 'error', :message => e.message, 'type' => e.class }, :status => 400
end
end
Expand Down Expand Up @@ -106,14 +104,12 @@ def create
render :json => model.as_json(:allow_nan => true), :status => 201
rescue ArgumentError, TypeError => e
render :json => { :status => 'error', :message => "Invalid input: #{hash}", :type => e.class, :e => e.to_s }, :status => 400
rescue OpenC3::ActivityInputError => e
rescue StandardError => e # includes ActivityInputError
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 400
rescue OpenC3::ActivityOverlapError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 409
rescue OpenC3::ActivityError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 418
rescue StandardError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 400
end
end

Expand Down Expand Up @@ -203,8 +199,6 @@ def event
user: username()
)
render :json => model.as_json(:allow_nan => true), :status => 200
rescue ArgumentError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 400
rescue OpenC3::ActivityError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 418
rescue StandardError => e
Expand Down Expand Up @@ -257,13 +251,11 @@ def update
render :json => model.as_json(:allow_nan => true), :status => 200
rescue ArgumentError, TypeError => e
render :json => { :status => 'error', :message => "Invalid input: #{hash}", :type => e.class, :e => e.to_s }, :status => 400
rescue OpenC3::ActivityInputError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 400
rescue OpenC3::ActivityOverlapError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 409
rescue OpenC3::ActivityError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 418
rescue StandardError => e
rescue StandardError => e # includes OpenC3::ActivityInputError
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 400
end
end
Expand All @@ -284,20 +276,17 @@ def update
def destroy
return unless authorization('script_run')
begin
model = @model_class.score(name: params[:name], score: params[:id].to_i, scope: params[:scope])
if model.nil?
render :json => { :status => 'error', :message => 'not found' }, :status => 404
return
ret = @model_class.destroy(name: params[:name], scope: params[:scope], score: params[:id].to_i, uuid: params[:uuid], recurring: params[:recurring])
if ret == 0
render :json => { :status => 'error', :message => "Activity not found" }, :status => 404
else
OpenC3::Logger.info(
"Activity destroyed name: #{params[:name]} id:#{params[:id]} recurring:#{params[:recurring]}",
scope: params[:scope],
user: username()
)
render :json => { "status" => ret }, :status => 204
end
ret = model.destroy(recurring: params[:recurring])
OpenC3::Logger.info(
"Activity destroyed name: #{params[:name]} id:#{params[:id]} recurring:#{params[:recurring]}",
scope: params[:scope],
user: username()
)
render :json => { "status" => ret }, :status => 204
rescue OpenC3::ActivityError => e
render :json => { :status => 'error', :message => e.message, :type => e.class }, :status => 400
rescue StandardError => e
render :json => { :status => 'error', :message => e.message, :type => e.class, :e => e.to_s }, :status => 400
end
Expand Down Expand Up @@ -357,13 +346,11 @@ def multi_create
ret << model.as_json(:allow_nan => true)
rescue ArgumentError, TypeError => e
ret << { :status => 'error', :message => "Invalid input, #{e.message}", 'input' => input, 'type' => e.class, status => 400 }
rescue OpenC3::ActivityInputError => e
ret << { :status => 'error', :message => e.message, :input => input, :type => e.class, :err => 400 }
rescue OpenC3::ActivityOverlapError => e
ret << { :status => 'error', :message => e.message, :input => input, :type => e.class, :err => 409 }
rescue OpenC3::ActivityError => e
ret << { :status => 'error', :message => e.message, :input => input, :type => e.class, :err => 418 }
rescue StandardError => e
rescue StandardError => e # includes OpenC3::ActivityInputError
ret << { :status => 'error', :message => e.message, :input => input, :type => e.class, :err => 400 }
end
end
Expand Down Expand Up @@ -402,18 +389,13 @@ def multi_destroy

ret = Array.new
input_activities.each do |input|
next if input.is_a?(Hash) == false || input['id'].nil? || input['name'].nil?

model = @model_class.score(name: input['name'], score: input['id'], scope: params[:scope])
next if model.nil?
next if input.is_a?(Hash) == false || input['id'].nil? || input['name'].nil? || input['uuid'].nil?

begin
check = model.destroy()
result = @model_class.destroy(name: input[:name], score: input[:id].to_i, uuid: input[:uuid], scope: params[:scope])
OpenC3::Logger.info("Activity destroyed: #{input['name']}", scope: params[:scope], user: username())
ret << { 'status' => 'removed', 'removed' => check, 'input' => input, 'type' => e.class }
rescue OpenC3::ActivityError => e
ret << { :status => 'error', :message => e.message, :input => input, :type => e.class, :err => 400 }
rescue StandardError => e
ret << { 'status' => 'removed', 'removed' => result, 'input' => input, 'type' => e.class }
rescue StandardError => e # includes OpenC3::ActivityInputError
ret << { :status => 'error', :message => e.message, :input => input, :type => e.class, :err => 400 }
end
end
Expand Down
3 changes: 2 additions & 1 deletion openc3-cosmos-cmd-tlm-api/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
get '/timeline/:name/activity/:id', to: 'activity#show', name: /[^\/]+/, id: /[^\/]+/
post '/timeline/:name/activity/:id', to: 'activity#event', name: /[^\/]+/, id: /[^\/]+/
match '/timeline/:name/activity/:id', to: 'activity#update', name: /[^\/]+/, id: /[^\/]+/, via: [:patch, :put]
delete '/timeline/:name/activity/:id', to: 'activity#destroy', name: /[^\/]+/, id: /[^\/]+/
# NOTE: uuid is new as of 5.19.0
delete '/timeline/:name/activity/:id(/:uuid)', to: 'activity#destroy', name: /[^\/]+/, id: /[^\/]+/, uuid: /[^\/]+/

get '/autonomic/group', to: 'trigger_group#index'
post '/autonomic/group', to: 'trigger_group#create'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# if purchased from OpenC3, Inc.

require 'rails_helper'
require 'openc3/utilities/store_autoload'

RSpec.describe ActivityController, :type => :controller do
before(:each) do
Expand Down Expand Up @@ -254,15 +255,29 @@ def generate_activity_hash(start)
expect(response).to have_http_status(:created)
created = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
expect(created['start']).not_to be_nil
delete :destroy, params: {'scope'=>'DEFAULT', 'name'=>'test', 'id'=>created['start'], 'uuid'=>created['uuid']}
expect(response).to have_http_status(:no_content)
end

it "deletes items without uuids if uuid is not given" do
hash = generate_activity_hash(1.0)
post :create, params: hash.merge({ 'scope'=>'DEFAULT', 'name'=>'test' })
expect(response).to have_http_status(:created)
created = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
expect(created['start']).not_to be_nil
# We have to manually delete the uuid because it's always added
created.delete('uuid')
# Now add it back to the store and write over the existing activity
OpenC3::Store.zadd('DEFAULT__openc3_timelines__test', created['start'], JSON.generate(created))
delete :destroy, params: {'scope'=>'DEFAULT', 'name'=>'test', 'id'=>created['start']}
expect(response).to have_http_status(:no_content)
end

it "returns a status code 404" do
delete :destroy, params: {'scope'=>'DEFAULT', 'name'=>'test', "id"=>"200"}
delete :destroy, params: {'scope'=>'DEFAULT', 'name'=>'test', "id"=>"200", "uuid"=>"123456"}
ret = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
expect(ret['status']).to eql('error')
expect(ret['message']).not_to be_nil
expect(ret['message']).to eql("Activity not found")
expect(response).to have_http_status(:not_found)
end
end
Expand Down Expand Up @@ -342,7 +357,7 @@ def generate_activity_hash(start)
json = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
destroy_post_array = []
json.each do |hash|
destroy_post_array << {"name" => hash["name"], "id" => hash['start']}
destroy_post_array << {"name" => hash["name"], "id" => hash['start'], "uuid" => hash['uuid']}
end
post :multi_destroy, params: {'scope'=>'DEFAULT', 'multi'=>destroy_post_array}
expect(response).to have_http_status(:ok)
Expand All @@ -353,7 +368,6 @@ def generate_activity_hash(start)
end

it "returns an array and status code 200 with errors" do
dt = DateTime.now.new_offset(0)
destroy_post_array = [
{'id'=>'123456'},
'Test',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
act = create_timeline_activity("Mine", kind: "reserve", start: start, stop: stop)
tlas = get_timeline_activities("Mine")
check_expression("#{tlas.length} == 2")
delete_timeline_activity("Mine", act['start'])
delete_timeline_activity("Mine", act['start'], act['uuid'])
tlas = get_timeline_activities("Mine")
check_expression("#{tlas.length} == 1")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
act = create_timeline_activity("Mine", kind="reserve", start=start, stop=stop)
tlas = get_timeline_activities("Mine")
check_expression(f"{len(tlas)} == 2")
delete_timeline_activity("Mine", act["start"])
delete_timeline_activity("Mine", act["start"], act["uuid"])
tlas = get_timeline_activities("Mine")
check_expression(f"{len(tlas)} == 1")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,12 @@ export default {
})
let api = null
let start = null
let uuid = null
switch (item.type) {
case 'activity':
api = `timeline/${item.activity.name}/activity`
start = item.activity.start
uuid = item.activity.uuid
break
case 'metadata':
api = 'metadata'
Expand All @@ -288,7 +290,11 @@ export default {
)
.then((dialog) => {
this.localEvents.splice(deleteIndex, 1)
return Api.delete(`/openc3-api/${api}/${start}`)
let url = `/openc3-api/${api}/${start}`
if (uuid) {
url += `/${uuid}`
}
return Api.delete(url)
})
.then((response) => {
this.$emit('update')
Expand Down
28 changes: 28 additions & 0 deletions openc3/lib/openc3/migrations/20240915000000_activity_uuid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'openc3/utilities/migration'
require 'openc3/models/scope_model'
require 'openc3/models/timeline_model'

module OpenC3
class ActivityUuid < Migration
def self.run
ScopeModel.names.each do |scope|
TimelineModel.names.each do |key|
name = key.split('__').last
json = Store.zrange("#{scope}#{ActivityModel::PRIMARY_KEY}__#{name}", 0, -1)
parsed = json.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
parsed.each_with_index do |activity, index|
if activity['uuid'].nil?
activity['uuid'] = SecureRandom.uuid
Store.zrem("#{scope}#{ActivityModel::PRIMARY_KEY}__#{name}", json[index])
Store.zadd("#{scope}#{ActivityModel::PRIMARY_KEY}__#{name}", activity['start'], JSON.generate(activity))
end
end
end
end
end
end
end

unless ENV['OPENC3_NO_MIGRATE']
OpenC3::ActivityUuid.run
end
Loading

0 comments on commit ebc6cbf

Please sign in to comment.