dregs 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -2
- data/README.md +119 -1
- data/lib/dregs/client.rb +111 -10
- data/lib/dregs/devices.rb +70 -0
- data/lib/dregs/errors.rb +43 -1
- data/lib/dregs/escalations.rb +107 -0
- data/lib/dregs/events.rb +73 -0
- data/lib/dregs/groups.rb +137 -0
- data/lib/dregs/identities.rb +135 -18
- data/lib/dregs/models.rb +670 -0
- data/lib/dregs/resource.rb +117 -0
- data/lib/dregs/version.rb +1 -1
- data/lib/dregs.rb +5 -0
- data/sig/dregs.rbs +418 -3
- metadata +7 -2
data/lib/dregs/events.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "models"
|
|
4
|
+
require_relative "resource"
|
|
5
|
+
|
|
6
|
+
module Dregs
|
|
7
|
+
# The +client.events+ namespace: reading the events Dregs has recorded. To record one, use
|
|
8
|
+
# {Client#track}.
|
|
9
|
+
class Events < Resource
|
|
10
|
+
# Returns one page of events, newest first. Use {#list_all} to walk every page.
|
|
11
|
+
#
|
|
12
|
+
# @param identity [String, nil] only events from this identity
|
|
13
|
+
# @param group [String, nil] only events sent with the group with this id
|
|
14
|
+
# @param group_type [String, nil] only events sent with a group of this type
|
|
15
|
+
# @param device [String, nil] only events from the device with this fingerprint
|
|
16
|
+
# @param session [String, nil] only events from this browser session
|
|
17
|
+
# @param term [String, nil] free text matched against event names and data
|
|
18
|
+
# @param page [Integer] the page to return, counting from 0
|
|
19
|
+
# @param page_size [Integer, nil] items per page, at most 100
|
|
20
|
+
# @param sort [String, nil] the field to sort on, such as +"timestamp"+ or +"eventName"+
|
|
21
|
+
# @param order [Symbol, String, nil] +:asc+ or +:desc+
|
|
22
|
+
# @return [Page<Event>]
|
|
23
|
+
# @raise [ArgumentError] the page is negative or the order is not one of the two
|
|
24
|
+
def list(identity: nil, group: nil, group_type: nil, device: nil, session: nil, term: nil, page: 0,
|
|
25
|
+
page_size: nil, sort: nil, order: nil)
|
|
26
|
+
filters = list_filters(
|
|
27
|
+
identity: identity, group: group, group_type: group_type, device: device, session: session, term: term
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
list_page("/events", Event, filters, page: page, page_size: page_size, sort: sort, order: order)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Walks every event that matches, fetching pages as it goes. Takes the filters {#list} does.
|
|
34
|
+
#
|
|
35
|
+
# client.events.list_all(identity: "user_12345").each do |event|
|
|
36
|
+
# puts "#{event.timestamp} #{event.name}"
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# @return [Enumerator<Event>] a lazy enumerator, which fetches a page only when the items before
|
|
40
|
+
# it have been used up; nil when a block is given, which is called with each event
|
|
41
|
+
# @raise [ArgumentError] the order is not one of the two
|
|
42
|
+
def list_all(identity: nil, group: nil, group_type: nil, device: nil, session: nil, term: nil,
|
|
43
|
+
page_size: nil, sort: nil, order: nil, &block)
|
|
44
|
+
filters = list_filters(
|
|
45
|
+
identity: identity, group: group, group_type: group_type, device: device, session: session, term: term
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
list_every("/events", Event, filters, page_size: page_size, sort: sort, order: order, &block)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the event with this id.
|
|
52
|
+
#
|
|
53
|
+
# @param event_id [String] the event's id, either the one you sent or one Dregs generated
|
|
54
|
+
# @return [Event]
|
|
55
|
+
# @raise [NotFoundError] no event has this id
|
|
56
|
+
def get(event_id)
|
|
57
|
+
Event.from_api(@client.request(:get, "/events/#{segment(event_id, "An event id")}"))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def list_filters(identity:, group:, group_type:, device:, session:, term:)
|
|
63
|
+
{
|
|
64
|
+
"identity" => identity,
|
|
65
|
+
"group" => group,
|
|
66
|
+
"groupType" => group_type,
|
|
67
|
+
"device" => device,
|
|
68
|
+
"session" => session,
|
|
69
|
+
"term" => term
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/dregs/groups.rb
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "models"
|
|
4
|
+
require_relative "resource"
|
|
5
|
+
|
|
6
|
+
module Dregs
|
|
7
|
+
# The +client.groups+ namespace: the organizations, teams, or workspaces your users belong to.
|
|
8
|
+
#
|
|
9
|
+
# {Client#track} already records groups and memberships as events arrive. These calls are for
|
|
10
|
+
# reading them back, and for keeping Dregs in step when something changes outside an event, such
|
|
11
|
+
# as a user leaving a team.
|
|
12
|
+
#
|
|
13
|
+
# A group is identified by its type and id together, so the calls that address one take both.
|
|
14
|
+
class Groups < Resource
|
|
15
|
+
# Returns one page of groups, most recently active first.
|
|
16
|
+
#
|
|
17
|
+
# The groups in a list leave out their attributes; {#get} returns them. Use {#list_all} to walk
|
|
18
|
+
# every page.
|
|
19
|
+
#
|
|
20
|
+
# @param group_type [String, nil] only groups of this type, such as +"team"+
|
|
21
|
+
# @param identity [String, nil] only groups this identity belongs to
|
|
22
|
+
# @param term [String, nil] free text matched against group ids and names
|
|
23
|
+
# @param page [Integer] the page to return, counting from 0
|
|
24
|
+
# @param page_size [Integer, nil] items per page, at most 100
|
|
25
|
+
# @param sort [String, nil] the field to sort on, such as +"displayName"+ or +"lastTrackedAt"+
|
|
26
|
+
# @param order [Symbol, String, nil] +:asc+ or +:desc+
|
|
27
|
+
# @return [Page<Group>]
|
|
28
|
+
# @raise [ArgumentError] the page is negative or the order is not one of the two
|
|
29
|
+
def list(group_type: nil, identity: nil, term: nil, page: 0, page_size: nil, sort: nil, order: nil)
|
|
30
|
+
filters = list_filters(group_type: group_type, identity: identity, term: term)
|
|
31
|
+
|
|
32
|
+
list_page("/groups", Group, filters, page: page, page_size: page_size, sort: sort, order: order)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Walks every group that matches, fetching pages as it goes. Takes the filters {#list} does.
|
|
36
|
+
#
|
|
37
|
+
# client.groups.list_all(group_type: "team", identity: "user_12345").each do |team|
|
|
38
|
+
# puts "#{team.id} #{team.display_name}"
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# @return [Enumerator<Group>] a lazy enumerator, which fetches a page only when the items before
|
|
42
|
+
# it have been used up; nil when a block is given, which is called with each group
|
|
43
|
+
# @raise [ArgumentError] the order is not one of the two
|
|
44
|
+
def list_all(group_type: nil, identity: nil, term: nil, page_size: nil, sort: nil, order: nil, &block)
|
|
45
|
+
filters = list_filters(group_type: group_type, identity: identity, term: term)
|
|
46
|
+
|
|
47
|
+
list_every("/groups", Group, filters, page_size: page_size, sort: sort, order: order, &block)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns the group, with its attributes.
|
|
51
|
+
#
|
|
52
|
+
# group = client.groups.get("organization", "org_678")
|
|
53
|
+
#
|
|
54
|
+
# group.identity_count #=> 12
|
|
55
|
+
#
|
|
56
|
+
# @param group_type [String] the kind of group, such as +"organization"+
|
|
57
|
+
# @param group_id [String] your own id for the group
|
|
58
|
+
# @return [Group]
|
|
59
|
+
# @raise [NotFoundError] no group of this type has this id
|
|
60
|
+
def get(group_type, group_id)
|
|
61
|
+
Group.from_api(@client.request(:get, path_for(group_type, group_id)))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Creates a group, or merges +attributes:+ into the group that already has this type and id,
|
|
65
|
+
# so it is safe to call on every sync.
|
|
66
|
+
#
|
|
67
|
+
# client.groups.create("org_678", attributes: { name: "Acme Inc", plan: "enterprise" })
|
|
68
|
+
#
|
|
69
|
+
# @param group_id [String] your own id for the group
|
|
70
|
+
# @param group_type [String, nil] the kind of group; +"organization"+ when omitted
|
|
71
|
+
# @param attributes [Hash, nil] attributes such as a name or plan
|
|
72
|
+
# @return [Group]
|
|
73
|
+
def create(group_id, group_type: nil, attributes: nil)
|
|
74
|
+
segment(group_id, "A group id")
|
|
75
|
+
|
|
76
|
+
group_type = Client::DEFAULT_GROUP_TYPE if group_type.nil? || group_type.to_s.empty?
|
|
77
|
+
body = { "type" => group_type.to_s, "id" => group_id.to_s, "attributes" => attributes_hash(attributes) }
|
|
78
|
+
|
|
79
|
+
Group.from_api(@client.request(:post, "/groups", body: body))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Merges +attributes+ into the group and returns it.
|
|
83
|
+
#
|
|
84
|
+
# client.groups.update("organization", "org_678", { plan: "business" })
|
|
85
|
+
#
|
|
86
|
+
# @param group_type [String] the kind of group
|
|
87
|
+
# @param group_id [String] your own id for the group
|
|
88
|
+
# @param attributes [Hash] the attributes to merge
|
|
89
|
+
# @return [Group]
|
|
90
|
+
# @raise [NotFoundError] no group of this type has this id
|
|
91
|
+
def update(group_type, group_id, attributes)
|
|
92
|
+
body = { "attributes" => attributes_hash(attributes) }
|
|
93
|
+
|
|
94
|
+
Group.from_api(@client.request(:patch, path_for(group_type, group_id), body: body))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Makes the identity a member of the group. Adding an existing member does nothing.
|
|
98
|
+
#
|
|
99
|
+
# @param group_type [String] the kind of group
|
|
100
|
+
# @param group_id [String] your own id for the group
|
|
101
|
+
# @param identity_id [String] your own id for the user
|
|
102
|
+
# @return [nil]
|
|
103
|
+
# @raise [NotFoundError] the group or the identity is unknown
|
|
104
|
+
def add_member(group_type, group_id, identity_id)
|
|
105
|
+
@client.request(:put, member_path(group_type, group_id, identity_id))
|
|
106
|
+
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Removes the identity from the group, such as when a user leaves a team.
|
|
111
|
+
#
|
|
112
|
+
# @param group_type [String] the kind of group
|
|
113
|
+
# @param group_id [String] your own id for the group
|
|
114
|
+
# @param identity_id [String] your own id for the user
|
|
115
|
+
# @return [nil]
|
|
116
|
+
# @raise [NotFoundError] the group or the identity is unknown
|
|
117
|
+
def remove_member(group_type, group_id, identity_id)
|
|
118
|
+
@client.request(:delete, member_path(group_type, group_id, identity_id))
|
|
119
|
+
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def path_for(group_type, group_id)
|
|
126
|
+
"/groups/#{segment(group_type, "A group type")}/#{segment(group_id, "A group id")}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def member_path(group_type, group_id, identity_id)
|
|
130
|
+
"#{path_for(group_type, group_id)}/identities/#{identity_segment(identity_id)}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def list_filters(group_type:, identity:, term:)
|
|
134
|
+
{ "type" => group_type, "identity" => identity, "term" => term }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
data/lib/dregs/identities.rb
CHANGED
|
@@ -1,20 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "uri"
|
|
4
|
-
|
|
5
3
|
require_relative "models"
|
|
4
|
+
require_relative "resource"
|
|
6
5
|
|
|
7
6
|
module Dregs
|
|
8
7
|
# The +client.identities+ namespace: reading identities, their scores, and the reasoning behind
|
|
9
|
-
# them.
|
|
8
|
+
# them, and the few writes that go with them.
|
|
10
9
|
#
|
|
11
10
|
# These methods are thin. They name the endpoint and hand the response to a model; the
|
|
12
11
|
# transport, the retries, and the error mapping all live on {Client}.
|
|
13
|
-
class Identities
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
class Identities < Resource
|
|
13
|
+
# Returns one page of identities, most recently updated first.
|
|
14
|
+
#
|
|
15
|
+
# page = client.identities.list(term: "example.com", page_size: 50)
|
|
16
|
+
#
|
|
17
|
+
# page.total_elements #=> how many match in all
|
|
18
|
+
#
|
|
19
|
+
# Use {#list_all} to walk every page.
|
|
20
|
+
#
|
|
21
|
+
# @param term [String, nil] free text matched against ids, names, emails, and usernames
|
|
22
|
+
# @param group [String, nil] only members of the group with this id. Pair it with
|
|
23
|
+
# +group_type:+ when your group ids are not unique across types.
|
|
24
|
+
# @param group_type [String, nil] only members of groups of this type
|
|
25
|
+
# @param device [String, nil] only identities seen on the device with this fingerprint
|
|
26
|
+
# @param badge [String, nil] only identities carrying the badge with this slug
|
|
27
|
+
# @param page [Integer] the page to return, counting from 0
|
|
28
|
+
# @param page_size [Integer, nil] items per page, at most 100
|
|
29
|
+
# @param sort [String, nil] the field to sort on, such as +"lastTrackedAt"+ or +"humanityScore"+
|
|
30
|
+
# @param order [Symbol, String, nil] +:asc+ or +:desc+
|
|
31
|
+
# @return [Page<Identity>]
|
|
32
|
+
# @raise [ArgumentError] the page is negative or the order is not one of the two
|
|
33
|
+
def list(term: nil, group: nil, group_type: nil, device: nil, badge: nil, page: 0, page_size: nil,
|
|
34
|
+
sort: nil, order: nil)
|
|
35
|
+
filters = list_filters(term: term, group: group, group_type: group_type, device: device, badge: badge)
|
|
36
|
+
|
|
37
|
+
list_page("/identities", Identity, filters, page: page, page_size: page_size, sort: sort, order: order)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Walks every identity that matches, fetching pages as it goes. Takes the filters {#list} does.
|
|
41
|
+
#
|
|
42
|
+
# client.identities.list_all(group: "org_678").each do |identity|
|
|
43
|
+
# puts identity.display_email
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# @return [Enumerator<Identity>] a lazy enumerator, which fetches a page only when the items
|
|
47
|
+
# before it have been used up; nil when a block is given, which is called with each identity
|
|
48
|
+
# @raise [ArgumentError] the order is not one of the two
|
|
49
|
+
def list_all(term: nil, group: nil, group_type: nil, device: nil, badge: nil, page_size: nil, sort: nil,
|
|
50
|
+
order: nil, &block)
|
|
51
|
+
filters = list_filters(term: term, group: group, group_type: group_type, device: device, badge: badge)
|
|
52
|
+
|
|
53
|
+
list_every("/identities", Identity, filters, page_size: page_size, sort: sort, order: order, &block)
|
|
18
54
|
end
|
|
19
55
|
|
|
20
56
|
# Returns the identity, with its current scores, badges, and attributes.
|
|
@@ -47,15 +83,48 @@ module Dregs
|
|
|
47
83
|
Scores.from_api(@client.request(:get, path_for(identity_id, "/scores")))
|
|
48
84
|
end
|
|
49
85
|
|
|
50
|
-
# Returns
|
|
86
|
+
# Returns an analysis cycle, with the observations behind each score.
|
|
51
87
|
#
|
|
52
88
|
# Use this when you need to show or log *why* an identity scored the way it did.
|
|
53
89
|
#
|
|
90
|
+
# analysis = client.identities.analysis("user_12345")
|
|
91
|
+
# older = client.identities.analysis("user_12345", cycle_id: entry.cycle_id)
|
|
92
|
+
#
|
|
54
93
|
# @param identity_id [String] your own id for the user
|
|
94
|
+
# @param cycle_id [Integer, nil] a past cycle to return, from {#history}; the most recent cycle
|
|
95
|
+
# when omitted
|
|
55
96
|
# @return [Analysis]
|
|
56
97
|
# @raise [NotFoundError] the identity is unknown, or it has not been analyzed yet
|
|
57
|
-
def analysis(identity_id)
|
|
58
|
-
|
|
98
|
+
def analysis(identity_id, cycle_id: nil)
|
|
99
|
+
suffix = cycle_id.nil? ? "/analysis" : "/analysis/#{segment(cycle_id, "A cycle id")}"
|
|
100
|
+
|
|
101
|
+
Analysis.from_api(@client.request(:get, path_for(identity_id, suffix)))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns the scores of past analysis cycles, newest first.
|
|
105
|
+
#
|
|
106
|
+
# client.identities.history("user_12345", limit: 10).each do |entry|
|
|
107
|
+
# puts "#{entry.finished_at} #{entry.humanity}"
|
|
108
|
+
# end
|
|
109
|
+
#
|
|
110
|
+
# @param identity_id [String] your own id for the user
|
|
111
|
+
# @param limit [Integer, nil] the most cycles to return; the API's default is 100
|
|
112
|
+
# @return [Array<ScoreHistoryEntry>]
|
|
113
|
+
# @raise [NotFoundError] Dregs has never seen this identity
|
|
114
|
+
def history(identity_id, limit: nil)
|
|
115
|
+
payload = @client.request(:get, path_for(identity_id, "/history"), params: { "limit" => limit })
|
|
116
|
+
|
|
117
|
+
parse_each(ScoreHistoryEntry, payload)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns the other identities Dregs believes are connected to this one, such as by a shared
|
|
121
|
+
# device.
|
|
122
|
+
#
|
|
123
|
+
# @param identity_id [String] your own id for the user
|
|
124
|
+
# @return [Array<LinkedIdentity>]
|
|
125
|
+
# @raise [NotFoundError] Dregs has never seen this identity
|
|
126
|
+
def links(identity_id)
|
|
127
|
+
parse_each(LinkedIdentity, @client.request(:get, path_for(identity_id, "/links")))
|
|
59
128
|
end
|
|
60
129
|
|
|
61
130
|
# Queues a re-analysis of the identity.
|
|
@@ -74,18 +143,66 @@ module Dregs
|
|
|
74
143
|
nil
|
|
75
144
|
end
|
|
76
145
|
|
|
146
|
+
# Updates an identity and returns it. Only what you pass is sent.
|
|
147
|
+
#
|
|
148
|
+
# client.identities.update("qa_bot_7", disregarded: true)
|
|
149
|
+
#
|
|
150
|
+
# @param identity_id [String] your own id for the user
|
|
151
|
+
# @param disregarded [Boolean, nil] true to leave the identity out of scoring and escalations,
|
|
152
|
+
# such as for your own staff or test accounts. Linked identities are re-scored.
|
|
153
|
+
# @param attributes [Hash, nil] attributes to merge into the identity, as +identity_data:+ on
|
|
154
|
+
# {Client#track} would
|
|
155
|
+
# @return [Identity]
|
|
156
|
+
# @raise [NotFoundError] Dregs has never seen this identity
|
|
157
|
+
# @raise [ArgumentError] neither +disregarded:+ nor +attributes:+ was given
|
|
158
|
+
def update(identity_id, disregarded: nil, attributes: nil)
|
|
159
|
+
body = {}
|
|
160
|
+
|
|
161
|
+
body["disregarded"] = disregarded ? true : false unless disregarded.nil?
|
|
162
|
+
body["attributes"] = attributes_hash(attributes) unless attributes.nil?
|
|
163
|
+
|
|
164
|
+
raise ArgumentError, "Pass disregarded:, attributes:, or both." if body.empty?
|
|
165
|
+
|
|
166
|
+
Identity.from_api(@client.request(:patch, path_for(identity_id), body: body))
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Returns the AI reviews requested for this identity, newest first.
|
|
170
|
+
#
|
|
171
|
+
# @param identity_id [String] your own id for the user
|
|
172
|
+
# @return [Array<IdentityReview>]
|
|
173
|
+
# @raise [NotFoundError] Dregs has never seen this identity
|
|
174
|
+
def reviews(identity_id)
|
|
175
|
+
parse_each(IdentityReview, @client.request(:get, path_for(identity_id, "/reviews")))
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Asks Dregs's AI reviewer to look over the identity's analysis.
|
|
179
|
+
#
|
|
180
|
+
# The review runs in the background, so the result comes back +"PENDING"+. Read it again with
|
|
181
|
+
# {#reviews} once it has finished.
|
|
182
|
+
#
|
|
183
|
+
# review = client.identities.review("user_12345", feedback: "This is a long-standing customer.")
|
|
184
|
+
#
|
|
185
|
+
# review.status #=> "PENDING"
|
|
186
|
+
#
|
|
187
|
+
# @param identity_id [String] your own id for the user
|
|
188
|
+
# @param feedback [String, nil] what you think the analysis got wrong, such as "this is a real
|
|
189
|
+
# customer"
|
|
190
|
+
# @return [IdentityReview]
|
|
191
|
+
# @raise [NotFoundError] Dregs has never seen this identity
|
|
192
|
+
def review(identity_id, feedback: nil)
|
|
193
|
+
body = feedback.nil? || feedback.to_s.empty? ? {} : { "feedback" => feedback.to_s }
|
|
194
|
+
|
|
195
|
+
IdentityReview.from_api(@client.request(:post, path_for(identity_id, "/actions/review"), body: body))
|
|
196
|
+
end
|
|
197
|
+
|
|
77
198
|
private
|
|
78
199
|
|
|
79
200
|
def path_for(identity_id, suffix = "")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Identity ids are the caller's own user ids and routinely contain characters that need
|
|
83
|
-
# escaping, an email address being the common one.
|
|
84
|
-
"/identities/#{escape(identity_id.to_s)}#{suffix}"
|
|
201
|
+
"/identities/#{identity_segment(identity_id)}#{suffix}"
|
|
85
202
|
end
|
|
86
203
|
|
|
87
|
-
def
|
|
88
|
-
|
|
204
|
+
def list_filters(term:, group:, group_type:, device:, badge:)
|
|
205
|
+
{ "term" => term, "group" => group, "groupType" => group_type, "device" => device, "badge" => badge }
|
|
89
206
|
end
|
|
90
207
|
end
|
|
91
208
|
end
|