Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit 3ec5d22

Browse files
monamohebbireidmit
andcommitted
v3: Client can filter **audit events** by **updated_ats**
[finishes #173691526] Co-authored-by: Mona Mohebbi <mmohebbi@pivotal.io> Co-authored-by: Reid Mitchell <rmitchell@pivotal.io>
1 parent 3b6ccfc commit 3ec5d22

5 files changed

Lines changed: 281 additions & 258 deletions

File tree

app/fetchers/event_list_fetcher.rb

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,25 @@ def filter(message, dataset)
2828
dataset = dataset.where(organization_guid: message.organization_guids)
2929
end
3030

31-
if message.requested?(:created_ats)
32-
if message.created_ats.is_a?(Hash)
33-
message.created_ats.map do |operator, given_timestamp|
31+
advanced_filters = {}
32+
advanced_filters['created_at'] = message.created_ats if message.requested?(:created_ats)
33+
advanced_filters['updated_at'] = message.updated_ats if message.requested?(:updated_ats)
34+
35+
advanced_filters.each do |filter, values|
36+
if values.is_a?(Hash)
37+
values.map do |operator, given_timestamp|
3438
if operator == Event::LESS_THAN_COMPARATOR
3539
normalized_timestamp = Time.parse(given_timestamp).utc
36-
dataset = dataset.where(Sequel.lit('created_at < ?', normalized_timestamp))
40+
dataset = dataset.where(Sequel.lit("#{filter} < ?", normalized_timestamp))
3741
elsif operator == Event::LESS_THAN_OR_EQUAL_COMPARATOR
3842
normalized_timestamp = (Time.parse(given_timestamp).utc + 0.999999).utc
39-
dataset = dataset.where(Sequel.lit('created_at <= ?', normalized_timestamp))
43+
dataset = dataset.where(Sequel.lit("#{filter} <= ?", normalized_timestamp))
4044
elsif operator == Event::GREATER_THAN_COMPARATOR
4145
normalized_timestamp = (Time.parse(given_timestamp).utc + 0.999999).utc
42-
dataset = dataset.where(Sequel.lit('created_at > ?', normalized_timestamp))
46+
dataset = dataset.where(Sequel.lit("#{filter} > ?", normalized_timestamp))
4347
elsif operator == Event::GREATER_THAN_OR_EQUAL_COMPARATOR
4448
normalized_timestamp = Time.parse(given_timestamp).utc
45-
dataset = dataset.where(Sequel.lit('created_at >= ?', normalized_timestamp))
49+
dataset = dataset.where(Sequel.lit("#{filter} >= ?", normalized_timestamp))
4650
end
4751
end
4852
else
@@ -53,10 +57,10 @@ def filter(message, dataset)
5357
# the span of the second (e.g. "12:34:56.00-12:34:56.9999999"), for databases store
5458
# timestamps in sub-second accuracy (PostgreSQL stores in microseconds, for example)
5559
sequel_query =
56-
(['created_at BETWEEN ? AND ?'] * message.created_ats.size).join(' OR ')
60+
(["#{filter} BETWEEN ? AND ?"] * values.size).join(' OR ')
5761

58-
times = message.created_ats.map do |created_at|
59-
lower_bound = Time.parse(created_at).utc
62+
times = values.map do |timestamp|
63+
lower_bound = Time.parse(timestamp).utc
6064
upper_bound = Time.at(lower_bound + 0.999999).utc
6165
[lower_bound, upper_bound]
6266
end.flatten

app/messages/events_list_message.rb

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,51 @@ module VCAP::CloudController
44
class EventsListMessage < ListMessage
55
class CreatedAtValidator < ActiveModel::Validator
66
def validate(record)
7-
if record.requested?(:created_ats)
8-
if record.created_ats.is_a?(Array)
9-
record.created_ats.each do |timestamp|
10-
opinionated_iso_8601(timestamp, record)
11-
end
12-
else
13-
unless record.created_ats.is_a?(Hash)
14-
record.errors[:created_ats] << 'relational operator and timestamp must be specified'
15-
return
16-
end
7+
filters = {}
8+
filters[:created_ats] = record.created_ats if record.requested?(:created_ats)
9+
filters[:updated_ats] = record.updated_ats if record.requested?(:updated_ats)
1710

18-
valid_relational_operators = [
19-
Event::LESS_THAN_COMPARATOR,
20-
Event::GREATER_THAN_COMPARATOR,
21-
Event::LESS_THAN_OR_EQUAL_COMPARATOR,
22-
Event::GREATER_THAN_OR_EQUAL_COMPARATOR,
23-
]
24-
25-
record.created_ats.each do |relational_operator, timestamp|
26-
unless valid_relational_operators.include?(relational_operator)
27-
record.errors[:created_ats] << "Invalid relational operator: '#{relational_operator}'"
11+
filters.each do |filter, values|
12+
if record.requested?(filter)
13+
if values.is_a?(Array)
14+
values.each do |timestamp|
15+
opinionated_iso_8601(timestamp, record, filter)
2816
end
29-
30-
if timestamp.to_s.include?(',')
31-
record.errors[:created_ats] << 'only accepts one value when using a relational operator'
17+
else
18+
unless values.is_a?(Hash)
19+
record.errors[filter] << 'relational operator and timestamp must be specified'
3220
next
3321
end
3422

35-
opinionated_iso_8601(timestamp, record)
23+
valid_relational_operators = [
24+
Event::LESS_THAN_COMPARATOR,
25+
Event::GREATER_THAN_COMPARATOR,
26+
Event::LESS_THAN_OR_EQUAL_COMPARATOR,
27+
Event::GREATER_THAN_OR_EQUAL_COMPARATOR,
28+
]
29+
30+
values.each do |relational_operator, timestamp|
31+
unless valid_relational_operators.include?(relational_operator)
32+
record.errors[filter] << "Invalid relational operator: '#{relational_operator}'"
33+
end
34+
35+
if timestamp.to_s.include?(',')
36+
record.errors[filter] << 'only accepts one value when using a relational operator'
37+
next
38+
end
39+
40+
opinionated_iso_8601(timestamp, record, filter)
41+
end
3642
end
3743
end
3844
end
3945
end
4046

4147
private
4248

43-
def opinionated_iso_8601(timestamp, record)
49+
def opinionated_iso_8601(timestamp, record, filter)
4450
if timestamp !~ /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\Z/
45-
record.errors[:created_ats] << "has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'"
51+
record.errors[filter] << "has an invalid timestamp format. Timestamps should be formatted as 'YYYY-MM-DDThh:mm:ssZ'"
4652
end
4753
end
4854
end
@@ -52,7 +58,8 @@ def opinionated_iso_8601(timestamp, record)
5258
:target_guids,
5359
:space_guids,
5460
:organization_guids,
55-
:created_ats
61+
:created_ats,
62+
:updated_ats
5663
]
5764

