Merge pull request #1404 from omniscopeio/onboarding

Sets the new user up with an example set of agents
This commit is contained in:
Dominik Sander 2016-04-11 09:28:56 +02:00
commit 15622e2c7e
15 changed files with 424 additions and 90 deletions

View file

@ -81,6 +81,13 @@ UNLOCK_AFTER=1.hour
# Duration for which the user will be remembered without asking for credentials again.
REMEMBER_FOR=4.weeks
# Set to 'true' if you would prefer new users to start with a default set of agents
IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS=true
# Users can be given a default set of agents to get them started
# You can override this scenario with your own scenario via file path or URL
# DEFAULT_SCENARIO_FILE=path-or-url-to-scenario.json
#############################
# Email Configuration #
#############################

View file

@ -26,6 +26,7 @@ class Admin::UsersController < ApplicationController
respond_to do |format|
if @user.save
DefaultScenarioImporter.import(@user)
format.html { redirect_to admin_users_path, notice: "User '#{@user.username}' was successfully created." }
format.json { render json: @user, status: :ok, location: admin_users_path(@user) }
else

View file

@ -0,0 +1,11 @@
module Users
class RegistrationsController < Devise::RegistrationsController
after_action :create_default_scenario, only: :create
private
def create_default_scenario
DefaultScenarioImporter.import(@user) if @user.persisted?
end
end
end

View file

@ -0,0 +1,20 @@
require 'open-uri'
class DefaultScenarioImporter
def self.import(user)
return unless ENV['IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS'] == 'true'
seed(user)
end
def self.seed(user)
scenario_import = ScenarioImport.new()
scenario_import.set_user(user)
scenario_file = ENV['DEFAULT_SCENARIO_FILE'].presence || File.join(Rails.root, "data", "default_scenario.json")
begin
scenario_import.file = open(scenario_file)
raise "Import failed" unless scenario_import.valid? && scenario_import.import
ensure
scenario_import.file.close
end
return true
end
end

View file

@ -82,7 +82,10 @@ Huginn::Application.routes.draw do
post "/users/:user_id/update_location/:secret" => "web_requests#update_location" # legacy
devise_for :users,
controllers: { omniauth_callbacks: 'omniauth_callbacks' },
controllers: {
omniauth_callbacks: 'omniauth_callbacks',
registrations: 'users/registrations'
},
sign_out_via: [:post, :delete]
if Rails.env.development?

162
data/default_scenario.json Normal file
View file

