Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agents can be archived #3253

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 15 additions & 3 deletions app/concerns/assignable_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@ def short_type
end

def validate_type
errors.add(:type, "cannot be changed once an instance has been created") if type_changed? && !new_record?
case type
when 'Agents::ArchivedAgent'
if new_record?
errors.add(:type, "cannot be #{type} for a new agent")
end
else
if !new_record? && type_changed?
errors.add(:type, "cannot be changed once an instance has been created")
end
end

errors.add(:type, "is not a valid type") unless self.class.valid_type?(type)
end

module ClassMethods
def load_types_in(module_name, my_name = module_name.singularize)
const_set(:MODULE_NAME, module_name)
const_set(:BASE_CLASS_NAME, my_name)
const_set(:TYPES, Dir[Rails.root.join("app", "models", module_name.underscore, "*.rb")].map { |path| module_name + "::" + File.basename(path, ".rb").camelize })
const_set(:TYPES, Dir[Rails.root.join("app", "models", module_name.underscore, "*.rb")].map { |path|
module_name + "::" + File.basename(path, ".rb").camelize
})
end

def types
Expand All @@ -44,4 +56,4 @@ def build_for_type(type, user, attributes = {})
end
end
end
end
end
42 changes: 29 additions & 13 deletions app/helpers/agent_helper.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
module AgentHelper

def agent_show_view(agent)
path = File.join('agents', 'agent_views', @agent.short_type.underscore, 'show')
return self.controller.template_exists?(path, [], true) ? path : nil
controller.template_exists?(path, [], true) ? path : nil
end

def toggle_disabled_text
Expand Down Expand Up @@ -32,7 +31,7 @@ def agent_schedule(agent, delimiter = ', ')
else
[
builtin_schedule_name(agent.schedule),
*(agent_controllers(agent, delimiter))
*agent_controllers(agent, delimiter)
].join(delimiter).html_safe
end
end
Expand Down Expand Up @@ -91,7 +90,21 @@ def agent_type_icon(agent, agents)

def agent_type_select_options
Rails.cache.fetch('agent_type_select_options') do
[['Select an Agent Type', 'Agent', {title: ''}]] + Agent.types.map {|type| [agent_type_to_human(type.name), type, {title: h(Agent.build_for_type(type.name, User.new(id: 0), {}).html_description.lines.first.strip)}] }
[
[
'Select an Agent Type',
'Agent',
{ title: '' }
],
*Agent.types.select(&:should_run?).map { |type|
agent = Agent.build_for_type(type.name, User.new(id: 0), {})
[
agent_type_to_human(type.name),
type,
{ title: h(agent.html_description.lines.first.strip) }
]
}
]
end
end

Expand All @@ -101,15 +114,18 @@ def links_counter_cache(agents)
@counter_cache ||= {}
@counter_cache[agents.__id__] ||= {}.tap do |cache|
agent_ids = agents.map(&:id)
cache[:links_as_receiver] = Hash[Link.where(receiver_id: agent_ids)
.group(:receiver_id)
.pluck(:receiver_id, Arel.sql('count(receiver_id) as id'))]
cache[:links_as_source] = Hash[Link.where(source_id: agent_ids)
.group(:source_id)
.pluck(:source_id, Arel.sql('count(source_id) as id'))]
cache[:control_links_as_controller] = Hash[ControlLink.where(controller_id: agent_ids)
.group(:controller_id)
.pluck(:controller_id, Arel.sql('count(controller_id) as id'))]
cache[:links_as_receiver] = Link.where(receiver_id: agent_ids)
.group(:receiver_id)
.pluck(:receiver_id, Arel.sql('count(receiver_id) as id'))
.to_h
cache[:links_as_source] = Link.where(source_id: agent_ids)
.group(:source_id)
.pluck(:source_id, Arel.sql('count(source_id) as id'))
.to_h
cache[:control_links_as_controller] = ControlLink.where(controller_id: agent_ids)
.group(:controller_id)
.pluck(:controller_id, Arel.sql('count(controller_id) as id'))
.to_h
end
end
end
69 changes: 67 additions & 2 deletions app/models/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,64 @@ def short_type
type.demodulize
end

def self.archive_all!
transaction do
id2type = connection.select_rows(
where.not(type: 'Agents::ArchivedAgent').select(:id, :type).to_sql
).to_h

agents = unscoped.where(id: id2type.keys)

agents.update_all(type: 'Agents::ArchivedAgent')

