Beeper.io is no more (#1808)

* Beeper.io is no more

* Avoid event propagation or scheduling of missing agents

* Update undefined_agents.html.erb with a link to the wiki
This commit is contained in:
Andrew Cantino 2016-11-27 15:30:50 -05:00 committed by GitHub
parent 8e36fdd60c
commit 8a14a57e00
5 changed files with 41 additions and 282 deletions

View file

@ -370,7 +370,7 @@ class Agent < ActiveRecord::Base
def receive!(options={})
Agent.transaction do
scope = Agent.
select("agents.id AS receiver_agent_id, events.id AS event_id").
select("agents.id AS receiver_agent_id, sources.type AS source_agent_type, agents.type AS receiver_agent_type, events.id AS event_id").
joins("JOIN links ON (links.receiver_id = agents.id)").
joins("JOIN agents AS sources ON (links.source_id = sources.id)").
joins("JOIN events ON (events.agent_id = sources.id AND events.id > links.event_id_at_creation)").
@ -379,10 +379,11 @@ class Agent < ActiveRecord::Base
scope = scope.where("agents.id in (?)", options[:only_receivers])
end
sql = scope.to_sql()
sql = scope.to_sql
agents_to_events = {}
Agent.connection.select_rows(sql).each do |receiver_agent_id, event_id|
Agent.connection.select_rows(sql).each do |receiver_agent_id, source_agent_type, receiver_agent_type, event_id|
next unless const_defined?(source_agent_type) && const_defined?(receiver_agent_type)
agents_to_events[receiver_agent_id.to_i] ||= []
agents_to_events[receiver_agent_id.to_i] << event_id
end
@ -417,6 +418,7 @@ class Agent < ActiveRecord::Base
return if schedule == 'never'
types = where(:schedule => schedule).group(:type).pluck(:type)
types.each do |type|
next unless const_defined?(type)
type.constantize.bulk_check(schedule)
end
end

View file

@ -1,129 +0,0 @@
module Agents
class BeeperAgent < Agent
cannot_be_scheduled!
cannot_create_events!
no_bulk_receive!
description <<-MD
Beeper agent sends messages to Beeper app on your mobile device via Push notifications.
You need a Beeper Application ID (`app_id`), Beeper REST API Key (`api_key`) and Beeper Sender ID (`sender_id`) [https://beeper.io](https://beeper.io)
You have to provide phone number (`phone`) of the recipient which have a mobile device with Beeper installed, or a `group_id` Beeper Group ID
Also you have to provide a message `type` which has to be `message`, `image`, `event`, `location` or `task`.
Depending on message type you have to provide additional fields:
##### Message
* `text` **required**
##### Image
* `image` **required** (Image URL or Base64-encoded image)
* `text` optional
##### Event
* `text` **required**
* `start_time` **required** (Corresponding to ISO 8601)
* `end_time` optional (Corresponding to ISO 8601)
##### Location
* `latitude` **required**
* `longitude` **required**
* `text` optional
##### Task
* `text` **required**
You can see additional documentation at [Beeper website](https://beeper.io/docs)
MD
BASE_URL = 'https://api.beeper.io/api'
TYPE_ATTRIBUTES = {
'message' => %w(text),
'image' => %w(text image),
'event' => %w(text start_time end_time),
'location' => %w(text latitude longitude),
'task' => %w(text)
}
MESSAGE_TYPES = TYPE_ATTRIBUTES.keys
TYPE_REQUIRED_ATTRIBUTES = {
'message' => %w(text),
'image' => %w(image),
'event' => %w(text start_time),
'location' => %w(latitude longitude),
'task' => %w(text)
}
def default_options
{
'type' => 'message',
'app_id' => '',
'api_key' => '',
'sender_id' => '',
'phone' => '',
'text' => '{{title}}'
}
end
def validate_options
%w(app_id api_key sender_id type).each do |attr|
errors.add(:base, "you need to specify a #{attr}") if options[attr].blank?
end
if options['type'].in?(MESSAGE_TYPES)
required_attributes = TYPE_REQUIRED_ATTRIBUTES[options['type']]
if required_attributes.any? { |attr| options[attr].blank? }
errors.add(:base, "you need to specify a #{required_attributes.join(', ')}")
end
else
errors.add(:base, 'you need to specify a valid message type')
end
unless options['group_id'].blank? ^ options['phone'].blank?
errors.add(:base, 'you need to specify a phone or group_id')
end
end
def working?
received_event_without_error? && !recent_error_logs?
end
def receive(incoming_events)
incoming_events.each do |event|
send_message(event)
end
end
def send_message(event)
mo = interpolated(event)
begin
response = HTTParty.post(endpoint_for(mo['type']), body: payload_for(mo), headers: headers)
error(response.body) if response.code != 201
rescue HTTParty::Error => e
error(e.message)
end
end
private
def headers
{
'X-Beeper-Application-Id' => options['app_id'],
'X-Beeper-REST-API-Key' => options['api_key'],
'Content-Type' => 'application/json'
}
end
def payload_for(mo)
mo.slice(*TYPE_ATTRIBUTES[mo['type']], 'sender_id', 'phone', 'group_id').to_json
end
def endpoint_for(type)
"#{BASE_URL}/#{type}s.json"
end
end
end

View file

@ -19,20 +19,20 @@
</ul>
<br/>
<p>
The issue most probably occurred because of one or more of the following reasons:
This issue probably occurred for one or more of the following reasons:
</p>
<ul>
<li>If the respective Agent is distributed as part of the Huginn application codebase, it may have been removed or moved to an Agent gem. Please see <a href="https://github.com/cantino/huginn/wiki/Dealing-with-Deleted-Agent-Types" target="_blank">this wiki page for more information</a>.</li>
<li>If the respective Agent is distributed as a Ruby gem, it might have been removed from the <code>ADDITIONAL_GEMS</code> environment setting.</li>
<li>If the respective Agent is distributed as part of the Huginn application codebase, it might have been removed from that either on purpose (because the Agent has been deprecated or been moved to an Agent gem) or accidentally. Please check if the Agent(s) in question are available in your Huginn codebase under the path <code>app/models/agents/</code>.</li>
</ul>
<br/>
<p>
You can fix the issue by adding the Agent(s) back to the application codebase by
You can fix this issue by:
</p>
<ul>
<li>deleting the respective Agent(s) from the database using the button below.</li>
<li>adding the respective Agent(s) to the the <code>ADDITIONAL_GEMS</code> environment setting. Please see <a href="https://github.com/cantino/huginn_agent" target="_blank">https://github.com/cantino/huginn_agent</a> for documentation on how to properly set it.</li>
<li>adding the respective Agent(s) code to the Huginn application codebase (in case it was deleted accidentally).</li>
<li>deleting the respective Agent(s) from the database using the button below.</li>
</ul>
<br/>
<div class="btn-group">

View file

@ -74,6 +74,13 @@ describe Agent do
Agent.run_schedule("midnight")
end
it "ignores unknown types" do
Agent.where(id: agents(:bob_weather_agent).id).update_all type: 'UnknownTypeAgent'
mock(Agents::WeatherAgent).bulk_check("midnight").once
mock(Agents::WebsiteAgent).bulk_check("midnight").once
Agent.run_schedule("midnight")
end
it "only runs agents with the given schedule" do
do_not_allow(Agents::WebsiteAgent).async_check
Agent.run_schedule("blah")
@ -283,13 +290,37 @@ describe Agent do
Agent.receive!
end
it "should not propogate to disabled Agents" do
it "should not propagate to disabled Agents" do
Agent.async_check(agents(:bob_weather_agent).id)
agents(:bob_rain_notifier_agent).update_attribute :disabled, true
mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
Agent.receive!
end
it "should not propagate to Agents with unknown types" do
Agent.async_check(agents(:jane_weather_agent).id)
Agent.async_check(agents(:bob_weather_agent).id)
Agent.where(id: agents(:bob_rain_notifier_agent).id).update_all type: 'UnknownTypeAgent'
mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
mock(Agent).async_receive(agents(:jane_rain_notifier_agent).id, anything).times(1)
Agent.receive!
end
it "should not propagate from Agents with unknown types" do
Agent.async_check(agents(:jane_weather_agent).id)
Agent.async_check(agents(:bob_weather_agent).id)
Agent.where(id: agents(:bob_weather_agent).id).update_all type: 'UnknownTypeAgent'
mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
mock(Agent).async_receive(agents(:jane_rain_notifier_agent).id, anything).times(1)
Agent.receive!
end
it "should log exceptions" do
mock.any_instance_of(Agents::TriggerAgent).receive(anything).once {
raise "foo"

View file

@ -1,145 +0,0 @@
require 'rails_helper'
describe Agents::BeeperAgent do
let(:base_params) {
{
'type' => 'message',
'app_id' => 'some-app-id',
'api_key' => 'some-api-key',
'sender_id' => 'sender-id',
'phone' => '+111111111111',
'text' => 'Some text'
}
}
subject {
agent = described_class.new(name: 'beeper-agent', options: base_params)
agent.user = users(:jane)
agent.save! and return agent
}
context 'validation' do
it 'valid' do
expect(subject).to be_valid
end
[:type, :app_id, :api_key, :sender_id].each do |attr|
it "invalid without #{attr}" do
subject.options[attr] = nil
expect(subject).not_to be_valid
end
end
it 'invalid with group_id and phone' do
subject.options['group_id'] ='some-group-id'
expect(subject).not_to be_valid
end
context '#message' do
it 'requires text' do
subject.options[:text] = nil
expect(subject).not_to be_valid
end
end
context '#image' do
before(:each) do
subject.options[:type] = 'image'
end
it 'invalid without image' do
expect(subject).not_to be_valid
end
it 'valid with image' do
subject.options[:image] = 'some-url'
expect(subject).to be_valid
end
end
context '#event' do
before(:each) do
subject.options[:type] = 'event'
end
it 'invalid without start_time' do
expect(subject).not_to be_valid
end
it 'valid with start_time' do
subject.options[:start_time] = Time.now
expect(subject).to be_valid
end
end
context '#location' do
before(:each) do
subject.options[:type] = 'location'
end
it 'invalid without latitude and longitude' do
expect(subject).not_to be_valid
end
it 'valid with latitude and longitude' do
subject.options[:latitude] = 15.0
subject.options[:longitude] = 16.0
expect(subject).to be_valid
end
end
context '#task' do
before(:each) do
subject.options[:type] = 'task'
end
it 'valid with text' do
expect(subject).to be_valid
end
end
end
context 'payload_for' do
it 'removes unwanted attributes' do
result = subject.send(:payload_for, {'type' => 'message', 'text' => 'text',
'sender_id' => 'sender', 'phone' => '+1', 'random_attribute' => 'unwanted'})
expect(result).to eq('{"text":"text","sender_id":"sender","phone":"+1"}')
end
end
context 'headers' do
it 'sets X-Beeper-Application-Id header with app_id' do
expect(subject.send(:headers)['X-Beeper-Application-Id']).to eq(base_params['app_id'])
end
it 'sets X-Beeper-REST-API-Key header with api_key' do
expect(subject.send(:headers)['X-Beeper-REST-API-Key']).to eq(base_params['api_key'])
end
it 'sets Content-Type' do
expect(subject.send(:headers)['Content-Type']).to eq('application/json')
end
end
context 'endpoint_for' do
it 'returns valid URL for message' do
expect(subject.send(:endpoint_for, 'message')).to eq('https://api.beeper.io/api/messages.json')
end
it 'returns valid URL for image' do
expect(subject.send(:endpoint_for, 'image')).to eq('https://api.beeper.io/api/images.json')
end
it 'returns valid URL for event' do
expect(subject.send(:endpoint_for, 'event')).to eq('https://api.beeper.io/api/events.json')
end
it 'returns valid URL for location' do
expect(subject.send(:endpoint_for, 'location')).to eq('https://api.beeper.io/api/locations.json')
end
it 'returns valid URL for task' do
expect(subject.send(:endpoint_for, 'task')).to eq('https://api.beeper.io/api/tasks.json')
end
end
end