@ -0,0 +1,162 @@
{
"schema_version": 1,
"name": "default-scenario",
"description": "This scenario has a few agents to get you started. Feel free to change them or delete them as you see fit!",
"source_url": false,
"guid": "ee4299225e6531c401a8bbbce0771ce4",
"tag_fg_color": "#ffffff",
"tag_bg_color": "#5bc0de",
"exported_at": "2016-04-03T18:24:42Z",
"agents": [
{
"type": "Agents::TriggerAgent",
"name": "Rain Notifier",
"disabled": false,
"guid": "361ee2e955d4726b52c8b044d4f75e25",
"options": {
"expected_receive_period_in_days": "2",
"rules": [
{
"type": "regex",
"value": "rain|storm",
"path": "conditions"
}
],
"message": "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
},
"keep_events_for": 0,
"propagate_immediately": false
},
{
"type": "Agents::WebsiteAgent",
"name": "XKCD Source",
"disabled": false,
"guid": "505c9bba65507c40e5786afff36f688c",
"options": {
"url": "http://xkcd.com",
"mode": "on_change",
"expected_update_period_in_days": 5,
"extract": {
"url": {
"css": "#comic img",
"value": "@src"
},
"title": {
"css": "#comic img",
"value": "@alt"
},
"hovertext": {
"css": "#comic img",
"value": "@title"
}
}
},
"schedule": "every_1d",
"keep_events_for": 0,
"propagate_immediately": false
},
{
"type": "Agents::EmailDigestAgent",
"name": "Afternoon Digest",
"disabled": false,
"guid": "65e8ae4533881537de3c346b5178b75d",
"options": {
"subject": "Your Afternoon Digest",
"expected_receive_period_in_days": "7"
},
"schedule": "5pm",
"propagate_immediately": false
},
{
"type": "Agents::EmailDigestAgent",
"name": "Morning Digest",
"disabled": false,
"guid": "b34eaee75d8dc67843c3bd257c213852",
"options": {
"subject": "Your Morning Digest",
"expected_receive_period_in_days": "30"
},
"schedule": "6am",
"propagate_immediately": false
},
{
"type": "Agents::WeatherAgent",
"name": "SF Weather Agent",
"disabled": false,
"guid": "bdae6dfdf9d01a123ddd513e695fd466",
"options": {
"location": "94103",
"api_key": "put-your-key-here"
},
"schedule": "10pm",
"keep_events_for": 0
},
{
"type": "Agents::WebsiteAgent",
"name": "iTunes Trailer Source",
"disabled": false,
"guid": "e9afa65457d0a736b9ec20a8dd452fc8",
"options": {
"url": "http://trailers.apple.com/trailers/home/rss/newtrailers.rss",
"mode": "on_change",
"type": "xml",
"expected_update_period_in_days": 5,
"extract": {
"title": {
"css": "item title",
"value": ".//text()"
},
"url": {
"css": "item link",
"value": ".//text()"
}
}
},
"schedule": "every_1d",
"keep_events_for": 0,
"propagate_immediately": false
},
{
"type": "Agents::EventFormattingAgent",
"name": "Comic Formatter",
"disabled": false,
"guid": "d86b069650edadfc61db9df767c8b65c",
"options": {
"instructions": {
"message": "<h2>{{title}}</h2><img src=\"{{url}}\"/> <p>{{hovertext}}</p>"
},
"matchers": [
],
"mode": "clean"
},
"keep_events_for": 2592000,
"propagate_immediately": false
}
],
"links": [
{
"source": 0,
"receiver": 3
},
{
"source": 1,
"receiver": 6
},
{
"source": 4,
"receiver": 0
},
{
"source": 5,
"receiver": 2
},
{
"source": 6,
"receiver": 2
}
],
"control_links": [
]
}

View file

@ -1,93 +1,6 @@
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
user = User.find_or_initialize_by(:email => ENV['SEED_EMAIL'] || "admin@example.com")
require_relative 'seeds/seeder'
if user.persisted?
puts "User with email '#{user.email}' already exists, not seeding."
exit
end
user.username = ENV['SEED_USERNAME'] || "admin"
user.password = ENV['SEED_PASSWORD'] || "password"
user.password_confirmation = ENV['SEED_PASSWORD'] || "password"
user.invitation_code = User::INVITATION_CODES.first
user.admin = true
user.save!
puts
puts
unless user.agents.where(:name => "SF Weather Agent").exists?
Agent.build_for_type("Agents::WeatherAgent", user,
:name => "SF Weather Agent",
:schedule => "10pm",
:options => { 'location' => "94103", 'api_key' => "put-your-key-here" }).save!
puts "NOTE: The example 'SF Weather Agent' will not work until you edit it and put in a free API key from http://www.wunderground.com/weather/api/"
end
unless user.agents.where(:name => "XKCD Source").exists?
Agent.build_for_type("Agents::WebsiteAgent", user,
:name => "XKCD Source",
:schedule => "every_1d",
:type => "html",
:options => {
'url' => "http://xkcd.com",
'mode' => "on_change",
'expected_update_period_in_days' => 5,
'extract' => {
'url' => { 'css' => "#comic img", 'value' => "@src" },
'title' => { 'css' => "#comic img", 'value' => "@alt" },
'hovertext' => { 'css' => "#comic img", 'value' => "@title" }
}
}).save!
end
unless user.agents.where(:name => "iTunes Trailer Source").exists?
Agent.build_for_type("Agents::WebsiteAgent", user, :name => "iTunes Trailer Source",
:schedule => "every_1d",
:options => {
'url' => "http://trailers.apple.com/trailers/home/rss/newtrailers.rss",
'mode' => "on_change",
'type' => "xml",
'expected_update_period_in_days' => 5,
'extract' => {
'title' => { 'css' => "item title", 'value' => ".//text()"},
'url' => { 'css' => "item link", 'value' => ".//text()"}
}
}).save!
end
unless user.agents.where(:name => "Rain Notifier").exists?
Agent.build_for_type("Agents::TriggerAgent", user,
:name => "Rain Notifier",
:source_ids => user.agents.where(:name => "SF Weather Agent").pluck(:id),
:options => {
'expected_receive_period_in_days' => "2",
'rules' => [{
'type' => "regex",
'value' => "rain|storm",
'path' => "conditions"
}],
'message' => "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
}).save!
end
unless user.agents.where(:name => "Morning Digest").exists?
Agent.build_for_type("Agents::EmailDigestAgent", user,
:name => "Morning Digest",
:schedule => "6am",
:options => { 'subject' => "Your Morning Digest", 'expected_receive_period_in_days' => "30" },
:source_ids => user.agents.where(:name => "Rain Notifier").pluck(:id)).save!
end
unless user.agents.where(:name => "Afternoon Digest").exists?
Agent.build_for_type("Agents::EmailDigestAgent", user,
:name => "Afternoon Digest",
:schedule => "5pm",
:options => { 'subject' => "Your Afternoon Digest", 'expected_receive_period_in_days' => "7" },
:source_ids => user.agents.where(:name => ["iTunes Trailer Source", "XKCD Source"]).pluck(:id)).save!
end
puts "See the Huginn Wiki for more Agent examples! https://github.com/cantino/huginn/wiki"
Seeder.seed

