basecamp-sdk 0.19.0 → 0.21.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/README.md +1 -0
- data/lib/basecamp/client.rb +5 -5
- data/lib/basecamp/generated/metadata.json +226 -4
- data/lib/basecamp/generated/services/card_steps_service.rb +3 -1
- data/lib/basecamp/generated/services/lineup_service.rb +8 -0
- data/lib/basecamp/generated/services/my_notifications_service.rb +2 -1
- data/lib/basecamp/generated/services/recordings_service.rb +2 -2
- data/lib/basecamp/generated/services/subtasks_service.rb +97 -0
- data/lib/basecamp/generated/services/templates_service.rb +68 -9
- data/lib/basecamp/generated/services/timesheets_service.rb +1 -1
- data/lib/basecamp/generated/types.rb +175 -81
- data/lib/basecamp/version.rb +2 -2
- data/scripts/generate-metadata.rb +95 -12
- data/scripts/generate-services.rb +128 -25
- data/scripts/generate-types.rb +73 -9
- metadata +3 -3
- data/lib/basecamp/generated/services/automation_service.rb +0 -19
|
@@ -17,7 +17,54 @@ require 'set'
|
|
|
17
17
|
|
|
18
18
|
# Service generator for Ruby SDK
|
|
19
19
|
class ServiceGenerator
|
|
20
|
-
|
|
20
|
+
# A Path Item Object is read by EXCLUSION. Its non-operation fields are a
|
|
21
|
+
# closed, spec-defined set and its extensions are `x-` prefixed, so every
|
|
22
|
+
# OTHER field is an operation. Enumerating the verbs instead is the defect
|
|
23
|
+
# this replaces (#925): Smithy's `@http` trait takes the method as a
|
|
24
|
+
# free-form string it "will use literally and will perform no validation
|
|
25
|
+
# on", so a model author writing `method: "HEAD"` produced a valid model, a
|
|
26
|
+
# valid openapi.json, and no method on any service — the verb was not in the
|
|
27
|
+
# list, so the operation was stepped over in silence.
|
|
28
|
+
NON_OPERATION_FIELDS = %w[summary description servers parameters].freeze
|
|
29
|
+
|
|
30
|
+
# Discovery above is total; emission is bounded by the ONE declaration every
|
|
31
|
+
# generator reads (spec/generated-verbs.json), whose header carries the
|
|
32
|
+
# reasoning. Each verb in it has an `Http` helper (`http_get`, `http_post`, …)
|
|
33
|
+
# the emitted method body calls. An operation on any other verb stops this
|
|
34
|
+
# generator by name instead of vanishing from the SDK — a loud refusal is the
|
|
35
|
+
# acceptable outcome, a silent drop never was.
|
|
36
|
+
#
|
|
37
|
+
# The declaration is ORDERED, and that order is emission order. Ordering is a
|
|
38
|
+
# different job from deciding membership and cannot reproduce #925: membership
|
|
39
|
+
# is decided by exclusion above, so a verb absent from the declaration is
|
|
40
|
+
# refused, never skipped.
|
|
41
|
+
# The self-test points this at a crafted declaration to prove the bound is
|
|
42
|
+
# sourced from the shared file rather than a private literal; production runs
|
|
43
|
+
# never set it.
|
|
44
|
+
GENERATED_VERBS_FILE = ENV.fetch(
|
|
45
|
+
'BASECAMP_GENERATED_VERBS', File.expand_path('../../spec/generated-verbs.json', __dir__)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
EMITTABLE_METHODS = begin
|
|
49
|
+
verbs = JSON.parse(File.read(GENERATED_VERBS_FILE, encoding: 'UTF-8'))['verbs']
|
|
50
|
+
# An HTTP method is a TOKEN, so the rule is a positive character class rather
|
|
51
|
+
# than a blankness predicate: `[a-z]+`, identical in all six loaders. Two
|
|
52
|
+
# review rounds chased "blank" across languages — Kotlin and Rust rejected a
|
|
53
|
+
# space while the others accepted it, then Ruby's ASCII `strip` accepted a
|
|
54
|
+
# non-breaking space the Unicode-aware ones rejected — and the next
|
|
55
|
+
# disagreement was guaranteed, because every language defines whitespace
|
|
56
|
+
# differently (Java's Character.isWhitespace excludes U+00A0; Rust's
|
|
57
|
+
# char::is_whitespace includes it; Ruby's String#strip is ASCII-only). A
|
|
58
|
+
# closed positive rule has no such seam: "", " ", "\u00a0", "GET" and 1 are
|
|
59
|
+
# all rejected the same way everywhere.
|
|
60
|
+
unless verbs.is_a?(Array) && !verbs.empty? && verbs.all? { |v| v.is_a?(String) && v.match?(/\A[a-z]+\z/) }
|
|
61
|
+
abort "Error: #{GENERATED_VERBS_FILE} must declare a non-empty `verbs` array of lowercase " \
|
|
62
|
+
'ASCII method names (/\A[a-z]+\z/).'
|
|
63
|
+
end
|
|
64
|
+
verbs.freeze
|
|
65
|
+
rescue Errno::ENOENT, JSON::ParserError => e
|
|
66
|
+
abort "Error: cannot read the generated-verb declaration #{GENERATED_VERBS_FILE}: #{e.message}"
|
|
67
|
+
end
|
|
21
68
|
|
|
22
69
|
# Schema reference cache for resolving $ref
|
|
23
70
|
attr_reader :schemas
|
|
@@ -33,9 +80,9 @@ class ServiceGenerator
|
|
|
33
80
|
'Schedule' => 'Schedules',
|
|
34
81
|
'People' => 'People',
|
|
35
82
|
'Projects' => 'Projects',
|
|
36
|
-
'Automation' => 'Automation',
|
|
37
83
|
'ClientFeatures' => 'ClientFeatures',
|
|
38
84
|
'Boosts' => 'Boosts',
|
|
85
|
+
'Subtasks' => 'Subtasks',
|
|
39
86
|
'Untagged' => 'Miscellaneous'
|
|
40
87
|
}.freeze
|
|
41
88
|
|
|
@@ -72,22 +119,11 @@ class ServiceGenerator
|
|
|
72
119
|
'CloudFiles' => %w[GetCloudFile CreateCloudFile UpdateCloudFile],
|
|
73
120
|
'GoogleDocuments' => %w[GetGoogleDocument CreateGoogleDocument UpdateGoogleDocument]
|
|
74
121
|
},
|
|
75
|
-
'
|
|
76
|
-
'Tools' => %w[GetTool UpdateTool DeleteTool CreateTool EnableTool DisableTool RepositionTool]
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
'Events' => %w[ListEvents]
|
|
80
|
-
'Lineup' => %w[CreateLineupMarker UpdateLineupMarker DeleteLineupMarker],
|
|
81
|
-
'Search' => %w[Search GetSearchMetadata],
|
|
82
|
-
'Templates' => %w[
|
|
83
|
-
ListTemplates CreateTemplate GetTemplate UpdateTemplate
|
|
84
|
-
DeleteTemplate CreateProjectFromTemplate GetProjectConstruction
|
|
85
|
-
GetTemplateLibrary CreateTemplateLibraryCopy GetTemplateLibraryCopy
|
|
86
|
-
],
|
|
87
|
-
'Checkins' => %w[
|
|
88
|
-
GetQuestionnaire ListQuestions CreateQuestion GetQuestion
|
|
89
|
-
UpdateQuestion ListAnswers CreateAnswer GetAnswer UpdateAnswer
|
|
90
|
-
]
|
|
122
|
+
'Dock' => {
|
|
123
|
+
'Tools' => %w[GetTool UpdateTool DeleteTool CreateTool EnableTool DisableTool RepositionTool]
|
|
124
|
+
},
|
|
125
|
+
'Recordings' => {
|
|
126
|
+
'Events' => %w[ListEvents]
|
|
91
127
|
},
|
|
92
128
|
'Messages' => {
|
|
93
129
|
'Messages' => %w[GetMessage UpdateMessage CreateMessage ListMessages PinMessage UnpinMessage],
|
|
@@ -179,7 +215,10 @@ class ServiceGenerator
|
|
|
179
215
|
'Search' => 'search',
|
|
180
216
|
'CreateProjectFromTemplate' => 'create_project',
|
|
181
217
|
'GetProjectConstruction' => 'get_construction',
|
|
182
|
-
'
|
|
218
|
+
'GetTemplateLibraryTodolists' => 'get_library_todolists',
|
|
219
|
+
'GetTemplateLibraryCardTables' => 'get_library_card_tables',
|
|
220
|
+
'CreateTemplateLibraryCardTable' => 'create_library_card_table',
|
|
221
|
+
'CreateTemplateLibraryTodolist' => 'create_library_todolist',
|
|
183
222
|
'CreateTemplateLibraryCopy' => 'create_library_copy',
|
|
184
223
|
'GetTemplateLibraryCopy' => 'get_library_copy',
|
|
185
224
|
'GetRecordingTimesheet' => 'for_recording',
|
|
@@ -323,7 +362,7 @@ class ServiceGenerator
|
|
|
323
362
|
lineupmarker clientapproval clientapprovals clientcorrespondence
|
|
324
363
|
clientcorrespondences clientreply clientreplies forwardreply
|
|
325
364
|
forwardreplies campfireline campfirelines todolistgroup todolistgroups
|
|
326
|
-
todolistorgroup uploadversions hillchart hillcharts
|
|
365
|
+
todolistorgroup uploadversions hillchart hillcharts subtask subtasks
|
|
327
366
|
wormhole wormholes
|
|
328
367
|
].freeze
|
|
329
368
|
|
|
@@ -366,14 +405,78 @@ class ServiceGenerator
|
|
|
366
405
|
|
|
367
406
|
private
|
|
368
407
|
|
|
408
|
+
# Yields [verb, operation] for every operation in a path item, identifying an
|
|
409
|
+
# operation by what it is NOT (see NON_OPERATION_FIELDS). Anything this
|
|
410
|
+
# generator cannot render aborts the run naming the operation.
|
|
411
|
+
def each_operation(path, path_item)
|
|
412
|
+
unless path_item.is_a?(Hash)
|
|
413
|
+
abort "Error: openapi.json path #{path} is a #{path_item.class}, not a path item object."
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# A `$ref` path item points at operations this generator cannot see without
|
|
417
|
+
# resolving the reference. Skipping it is the same silent under-count the
|
|
418
|
+
# exclusion walk exists to prevent, so refuse until someone teaches it to
|
|
419
|
+
# follow one.
|
|
420
|
+
if path_item.key?('$ref')
|
|
421
|
+
abort "Error: openapi.json path #{path} is a $ref to #{path_item['$ref'].inspect}. " \
|
|
422
|
+
'This generator cannot resolve a path-item reference, and skipping it would hide ' \
|
|
423
|
+
'every operation behind it from the SDK. Inline the path item, or teach this ' \
|
|
424
|
+
'generator to resolve local references.'
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# OpenAPI 3.2's `additionalOperations` is a MAP of method to Operation, not an
|
|
428
|
+
# operation. Read as one it carries no operationId, so it would drop every
|
|
429
|
+
# operation inside it without saying so. Refuse by name until the walk learns
|
|
430
|
+
# the map shape.
|
|
431
|
+
if path_item.key?('additionalOperations')
|
|
432
|
+
abort "Error: openapi.json path #{path} declares `additionalOperations`, which OpenAPI 3.2 " \
|
|
433
|
+
'defines as a map of method to Operation. This walk reads a path-item field as a ' \
|
|
434
|
+
'single operation, so it would drop every operation inside it. Teach the walk the map ' \
|
|
435
|
+
'shape, or take the field out of the spec.'
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
fields = path_item.keys.reject { |f| NON_OPERATION_FIELDS.include?(f) || f.start_with?('x-') }
|
|
439
|
+
fields.sort_by! { |f| [ EMITTABLE_METHODS.index(f) || EMITTABLE_METHODS.length, f ] }
|
|
440
|
+
|
|
441
|
+
fields.each do |field|
|
|
442
|
+
operation = path_item[field]
|
|
443
|
+
|
|
444
|
+
unless operation.is_a?(Hash)
|
|
445
|
+
abort "Error: openapi.json path #{path} field #{field.inspect} is a #{operation.class}, " \
|
|
446
|
+
'which is neither a known non-operation field nor an operation object. If a later ' \
|
|
447
|
+
'OpenAPI version added it, add it to NON_OPERATION_FIELDS with a reason.'
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
unless EMITTABLE_METHODS.include?(field)
|
|
451
|
+
op_id = operation['operationId'] || '(no operationId)'
|
|
452
|
+
abort "Error: openapi.json declares #{field.upcase} #{path} (#{op_id}), and this generator " \
|
|
453
|
+
"emits only #{EMITTABLE_METHODS.map(&:upcase).join('/')}. Generating the rest of the " \
|
|
454
|
+
'SDK without it would drop the operation from every Ruby client in silence, which is ' \
|
|
455
|
+
'the failure #925 closed. Give Basecamp::Http a ' \
|
|
456
|
+
"http_#{field} helper, add #{field.inspect} to spec/generated-verbs.json (read that " \
|
|
457
|
+
'file first — the other five SDKs need the same helper), or take the operation out ' \
|
|
458
|
+
'of the Smithy model.'
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# An operation has to be IDENTIFIABLE. OpenAPI lets operationId be omitted,
|
|
462
|
+
# and every walker here used to step over one that was — a silent drop of a
|
|
463
|
+
# real operation, which is #925 wearing a different field.
|
|
464
|
+
op_id = operation['operationId']
|
|
465
|
+
unless op_id.is_a?(String) && !op_id.empty?
|
|
466
|
+
abort "Error: openapi.json declares #{field.upcase} #{path} with no operationId. " \
|
|
467
|
+
'Everything downstream is keyed by it, and skipping the operation would drop it ' \
|
|
468
|
+
'from the SDK in silence.'
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
yield field, operation
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
369
475
|
def group_operations
|
|
370
476
|
services = {}
|
|
371
477
|
|
|
372
478
|
@openapi['paths'].each do |path, path_item|
|
|
373
|
-
|
|
374
|
-
operation = path_item[method]
|
|
375
|
-
next unless operation
|
|
376
|
-
|
|
479
|
+
each_operation(path, path_item) do |method, operation|
|
|
377
480
|
tag = operation['tags']&.first || 'Untagged'
|
|
378
481
|
parsed = parse_operation(path, method, operation)
|
|
379
482
|
|
|
@@ -692,7 +795,7 @@ class ServiceGenerator
|
|
|
692
795
|
op[:path_params].each do |p|
|
|
693
796
|
ruby_name = to_snake_case(p[:name])
|
|
694
797
|
type = p[:type] || 'Integer'
|
|
695
|
-
desc = p[:description] || "#{ruby_name.gsub('_', ' ')} ID"
|
|
798
|
+
desc = yard_param_description(p[:description] || "#{ruby_name.gsub('_', ' ')} ID")
|
|
696
799
|
lines << " # @param #{ruby_name} [#{type}] #{desc}"
|
|
697
800
|
end
|
|
698
801
|
|
data/scripts/generate-types.rb
CHANGED
|
@@ -132,6 +132,59 @@ def deprecation_doc_lines(reason, indent:, tag_prefix: '')
|
|
|
132
132
|
out
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
+
# Whether the main loop emits a class for this component. The alias pass checks
|
|
136
|
+
# its targets against exactly this predicate, so an alias can never name a
|
|
137
|
+
# class that was not emitted (a *RequestContent target used to produce
|
|
138
|
+
# `Alias = CreateCardStepRequestContent`, a NameError the moment types.rb loads).
|
|
139
|
+
def emits_class?(name, schema)
|
|
140
|
+
return false if SKIP_PATTERNS.any? { |p| name.match?(p) }
|
|
141
|
+
|
|
142
|
+
schema.is_a?(Hash) && schema['type'] == 'object' && !(schema['properties'] || {}).empty?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
DEPRECATED_ALIAS_KEYS = %w[$ref deprecated description x-deprecated-reason].freeze
|
|
146
|
+
|
|
147
|
+
# A component that is nothing but a deprecated $ref: a rename kept for
|
|
148
|
+
# compatibility, e.g. CardStep -> Subtask. `deprecated` must be the JSON boolean
|
|
149
|
+
# true, as in every other generator; 1 or "false" is not an alias.
|
|
150
|
+
def deprecated_alias?(schema)
|
|
151
|
+
schema.is_a?(Hash) && schema['deprecated'] == true && schema['$ref'].is_a?(String) &&
|
|
152
|
+
(schema.keys - DEPRECATED_ALIAS_KEYS).empty?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Constants Basecamp::Types already holds besides the generated classes.
|
|
156
|
+
RESERVED_TYPE_CONSTANTS = %w[TypeHelpers].freeze
|
|
157
|
+
|
|
158
|
+
# Every deprecated alias as [alias, target], sorted by alias. Aborts on one this
|
|
159
|
+
# generator cannot place, the same cases every SDK generator refuses: a missing
|
|
160
|
+
# target, an alias of an alias (rejected, not resolved), a target that is not an
|
|
161
|
+
# object model, a target this generator does not emit, and a name collision.
|
|
162
|
+
def deprecated_aliases(schemas)
|
|
163
|
+
schemas.keys.sort.filter_map do |name|
|
|
164
|
+
schema = schemas[name]
|
|
165
|
+
next unless deprecated_alias?(schema)
|
|
166
|
+
|
|
167
|
+
target = schema['$ref'].split('/').last
|
|
168
|
+
target_schema = schemas[target]
|
|
169
|
+
problem =
|
|
170
|
+
if !schemas.key?(target)
|
|
171
|
+
'target schema does not exist'
|
|
172
|
+
elsif deprecated_alias?(target_schema)
|
|
173
|
+
"target is itself a deprecated alias; point #{name} at the model directly"
|
|
174
|
+
elsif !(target_schema.is_a?(Hash) && target_schema['type'] == 'object' &&
|
|
175
|
+
!(target_schema['properties'] || {}).empty?)
|
|
176
|
+
'target is not an object model'
|
|
177
|
+
elsif !emits_class?(target, target_schema)
|
|
178
|
+
'target class was not emitted (SKIP_PATTERNS excludes it)'
|
|
179
|
+
elsif RESERVED_TYPE_CONSTANTS.include?(name)
|
|
180
|
+
"#{name} collides with an existing constant in Basecamp::Types"
|
|
181
|
+
end
|
|
182
|
+
abort "Error: deprecated alias #{name} -> #{target}: #{problem}" if problem
|
|
183
|
+
|
|
184
|
+
[ name, target ]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
135
188
|
# Main execution
|
|
136
189
|
if __FILE__ == $PROGRAM_NAME
|
|
137
190
|
openapi_path = ARGV[0] || File.expand_path('../../openapi.json', __dir__)
|
|
@@ -141,6 +194,13 @@ if __FILE__ == $PROGRAM_NAME
|
|
|
141
194
|
exit 1
|
|
142
195
|
end
|
|
143
196
|
|
|
197
|
+
# UTF-8 regardless of process locale — see generate-metadata.rb
|
|
198
|
+
schemas = JSON.parse(File.read(openapi_path, encoding: 'UTF-8'))['components']['schemas'] || {}
|
|
199
|
+
sorted = schemas.keys.sort
|
|
200
|
+
# Validated before the first line is printed, so a refusal leaves no partial
|
|
201
|
+
# types.rb on stdout.
|
|
202
|
+
aliases = deprecated_aliases(schemas)
|
|
203
|
+
|
|
144
204
|
puts header
|
|
145
205
|
puts generate_helpers
|
|
146
206
|
puts ''
|
|
@@ -148,18 +208,11 @@ if __FILE__ == $PROGRAM_NAME
|
|
|
148
208
|
puts ' module Types'
|
|
149
209
|
puts ' include TypeHelpers'
|
|
150
210
|
|
|
151
|
-
# UTF-8 regardless of process locale — see generate-metadata.rb
|
|
152
|
-
schemas = JSON.parse(File.read(openapi_path, encoding: 'UTF-8'))['components']['schemas'] || {}
|
|
153
|
-
sorted = schemas.keys.sort
|
|
154
|
-
|
|
155
211
|
sorted.each do |name|
|
|
156
|
-
next if SKIP_PATTERNS.any? { |p| name.match?(p) }
|
|
157
|
-
|
|
158
212
|
schema = schemas[name]
|
|
159
|
-
next unless schema
|
|
213
|
+
next unless emits_class?(name, schema)
|
|
160
214
|
|
|
161
|
-
properties = schema['properties']
|
|
162
|
-
next if properties.empty?
|
|
215
|
+
properties = schema['properties']
|
|
163
216
|
|
|
164
217
|
required_fields = schema['required'] || []
|
|
165
218
|
required_set = required_fields.to_set
|
|
@@ -273,6 +326,17 @@ if __FILE__ == $PROGRAM_NAME
|
|
|
273
326
|
puts ' end'
|
|
274
327
|
end
|
|
275
328
|
|
|
329
|
+
# Deprecated former names (see deprecated_aliases), emitted after every class
|
|
330
|
+
# because the alias must reference an already-defined constant. The alias
|
|
331
|
+
# also keeps Basecamp::Types.const_get("CardStep") resolving.
|
|
332
|
+
aliases.each do |name, target|
|
|
333
|
+
schema = schemas[name]
|
|
334
|
+
puts ''
|
|
335
|
+
puts " # #{name}"
|
|
336
|
+
puts deprecation_doc_lines(schema['x-deprecated-reason'] || 'deprecated', indent: ' ')
|
|
337
|
+
puts " #{name} = #{target}"
|
|
338
|
+
end
|
|
339
|
+
|
|
276
340
|
puts ' end'
|
|
277
341
|
puts 'end'
|
|
278
342
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: basecamp-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Basecamp
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -183,7 +183,6 @@ files:
|
|
|
183
183
|
- lib/basecamp/generated/metadata.json
|
|
184
184
|
- lib/basecamp/generated/services/account_service.rb
|
|
185
185
|
- lib/basecamp/generated/services/attachments_service.rb
|
|
186
|
-
- lib/basecamp/generated/services/automation_service.rb
|
|
187
186
|
- lib/basecamp/generated/services/base_service.rb
|
|
188
187
|
- lib/basecamp/generated/services/bookmarks_service.rb
|
|
189
188
|
- lib/basecamp/generated/services/boosts_service.rb
|
|
@@ -225,6 +224,7 @@ files:
|
|
|
225
224
|
- lib/basecamp/generated/services/schedules_service.rb
|
|
226
225
|
- lib/basecamp/generated/services/search_service.rb
|
|
227
226
|
- lib/basecamp/generated/services/subscriptions_service.rb
|
|
227
|
+
- lib/basecamp/generated/services/subtasks_service.rb
|
|
228
228
|
- lib/basecamp/generated/services/templates_service.rb
|
|
229
229
|
- lib/basecamp/generated/services/timeline_service.rb
|
|
230
230
|
- lib/basecamp/generated/services/timesheets_service.rb
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Basecamp
|
|
4
|
-
module Services
|
|
5
|
-
# Service for Automation operations
|
|
6
|
-
#
|
|
7
|
-
# @generated from OpenAPI spec
|
|
8
|
-
class AutomationService < BaseService
|
|
9
|
-
|
|
10
|
-
# List all lineup markers for the account
|
|
11
|
-
# @return [Array<Hash>] response data
|
|
12
|
-
def list_lineup_markers()
|
|
13
|
-
with_operation(service: "automation", operation: "list_lineup_markers", is_mutation: false) do
|
|
14
|
-
http_get("/lineup/markers.json", operation: "ListLineupMarkers").json(operation: "ListLineupMarkers")
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|