@@ -16,6 +16,12 @@ $ -> |
||
16 | 16 |
# Select2 Selects |
17 | 17 |
$(".select2").select2(width: 'resolve') |
18 | 18 |
|
19 |
+ $(".select2-linked-tags").select2( |
|
20 |
+ width: 'resolve', |
|
21 |
+ formatSelection: (obj) -> |
|
22 |
+ "<a href=\"#{this.element.data('urlPrefix')}/#{obj.id}/edit\" onClick=\"Utils.select2TagClickHandler(event, this)\">#{Utils.escape(obj.text)}</a>" |
|
23 |
+ ) |
|
24 |
+ |
|
19 | 25 |
# Helper for selecting text when clicked |
20 | 26 |
$('.selectable-text').each -> |
21 | 27 |
$(this).click -> |
@@ -132,3 +132,32 @@ class @Utils |
||
132 | 132 |
.fail (xhr, status, error) -> |
133 | 133 |
alert('Error: ' + error) |
134 | 134 |
callback() |
135 |
+ |
|
136 |
+ @select2TagClickHandler: (e, elem) -> |
|
137 |
+ if e.which == 1 |
|
138 |
+ window.location = $(elem).attr('href') |
|
139 |
+ else |
|
140 |
+ window.open($(elem).attr('href')) |
|
141 |
+ |
|
142 |
+ # _.escape from underscore: https://github.com/jashkenas/underscore/blob/1e68f06610fa4ecb7f2c45d1eb2ad0173d6a2cc1/underscore.js#L1411-L1436 |
|
143 |
+ escapeMap = |
|
144 |
+ '&': '&' |
|
145 |
+ '<': '<' |
|
146 |
+ '>': '>' |
|
147 |
+ '"': '"' |
|
148 |
+ '\'': ''' |
|
149 |
+ '`': '`' |
|
150 |
+ |
|
151 |
+ createEscaper = (map) -> |
|
152 |
+ escaper = (match) -> |
|
153 |
+ map[match] |
|
154 |
+ |
|
155 |
+ # Regexes for identifying a key that needs to be escaped. |
|
156 |
+ source = '(?:' + Object.keys(map).join('|') + ')' |
|
157 |
+ testRegexp = RegExp(source) |
|
158 |
+ replaceRegexp = RegExp(source, 'g') |
|
159 |
+ (string) -> |
|
160 |
+ string = if string == null then '' else '' + string |
|
161 |
+ if testRegexp.test(string) then string.replace(replaceRegexp, escaper) else string |
|
162 |
+ |
|
163 |
+ @escape = createEscaper(escapeMap) |
@@ -117,10 +117,12 @@ class @AgentEditPage |
||
117 | 117 |
$(".control-link-region").show() |
118 | 118 |
|
119 | 119 |
hideEventCreation: -> |
120 |
- $(".event-related-region").hide() |
|
120 |
+ $(".event-related-region .select2-container").hide() |
|
121 |
+ $(".event-related-region .cannot-create-events").show() |
|
121 | 122 |
|
122 | 123 |
showEventCreation: -> |
123 |
- $(".event-related-region").show() |
|
124 |
+ $(".event-related-region .select2-container").show() |
|
125 |
+ $(".event-related-region .cannot-create-events").hide() |
|
124 | 126 |
|
125 | 127 |
showEventDescriptions: -> |
126 | 128 |
if $("#agent_source_ids").val() |
@@ -24,16 +24,17 @@ class Agent < ActiveRecord::Base |
||
24 | 24 |
|
25 | 25 |
EVENT_RETENTION_SCHEDULES = [["Forever", 0], ['1 hour', 1.hour], ['6 hours', 6.hours], ["1 day", 1.day], *([2, 3, 4, 5, 7, 14, 21, 30, 45, 90, 180, 365].map {|n| ["#{n} days", n.days] })] |
26 | 26 |
|
27 |
- attr_accessible :options, :memory, :name, :type, :schedule, :controller_ids, :control_target_ids, :disabled, :source_ids, :scenario_ids, :keep_events_for, :propagate_immediately, :drop_pending_events |
|
27 |
+ attr_accessible :options, :memory, :name, :type, :schedule, :controller_ids, :control_target_ids, :disabled, :source_ids, :receiver_ids, :scenario_ids, :keep_events_for, :propagate_immediately, :drop_pending_events |
|
28 | 28 |
|
29 | 29 |
json_serialize :options, :memory |
30 | 30 |
|
31 | 31 |
validates_presence_of :name, :user |
32 | 32 |
validates_inclusion_of :keep_events_for, :in => EVENT_RETENTION_SCHEDULES.map(&:last) |
33 |
- validate :sources_are_owned |
|
34 |
- validate :controllers_are_owned |
|
35 |
- validate :control_targets_are_owned |
|
36 |
- validate :scenarios_are_owned |
|
33 |
+ validates :sources, owned_by: :user_id |
|
34 |
+ validates :receivers, owned_by: :user_id |
|
35 |
+ validates :controllers, owned_by: :user_id |
|
36 |
+ validates :control_targets, owned_by: :user_id |
|
37 |
+ validates :scenarios, owned_by: :user_id |
|
37 | 38 |
validate :validate_schedule |
38 | 39 |
validate :validate_options |
39 | 40 |
|
@@ -267,22 +268,6 @@ class Agent < ActiveRecord::Base |
||
267 | 268 |
|
268 | 269 |
private |
269 | 270 |
|
270 |
- def sources_are_owned |
|
271 |
- errors.add(:sources, "must be owned by you") unless sources.all? {|s| s.user_id == user_id } |
|
272 |
- end |
|
273 |
- |
|
274 |
- def controllers_are_owned |
|
275 |
- errors.add(:controllers, "must be owned by you") unless controllers.all? {|s| s.user_id == user_id } |
|
276 |
- end |
|
277 |
- |
|
278 |
- def control_targets_are_owned |
|
279 |
- errors.add(:control_targets, "must be owned by you") unless control_targets.all? {|s| s.user_id == user_id } |
|
280 |
- end |
|
281 |
- |
|
282 |
- def scenarios_are_owned |
|
283 |
- errors.add(:scenarios, "must be owned by you") unless scenarios.all? {|s| s.user_id == user_id } |
|
284 |
- end |
|
285 |
- |
|
286 | 271 |
def validate_schedule |
287 | 272 |
unless cannot_be_scheduled? |
288 | 273 |
errors.add(:schedule, "is not a valid schedule") unless SCHEDULES.include?(schedule.to_s) |
@@ -0,0 +1,6 @@ |
||
1 |
+class OwnedByValidator < ActiveModel::EachValidator |
|
2 |
+ def validate_each(record, attribute, association) |
|
3 |
+ return if association.all? {|s| s[options[:with]] == record[options[:with]] } |
|
4 |
+ record.errors[attribute] << "must be owned by you" |
|
5 |
+ end |
|
6 |
+end |
@@ -68,7 +68,7 @@ |
||
68 | 68 |
<%= f.select(:control_target_ids, |
69 | 69 |
options_for_select(current_user.agents.map {|s| [s.name, s.id] }, |
70 | 70 |
@agent.control_target_ids), |
71 |
- {}, { multiple: true, size: 5, class: 'select2 form-control' }) %> |
|
71 |
+ {}, { multiple: true, size: 5, class: 'select2-linked-tags form-control', data: {url_prefix: '/agents'}}) %> |
|
72 | 72 |
</div> |
73 | 73 |
</div> |
74 | 74 |
</div> |
@@ -83,12 +83,13 @@ |
||
83 | 83 |
|
84 | 84 |
<div class="form-group"> |
85 | 85 |
<%= f.label :sources %> |
86 |
+ <span class="glyphicon glyphicon-question-sign hover-help" data-content="This Agent will receive events from the selected Agents."></span> |
|
86 | 87 |
<div class="link-region" data-can-receive-events="<%= @agent.can_receive_events? %>"> |
87 | 88 |
<% eventSources = (current_user.agents - [@agent]).find_all { |a| a.can_create_events? } %> |
88 | 89 |
<%= f.select(:source_ids, |
89 | 90 |
options_for_select(eventSources.map {|s| [s.name, s.id] }, |
90 | 91 |
@agent.source_ids), |
91 |
- {}, { :multiple => true, :size => 5, :class => 'select2 form-control' }) %> |
|
92 |
+ {}, { :multiple => true, :size => 5, :class => 'select2-linked-tags form-control', data: {url_prefix: '/agents'} }) %> |
|
92 | 93 |
<span class='cannot-receive-events text-info'>This type of Agent cannot receive events.</span> |
93 | 94 |
<%= f.label :propagate_immediately, :class => 'propagate-immediately' do %>Propagate immediately |
94 | 95 |
<%= f.check_box :propagate_immediately %> |
@@ -98,13 +99,26 @@ |
||
98 | 99 |
</div> |
99 | 100 |
</div> |
100 | 101 |
|
102 |
+ <div class="form-group"> |
|
103 |
+ <%= f.label :receivers %> |
|
104 |
+ <span class="glyphicon glyphicon-question-sign hover-help" data-content="Events created by this Agent will be sent to the selected Agents."></span> |
|
105 |
+ <div class="event-related-region"> |
|
106 |
+ <% eventTargets = (current_user.agents - [@agent]).find_all { |a| a.can_receive_events? } %> |
|
107 |
+ <%= f.select(:receiver_ids, |
|
108 |
+ options_for_select(eventTargets.map {|s| [s.name, s.id] }, |
|
109 |
+ @agent.receiver_ids), |
|
110 |
+ {}, { :multiple => true, :size => 5, :class => 'select2-linked-tags form-control', data: {url_prefix: '/agents'} }) %> |
|
111 |
+ <span class='cannot-create-events text-info'>This type of Agent cannot create events.</span> |
|
112 |
+ </div> |
|
113 |
+ </div> |
|
114 |
+ |
|
101 | 115 |
<% if current_user.scenario_count > 0 %> |
102 | 116 |
<div class="form-group"> |
103 | 117 |
<%= f.label :scenarios %> |
104 | 118 |
<span class="glyphicon glyphicon-question-sign hover-help" data-content="Use Scenarios to group sets of Agents, both for organization, and to make them easy to export and share."></span> |
105 | 119 |
<%= f.select(:scenario_ids, |
106 | 120 |
options_for_select(current_user.scenarios.pluck(:name, :id), @agent.scenario_ids), |
107 |
- {}, { :multiple => true, :size => 5, :class => 'select2 form-control' }) %> |
|
121 |
+ {}, { :multiple => true, :size => 5, :class => 'select2-linked-tags form-control', data: {url_prefix: '/scenarios'} }) %> |
|
108 | 122 |
</div> |
109 | 123 |
<% end %> |
110 | 124 |
|
@@ -53,7 +53,7 @@ |
||
53 | 53 |
<%= f.label :agents %> |
54 | 54 |
<%= f.select(:agent_ids, |
55 | 55 |
options_for_select(current_user.agents.pluck(:name, :id), @scenario.agent_ids), |
56 |
- {}, { :multiple => true, :size => 5, :class => 'select2 form-control' }) %> |
|
56 |
+ {}, { :multiple => true, :size => 5, :class => 'select2-linked-tags form-control', data: {url_prefix: '/agents'} }) %> |
|
57 | 57 |
</div> |
58 | 58 |
</div> |
59 | 59 |
</div> |
@@ -40,4 +40,4 @@ en: |
||
40 | 40 |
other: ">%{count}yr" |
41 | 41 |
almost_x_years: |
42 | 42 |
one: "~1yr" |
43 |
- other: "~%{count}yr" |
|
43 |
+ other: "~%{count}yr" |
@@ -203,6 +203,18 @@ describe AgentsController do |
||
203 | 203 |
expect(assigns(:agent)).to be_a(Agents::WebsiteAgent) |
204 | 204 |
end |
205 | 205 |
|
206 |
+ it "creates Agents and accepts specifing a target agent" do |
|
207 |
+ sign_in users(:bob) |
|
208 |
+ attributes = valid_attributes |
|
209 |
+ attributes[:receiver_ids] = attributes[:source_ids] |
|
210 |
+ expect { |
|
211 |
+ expect { |
|
212 |
+ post :create, :agent => attributes |
|
213 |
+ }.to change { users(:bob).agents.count }.by(1) |
|
214 |
+ }.to change { Link.count }.by(2) |
|
215 |
+ expect(assigns(:agent)).to be_a(Agents::WebsiteAgent) |
|
216 |
+ end |
|
217 |
+ |
|
206 | 218 |
it "shows errors" do |
207 | 219 |
sign_in users(:bob) |
208 | 220 |
expect { |
@@ -1,8 +1,11 @@ |
||
1 | 1 |
require 'capybara_helper' |
2 | 2 |
|
3 | 3 |
describe "Creating a new agent", js: true do |
4 |
- it "creates an agent" do |
|
4 |
+ before(:each) do |
|
5 | 5 |
login_as(users(:bob)) |
6 |
+ end |
|
7 |
+ |
|
8 |
+ it "creates an agent" do |
|
6 | 9 |
visit "/" |
7 | 10 |
page.find("a", text: "Agents").trigger(:mouseover) |
8 | 11 |
click_on("New Agent") |
@@ -15,7 +18,6 @@ describe "Creating a new agent", js: true do |
||
15 | 18 |
end |
16 | 19 |
|
17 | 20 |
it "creates an alert if a new agent with invalid json is submitted" do |
18 |
- login_as(users(:bob)) |
|
19 | 21 |
visit "/" |
20 | 22 |
page.find("a", text: "Agents").trigger(:mouseover) |
21 | 23 |
click_on("New Agent") |
@@ -30,4 +32,28 @@ describe "Creating a new agent", js: true do |
||
30 | 32 |
}') |
31 | 33 |
expect(get_alert_text_from { click_on "Save" }).to have_text("Sorry, there appears to be an error in your JSON input. Please fix it before continuing.") |
32 | 34 |
end |
35 |
+ |
|
36 |
+ context "displaying the correct information" do |
|
37 |
+ before(:each) do |
|
38 |
+ visit new_agent_path |
|
39 |
+ end |
|
40 |
+ it "shows all options for agents that can be scheduled, create and receive events" do |
|
41 |
+ select2("Website Agent", from: "Type") |
|
42 |
+ expect(page).not_to have_content('This type of Agent cannot create events.') |
|
43 |
+ end |
|
44 |
+ |
|
45 |
+ it "does not show the target select2 field when the agent can not create events" do |
|
46 |
+ select2("Growl Agent", from: "Type") |
|
47 |
+ expect(page).to have_content('This type of Agent cannot create events.') |
|
48 |
+ end |
|
49 |
+ end |
|
50 |
+ |
|
51 |
+ it "allows to click on on the agent name in select2 tags" do |
|
52 |
+ agent = agents(:bob_weather_agent) |
|
53 |
+ visit new_agent_path |
|
54 |
+ select2("Website Agent", from: "Type") |
|
55 |
+ select2("SF Weather", from: 'Sources') |
|
56 |
+ click_on "SF Weather" |
|
57 |
+ expect(page).to have_content "Editing your WeatherAgent" |
|
58 |
+ end |
|
33 | 59 |
end |
@@ -51,6 +51,8 @@ describe DotHelper do |
||
51 | 51 |
agent.save! |
52 | 52 |
}, |
53 | 53 |
] |
54 |
+ @foo.reload |
|
55 |
+ @bar2.reload |
|
54 | 56 |
end |
55 | 57 |
|
56 | 58 |
it "generates a DOT script" do |
@@ -546,6 +546,17 @@ describe Agent do |
||
546 | 546 |
expect(agent).to have(0).errors_on(:sources) |
547 | 547 |
end |
548 | 548 |
|
549 |
+ it "should not allow target agents owned by other people" do |
|
550 |
+ agent = Agents::SomethingSource.new(:name => "something") |
|
551 |
+ agent.user = users(:bob) |
|
552 |
+ agent.receiver_ids = [agents(:bob_weather_agent).id] |
|
553 |
+ expect(agent).to have(0).errors_on(:receivers) |
|
554 |
+ agent.receiver_ids = [agents(:jane_weather_agent).id] |
|
555 |
+ expect(agent).to have(1).errors_on(:receivers) |
|
556 |
+ agent.user = users(:jane) |
|
557 |
+ expect(agent).to have(0).errors_on(:receivers) |
|
558 |
+ end |
|
559 |
+ |
|
549 | 560 |
it "should not allow controller agents owned by other people" do |
550 | 561 |
agent = Agents::SomethingSource.new(:name => "something") |
551 | 562 |
agent.user = users(:bob) |
@@ -961,6 +972,8 @@ describe AgentDrop do |
||
961 | 972 |
@efa.sources << @wsa1 << @wsa2 |
962 | 973 |
@efa.memory[:test] = 1 |
963 | 974 |
@efa.save! |
975 |
+ @wsa1.reload |
|
976 |
+ @wsa2.reload |
|
964 | 977 |
end |
965 | 978 |
|
966 | 979 |
it 'should be created via Agent#to_liquid' do |