23
db/seeds/seeder.rb Normal file
View file

@ -0,0 +1,23 @@
class Seeder
def self.seed
user = User.find_or_initialize_by(:email => ENV['SEED_EMAIL'] || "admin@example.com")
if user.persisted?
puts "User with email '#{user.email}' already exists, not seeding."
exit
end
user.username = ENV['SEED_USERNAME'] || "admin"
user.password = ENV['SEED_PASSWORD'] || "password"
user.password_confirmation = ENV['SEED_PASSWORD'] || "password"
user.invitation_code = User::INVITATION_CODES.first
user.admin = true
user.save!
if DefaultScenarioImporter.seed(user)
puts "NOTE: The example 'SF Weather Agent' will not work until you edit it and put in a free API key from http://www.wunderground.com/weather/api/"
puts "See the Huginn Wiki for more Agent examples! https://github.com/cantino/huginn/wiki"
else
raise('Unable to import the default scenario')
end
end
end

View file

@ -0,0 +1,22 @@
require 'rails_helper'
describe Admin::UsersController do
describe 'POST #create' do
context 'with valid user params' do
it 'imports the default scenario for the new user' do
mock(DefaultScenarioImporter).import(is_a(User))
sign_in users(:jane)
post :create, :user => {username: 'jdoe', email: 'jdoe@example.com',
password: 's3cr3t55', password_confirmation: 's3cr3t55', admin: false }
end
end
context 'with invalid user params' do
it 'does not import the default scenario' do
stub(DefaultScenarioImporter).import(is_a(User)) { fail "Should not attempt import" }
sign_in users(:jane)
post :create, :user => {}
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'rails_helper'
module Users
describe RegistrationsController do
include Devise::TestHelpers
describe "POST create" do
context 'with valid params' do
it "imports the default scenario for the new user" do
mock(DefaultScenarioImporter).import(is_a(User))
@request.env["devise.mapping"] = Devise.mappings[:user]
post :create, :user => {username: 'jdoe', email: 'jdoe@example.com',
password: 's3cr3t55', password_confirmation: 's3cr3t55', admin: false, invitation_code: 'try-huginn'}
end
end
context 'with invalid params' do
it "does not import the default scenario" do
stub(DefaultScenarioImporter).import(is_a(User)) { fail "Should not attempt import" }
@request.env["devise.mapping"] = Devise.mappings[:user]
setup_controller_for_warden
post :create, :user => {}
end
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'rails_helper'
require_relative '../../../db/seeds/seeder'
describe Seeder do
before do
stub_puts_to_prevent_spew_in_spec_output
end
describe '.seed' do
it 'imports a default scenario' do
expect { Seeder.seed }.to change(Agent, :count).by(7)
end
it 'creates an admin' do
expect { Seeder.seed }.to change(User, :count).by(1)
expect(User.last).to be_admin
end
it 'can be run multiple times and exit normally' do
Seeder.seed
expect { Seeder.seed }.to raise_error(SystemExit)
end
end
def stub_puts_to_prevent_spew_in_spec_output
stub(Seeder).puts(anything)
stub(Seeder).puts
end
end