agents.find_each do |agent|
id = agent.id
options = {
"type" => id2type.fetch(id),
}.update(
agent.slice(
:schedule,
:last_check_at,
:last_receive_at,
:last_checked_event_id,
:last_web_request_at,
:last_event_at,
:last_error_log_at,
:keep_events_for,
:propagate_immediately,
:deactivated,
:disabled,
:options,
:memory,
)
)

agent.update_columns(
disabled: true,
options:,
memory: {}
)

agent.update!(keep_events_for: 0)
end

agents.reset
end
end

def self.unarchive_all!
transaction do
ids = []

where(type: 'Agents::ArchivedAgent').find_each do |agent|
ids << agent.id
agent.update_columns(agent.options)
end

unscoped.where(id: ids)
end
end

def check
# Implement me in your subclass of Agent.
end
Expand Down Expand Up @@ -233,6 +291,10 @@ def can_dry_run?
self.class.can_dry_run?
end

def should_run?
self.class.should_run?
end

def no_bulk_receive?
self.class.no_bulk_receive?
end
Expand Down Expand Up @@ -377,6 +439,10 @@ def can_dry_run?
!!@can_dry_run
end

def should_run?
true
end

def no_bulk_receive!
@no_bulk_receive = true
end
Expand Down Expand Up @@ -453,8 +519,7 @@ def async_receive(agent_id, event_ids)
def run_schedule(schedule)
return if schedule == 'never'

types = where(schedule:).group(:type).pluck(:type)
types.each do |type|
where(schedule:).group(:type).pluck(:type).each do |type|
next unless valid_type?(type)

type.constantize.bulk_check(schedule)
Expand Down
41 changes: 41 additions & 0 deletions app/models/agents/archived_agent.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module Agents
class ArchivedAgent < Agent
can_control_other_agents!

description <<~MD
The Archived Agent is a dormant and unresponsive agent that does nothing when it is run or when it receives an event.

It serves the sole purpose of keeping an agent that has been deleted or is no longer functional in an idle state, waiting for future fixes.
MD

def self.should_run?
false
end

def working?
false
end

def control_action
'control'
end

def validate_options
if options_changed?
errors.add(:base, "options cannot be edited")
end

if !disabled?
errors.add(:base, "cannot be enabled")
end
end

def check
# Do nada
end

def receive(incoming_events)
# Do nada
end
end
end
10 changes: 10 additions & 0 deletions spec/features/create_an_agent_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
login_as(users(:bob))
end

it "does not support ArchivedAgent" do
visit "/"
page.find("a", text: "Agents").hover
click_on("New Agent")

select2_open(from: 'Type')
expect(page).to have_select2_option('Website Agent', from: 'Type')
expect(page).not_to have_select2_option('Archived Agent', from: 'Type')
end

it "creates an agent" do
visit "/"
page.find("a", text: "Agents").hover
Expand Down
70 changes: 70 additions & 0 deletions spec/models/agent_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,76 @@
end
end

describe '.archive_all!/.unarchive_all!' do
let!(:agent_id) { agents(:bob_weather_agent).id }

it 'archives and unarchives Agents' do
expect {
Agent.where(id: [agent_id]).archive_all!
}.to change {
Agent.find(agent_id)
}.from(
be_a(Agents::WeatherAgent) & have_attributes(
disabled: false,
keep_events_for: be > 0,
options: hash_including(:location, :api_key)
)
).to(
be_a(Agents::ArchivedAgent) & have_attributes(
disabled: true,
keep_events_for: 0,
options: hash_including(
:schedule,
:last_check_at,
:last_receive_at,
:last_checked_event_id,
:last_web_request_at,
:last_event_at,
:last_error_log_at,
:propagate_immediately,
type: 'Agents::WeatherAgent',
keep_events_for: be > 0,
deactivated: false,
disabled: false,
options: hash_including(:location, :api_key)
)
)
)

expect {
Agent.where(id: [agent_id]).unarchive_all!
}.to change {
Agent.find(agent_id)
}.from(
be_a(Agents::ArchivedAgent) & have_attributes(
disabled: true,
keep_events_for: 0,
options: hash_including(
:schedule,
:last_check_at,
:last_receive_at,
:last_checked_event_id,
:last_web_request_at,
:last_event_at,
:last_error_log_at,
:propagate_immediately,
type: 'Agents::WeatherAgent',
keep_events_for: be > 0,
deactivated: false,
disabled: false,
options: hash_including(:location, :api_key)
)
)
).to(
be_a(Agents::WeatherAgent) & have_attributes(
disabled: false,
keep_events_for: be > 0,
options: hash_including(:location, :api_key)
)
)
end
end

describe ".bulk_check" do
before do
@weather_agent_count = Agents::WeatherAgent.where(schedule: "midnight", disabled: false).count
Expand Down