5865
validates_with NoAdditionalParamsValidator
@@ -64,7 +71,7 @@ def opinionated_iso_8601(timestamp, record)
6471
validates :organization_guids, array: true, allow_nil: true
6572

6673
def self.from_params(params)
67-
super(params, %w(types target_guids space_guids organization_guids created_ats))
74+
super(params, %w(types target_guids space_guids organization_guids created_ats updated_ats))
6875
end
6976
end
7077
end

spec/request/events_spec.rb

Lines changed: 10 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,17 @@
161161
end
162162

163163
context 'filtering by timestamp' do
164+
before do
165+
VCAP::CloudController::Event.plugin :timestamps, update_on_create: false
166+
end
167+
164168
let(:timestamp) { (Time.now + 1).utc.iso8601 }
165169
let(:timestamp_half_second_later) { (Time.parse(timestamp) + 0.5).utc.iso8601 }
166170

171+
after do
172+
VCAP::CloudController::Event.plugin :timestamps, update_on_create: true
173+
end
174+
167175
context 'using less than' do
168176
let!(:extra_event) { VCAP::CloudController::Event.make(created_at: Time.now + 100, organization_guid: org.guid) }
169177
let!(:half_second_event) { VCAP::CloudController::Event.make(created_at: timestamp_half_second_later, organization_guid: org.guid, type: 'audit.organization.create') }
@@ -179,225 +187,8 @@
179187
end
180188
end
181189