View file

@ -0,0 +1,68 @@
{
"schema_version": 1,
"name": "default-scenario",
"description": "This scenario has a few agents to get you started. Feel free to change them or delete them as you see fit!",
"source_url": false,
"guid": "ee4299225e6531c401a8bbbce0771ce4",
"tag_fg_color": "#ffffff",
"tag_bg_color": "#5bc0de",
"exported_at": "2016-04-03T18:24:42Z",
"agents": [
{
"type": "Agents::TriggerAgent",
"name": "Rain Notifier",
"disabled": false,
"guid": "361ee2e955d4726b52c8b044d4f75e25",
"options": {
"expected_receive_period_in_days": "2",
"rules": [
{
"type": "regex",
"value": "rain|storm",
"path": "conditions"
}
],
"message": "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
},
"keep_events_for": 0,
"propagate_immediately": false
},
{
"type": "Agents::EmailDigestAgent",
"name": "Morning Digest",
"disabled": false,
"guid": "b34eaee75d8dc67843c3bd257c213852",
"options": {
"subject": "Your Morning Digest",
"expected_receive_period_in_days": "30"
},
"schedule": "6am",
"propagate_immediately": false
},
{
"type": "Agents::WeatherAgent",
"name": "SF Weather Agent",
"disabled": false,
"guid": "bdae6dfdf9d01a123ddd513e695fd466",
"options": {
"location": "94103",
"api_key": "put-your-key-here"
},
"schedule": "10pm",
"keep_events_for": 0
}
],
"links": [
{
"source": 2,
"receiver": 0
},
{
"source": 0,
"receiver": 1
}
],
"control_links": [
]
}

View file

@ -0,0 +1,46 @@
require 'rails_helper'
describe DefaultScenarioImporter do
let(:user) { users(:bob) }
describe '.import' do
it 'imports a set of agents to get the user going when they are first created' do
mock(DefaultScenarioImporter).seed(is_a(User))
stub.proxy(ENV).[](anything)
stub(ENV).[]('IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS') { 'true' }
DefaultScenarioImporter.import(user)
end
it 'can be turned off' do
stub(DefaultScenarioImporter).seed { fail "seed should not have been called"}
stub.proxy(ENV).[](anything)
stub(ENV).[]('IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS') { 'false' }
DefaultScenarioImporter.import(user)
end
it 'is turned off for existing instances of Huginn' do
stub(DefaultScenarioImporter).seed { fail "seed should not have been called"}
stub.proxy(ENV).[](anything)
stub(ENV).[]('IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS') { nil }
DefaultScenarioImporter.import(user)
end
end
describe '.seed' do
it 'imports a set of agents to get the user going when they are first created' do
expect { DefaultScenarioImporter.seed(user) }.to change(user.agents, :count).by(7)
end
it 'respects an environment variable that specifies a path or URL to a different scenario' do
stub.proxy(ENV).[](anything)
stub(ENV).[]('DEFAULT_SCENARIO_FILE') { File.join(Rails.root, "spec", "fixtures", "test_default_scenario.json") }
expect { DefaultScenarioImporter.seed(user) }.to change(user.agents, :count).by(3)
end
it 'can not be turned off' do
stub.proxy(ENV).[](anything)
stub(ENV).[]('IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS') { 'true' }
expect { DefaultScenarioImporter.seed(user) }.to change(user.agents, :count).by(7)
end
end
end