182-
context 'using less than or equal to' do
183-
let!(:extra_event) { VCAP::CloudController::Event.make(created_at: timestamp, organization_guid: org.guid, type: 'audit.organization.create') }
184-
185-
let(:extra_event_json) do
186-
{
187-
guid: extra_event.guid,
188-
created_at: iso8601,
189-
updated_at: iso8601,
190-
type: 'audit.organization.create',
191-
actor: {
192-
guid: extra_event.actor,
193-
type: extra_event.actor_type,
194-
name: extra_event.actor_name
195-
},
196-
target: {
197-
guid: extra_event.actee,
198-
type: extra_event.actee_type,
199-
name: extra_event.actee_name
200-
},
201-
data: {},
202-
space: nil,
203-
organization: {
204-
guid: org.guid
205-
},
206-
links: {
207-
self: {
208-
href: "#{link_prefix}/v3/audit_events/#{extra_event.guid}"
209-
}
210-
}
211-
}
212-
end
213-
214-
let!(:half_second_event) { VCAP::CloudController::Event.make(created_at: timestamp_half_second_later, organization_guid: org.guid, type: 'audit.organization.create') }
215-
216-
let(:half_second_event_json) do
217-
{
218-
guid: half_second_event.guid,
219-
created_at: iso8601,
220-
updated_at: iso8601,
221-
type: 'audit.organization.create',
222-
actor: {
223-
guid: half_second_event.actor,
224-
type: half_second_event.actor_type,
225-
name: half_second_event.actor_name
226-
},
227-
target: {
228-
guid: half_second_event.actee,
229-
type: half_second_event.actee_type,
230-
name: half_second_event.actee_name
231-
},
232-
data: {},
233-
space: nil,
234-
organization: {
235-
guid: org.guid
236-
},
237-
links: {
238-
self: {
239-
href: "#{link_prefix}/v3/audit_events/#{half_second_event.guid}"
240-
}
241-
}
242-
}
243-
end
244-
245-
it 'returns events earlier than the given timestamp' do
246-
get "/v3/audit_events?created_ats[lte]=#{timestamp}", nil, admin_header
247-
248-
expect(
249-
resources: parsed_response['resources']
250-
).to match_json_response(
251-
resources: [unscoped_event_json, space_scoped_event_json, extra_event_json, half_second_event_json]
252-
)
253-
end
254-
end
255-
256-
context 'using greater than or equal to' do
257-
let!(:extra_event) { VCAP::CloudController::Event.make(created_at: timestamp, organization_guid: org.guid, type: 'audit.organization.create') }
258-
259-
let(:extra_event_json) do
260-
{
261-
guid: extra_event.guid,
262-
created_at: iso8601,
263-
updated_at: iso8601,
264-
type: 'audit.organization.create',
265-
actor: {
266-
guid: extra_event.actor,
267-
type: extra_event.actor_type,
268-
name: extra_event.actor_name
269-
},
270-
target: {
271-
guid: extra_event.actee,
272-
type: extra_event.actee_type,
273-
name: extra_event.actee_name
274-
},
275-
data: {},
276-
space: nil,
277-
organization: {
278-
guid: org.guid
279-
},
280-
links: {
281-
self: {
282-
href: "#{link_prefix}/v3/audit_events/#{extra_event.guid}"
283-
}
284-
}
285-
}
286-
end
287-
288-
it 'returns events at or after the given timestamp' do
289-
get "/v3/audit_events?created_ats[gte]=#{timestamp}", nil, admin_header
290-
291-
expect(
292-
resources: parsed_response['resources']
293-
).to match_json_response(
294-
resources: [org_scoped_event_json, extra_event_json]
295-
)
296-
end
297-
end
298-
299-
context 'using greater than' do
300-
it 'returns events after the given timestamp' do
301-
get "/v3/audit_events?created_ats[gt]=#{timestamp}", nil, admin_header
302-
303-
expect(
304-
resources: parsed_response['resources']
305-
).to match_json_response(
306-
resources: [org_scoped_event_json]
307-
)
308-
end
309-
end
310-
context 'using greater than or equal to' do
311-
let!(:extra_event) { VCAP::CloudController::Event.make(created_at: timestamp, organization_guid: org.guid, type: 'audit.organization.create') }
312-
313-
let(:extra_event_json) do
314-
{
315-
guid: extra_event.guid,
316-
created_at: iso8601,
317-
updated_at: iso8601,
318-
type: 'audit.organization.create',
319-
actor: {
320-
guid: extra_event.actor,
321-
type: extra_event.actor_type,
322-
name: extra_event.actor_name
323-
},
324-
target: {
325-
guid: extra_event.actee,
326-
type: extra_event.actee_type,
327-
name: extra_event.actee_name
328-
},
329-
data: {},
330-
space: nil,
331-
organization: {
332-
guid: org.guid
333-
},
334-
links: {
335-
self: {
336-
href: "#{link_prefix}/v3/audit_events/#{extra_event.guid}"
337-
}
338-
}
339-
}
340-
end
341-
342-
it 'returns events at or after the given timestamp' do
343-
get "/v3/audit_events?created_ats[gte]=#{timestamp}", nil, admin_header
344-
345-
expect(
346-
resources: parsed_response['resources']
347-
).to match_json_response(
348-
resources: [org_scoped_event_json, extra_event_json]
349-
)
350-
end
351-
end
352-
353-
context 'using greater than and less than, together' do
354-
let!(:event_1) { VCAP::CloudController::Event.make(guid: '1', created_at: '2020-05-26T18:47:01Z') }
355-
let!(:event_2) { VCAP::CloudController::Event.make(guid: '2', created_at: '2020-05-26T18:47:02Z') }
356-
let!(:event_3) { VCAP::CloudController::Event.make(guid: '3', created_at: '2020-05-26T18:47:03Z') }
357-
let!(:event_4) { VCAP::CloudController::Event.make(guid: '4', created_at: '2020-05-26T18:47:04Z') }
358-
359-
let(:event_3_json) do
360-
{
361-
guid: event_3.guid,
362-
created_at: iso8601,
363-
updated_at: iso8601,
364-
type: event_3.type,
365-
actor: {
366-
guid: event_3.actor,
367-
type: event_3.actor_type,
368-
name: event_3.actor_name
369-
},
370-
target: {
371-
guid: event_3.actee,
372-
type: event_3.actee_type,
373-
name: event_3.actee_name
374-
},
375-
data: {},
376-
space: nil,
377-
organization: {
378-
guid: event_3.organization_guid
379-
},
380-
links: {
381-
self: {
382-
href: "#{link_prefix}/v3/audit_events/#{event_3.guid}"
383-
}
384-
}
385-
}
386-
end
387-
388-
it 'returns events after the greater-than timestamp but before the less-than timestamp' do
389-
get "/v3/audit_events?created_ats[gt]=#{event_2.created_at.iso8601}&created_ats[lt]=#{event_4.created_at.iso8601}", nil, admin_header
390-
391-
expect(
392-
resources: parsed_response['resources']
393-
).to match_json_response(
394-
resources: [event_3_json]
395-
)
396-
end
397-
end
398-
399190
context 'using equal' do
400-
let!(:same_time_event) { VCAP::CloudController::Event.make(created_at: timestamp, organization_guid: org.guid, type: 'audit.organization.create') }
191+
let!(:same_time_event) { VCAP::CloudController::Event.make(updated_at: timestamp, organization_guid: org.guid, type: 'audit.organization.create') }
401192

402193
let(:same_time_event_json) do
403194
{
@@ -429,7 +220,7 @@
429220
end
430221

431222
it 'returns events at the given timestamp' do
432-
get "/v3/audit_events?created_ats=#{timestamp}", nil, admin_header
223+
get "/v3/audit_events?updated_ats=#{timestamp}", nil, admin_header
433224

434225
expect(last_response).to have_status_code(200)
435226
expect(

0 commit comments

Comments
 (0)