carddb 0.3.0 → 0.3.5
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/.rspec_status +171 -151
- data/CHANGELOG.md +7 -0
- data/README.md +189 -1
- data/examples/publisher_content_pipeline.rb +80 -0
- data/lib/carddb/client.rb +28 -0
- data/lib/carddb/collection.rb +517 -0
- data/lib/carddb/query_builder.rb +1030 -44
- data/lib/carddb/resources/datasets.rb +85 -0
- data/lib/carddb/resources/exports.rb +96 -0
- data/lib/carddb/resources/files.rb +43 -0
- data/lib/carddb/resources/games.rb +70 -0
- data/lib/carddb/resources/import_formats.rb +103 -0
- data/lib/carddb/resources/imports.rb +225 -0
- data/lib/carddb/resources/records.rb +157 -0
- data/lib/carddb/version.rb +1 -1
- data/lib/carddb.rb +32 -0
- metadata +6 -1
|
@@ -4,6 +4,79 @@ module CardDB
|
|
|
4
4
|
module Resources
|
|
5
5
|
# Datasets resource for searching, fetching, and getting schema
|
|
6
6
|
class Datasets < Base
|
|
7
|
+
# List datasets for a publisher-management context.
|
|
8
|
+
def list(publisher_id:, game_id: nil, purpose: nil, first: nil, after: nil, include_archived: nil, cache: nil)
|
|
9
|
+
query = QueryBuilder.list_datasets(
|
|
10
|
+
publisher_id: publisher_id,
|
|
11
|
+
game_id: game_id,
|
|
12
|
+
purpose: purpose,
|
|
13
|
+
first: first,
|
|
14
|
+
after: after,
|
|
15
|
+
include_archived: include_archived
|
|
16
|
+
)
|
|
17
|
+
variables = build_variables(
|
|
18
|
+
publisherId: publisher_id,
|
|
19
|
+
gameId: game_id,
|
|
20
|
+
purpose: purpose,
|
|
21
|
+
first: first,
|
|
22
|
+
after: after,
|
|
23
|
+
includeArchived: include_archived
|
|
24
|
+
)
|
|
25
|
+
key = cache_key('datasets', 'list', **variables)
|
|
26
|
+
|
|
27
|
+
data = with_cache(key, resource: :datasets, cache: cache) { connection.execute(query, variables) }
|
|
28
|
+
|
|
29
|
+
Collection.new(
|
|
30
|
+
data['datasets'],
|
|
31
|
+
item_class: Dataset,
|
|
32
|
+
next_page_loader: lambda { |cursor|
|
|
33
|
+
list(
|
|
34
|
+
publisher_id: publisher_id,
|
|
35
|
+
game_id: game_id,
|
|
36
|
+
purpose: purpose,
|
|
37
|
+
first: first,
|
|
38
|
+
after: cursor,
|
|
39
|
+
include_archived: include_archived,
|
|
40
|
+
cache: cache
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
client: client
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Get a dataset by game ID and stable dataset key.
|
|
48
|
+
def get_by_key(game_id:, dataset_key:, publisher_id: nil, cache: nil)
|
|
49
|
+
key = cache_key(
|
|
50
|
+
'datasets',
|
|
51
|
+
'get_by_key',
|
|
52
|
+
publisher_id: publisher_id,
|
|
53
|
+
game_id: game_id,
|
|
54
|
+
dataset_key: dataset_key
|
|
55
|
+
)
|
|
56
|
+
with_cache(key, resource: :datasets, cache: cache) do
|
|
57
|
+
query = QueryBuilder.dataset(publisher_id: publisher_id, game_id: game_id, dataset_key: dataset_key)
|
|
58
|
+
variables = build_variables(publisherId: publisher_id, gameId: game_id, datasetKey: dataset_key)
|
|
59
|
+
data = connection.execute(query, variables)
|
|
60
|
+
|
|
61
|
+
data['dataset'] ? Dataset.new(data['dataset'], client: client) : nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Fetch only the schema portion for a dataset.
|
|
66
|
+
def get_schema(id: nil, publisher_id: nil, game_id: nil, dataset_key: nil, cache: nil)
|
|
67
|
+
dataset = if id
|
|
68
|
+
get_by_id_for_management(id, cache: cache)
|
|
69
|
+
else
|
|
70
|
+
get_by_key(
|
|
71
|
+
publisher_id: publisher_id,
|
|
72
|
+
game_id: game_id,
|
|
73
|
+
dataset_key: dataset_key,
|
|
74
|
+
cache: cache
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
dataset&.schema
|
|
78
|
+
end
|
|
79
|
+
|
|
7
80
|
# Search for datasets
|
|
8
81
|
#
|
|
9
82
|
# @param publisher_slug [String, nil] Filter by publisher slug
|
|
@@ -127,6 +200,18 @@ module CardDB
|
|
|
127
200
|
)
|
|
128
201
|
dataset&.schema
|
|
129
202
|
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
def get_by_id_for_management(id, cache: nil)
|
|
207
|
+
key = cache_key('datasets', 'get_schema', id: id)
|
|
208
|
+
with_cache(key, resource: :datasets, cache: cache) do
|
|
209
|
+
query = QueryBuilder.dataset(id: id)
|
|
210
|
+
data = connection.execute(query, { id: id })
|
|
211
|
+
|
|
212
|
+
data['dataset'] ? Dataset.new(data['dataset'], client: client) : nil
|
|
213
|
+
end
|
|
214
|
+
end
|
|
130
215
|
end
|
|
131
216
|
end
|
|
132
217
|
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Publisher dataset export jobs resource.
|
|
6
|
+
class Exports < Base
|
|
7
|
+
# Start an async dataset export. Requires a server-side secret credential.
|
|
8
|
+
def run(input:)
|
|
9
|
+
config.require_secret_credential!('exports.run')
|
|
10
|
+
|
|
11
|
+
data = connection.execute(QueryBuilder.dataset_export, { input: input })
|
|
12
|
+
ExportJob.new(data['datasetExport'], client: client)
|
|
13
|
+
end
|
|
14
|
+
alias create run
|
|
15
|
+
|
|
16
|
+
# Fetch one export job.
|
|
17
|
+
def get_job(id, cache: nil)
|
|
18
|
+
key = cache_key('exports', 'get_job', id: id)
|
|
19
|
+
with_cache(key, resource: :exports, cache: cache) do
|
|
20
|
+
data = connection.execute(QueryBuilder.export_job, { id: id })
|
|
21
|
+
data['exportJob'] ? ExportJob.new(data['exportJob'], client: client) : nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# List export jobs for a publisher, optionally filtered by game, dataset, or status.
|
|
26
|
+
def list_jobs(publisher_id:, game_id: nil, dataset_id: nil, status: nil, first: nil, after: nil, cache: nil)
|
|
27
|
+
query = QueryBuilder.export_jobs(
|
|
28
|
+
publisher_id: publisher_id,
|
|
29
|
+
game_id: game_id,
|
|
30
|
+
dataset_id: dataset_id,
|
|
31
|
+
status: status,
|
|
32
|
+
first: first,
|
|
33
|
+
after: after
|
|
34
|
+
)
|
|
35
|
+
variables = build_variables(
|
|
36
|
+
publisherId: publisher_id,
|
|
37
|
+
gameId: game_id,
|
|
38
|
+
datasetId: dataset_id,
|
|
39
|
+
status: status,
|
|
40
|
+
first: first,
|
|
41
|
+
after: after
|
|
42
|
+
)
|
|
43
|
+
key = cache_key('exports', 'list_jobs', **variables)
|
|
44
|
+
|
|
45
|
+
data = with_cache(key, resource: :exports, cache: cache) { connection.execute(query, variables) }
|
|
46
|
+
|
|
47
|
+
Collection.new(
|
|
48
|
+
data['exportJobs'],
|
|
49
|
+
item_class: ExportJob,
|
|
50
|
+
next_page_loader: lambda { |cursor|
|
|
51
|
+
list_jobs(
|
|
52
|
+
publisher_id: publisher_id,
|
|
53
|
+
game_id: game_id,
|
|
54
|
+
dataset_id: dataset_id,
|
|
55
|
+
status: status,
|
|
56
|
+
first: first,
|
|
57
|
+
after: cursor,
|
|
58
|
+
cache: cache
|
|
59
|
+
)
|
|
60
|
+
},
|
|
61
|
+
client: client
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Refresh the signed download URL for a completed export job.
|
|
66
|
+
def refresh_url(id:)
|
|
67
|
+
data = connection.execute(QueryBuilder.export_job_refresh_url, { id: id })
|
|
68
|
+
ExportJob.new(data['exportJobRefreshUrl'], client: client)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Cancel a pending or processing export job. Requires a server-side secret credential.
|
|
72
|
+
def cancel(id:)
|
|
73
|
+
config.require_secret_credential!('exports.cancel')
|
|
74
|
+
|
|
75
|
+
data = connection.execute(QueryBuilder.export_job_cancel, { id: id })
|
|
76
|
+
ExportJob.new(data['exportJobCancel'], client: client)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Poll an export job until completion or failure.
|
|
80
|
+
def wait_for_job(id, interval: 1, timeout: 300)
|
|
81
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
82
|
+
|
|
83
|
+
loop do
|
|
84
|
+
job = get_job(id, cache: false)
|
|
85
|
+
raise Error, "Export job '#{id}' was not found" unless job
|
|
86
|
+
return job if job.completed? || job.failed?
|
|
87
|
+
|
|
88
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
89
|
+
raise Error, "Timed out waiting for export job '#{id}'" if elapsed >= timeout
|
|
90
|
+
|
|
91
|
+
sleep interval
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# File upload helper resource.
|
|
6
|
+
class Files < Base
|
|
7
|
+
# Get one file by ID.
|
|
8
|
+
def get(id, cache: nil)
|
|
9
|
+
key = cache_key('files', 'get', id: id)
|
|
10
|
+
with_cache(key, resource: :files, cache: cache) do
|
|
11
|
+
data = connection.execute(QueryBuilder.file, { id: id })
|
|
12
|
+
data['file'] ? File.new(data['file'], client: client) : nil
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Request a presigned URL for direct upload. Requires a server-side secret credential.
|
|
17
|
+
def request_upload(input:)
|
|
18
|
+
config.require_secret_credential!('files.request_upload')
|
|
19
|
+
|
|
20
|
+
data = connection.execute(QueryBuilder.file_upload_request, { input: input })
|
|
21
|
+
PresignedUpload.new(data['fileUploadRequest'], client: client)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Confirm that a presigned upload completed. Requires a server-side secret credential.
|
|
25
|
+
def confirm_upload(id:)
|
|
26
|
+
config.require_secret_credential!('files.confirm_upload')
|
|
27
|
+
|
|
28
|
+
data = connection.execute(QueryBuilder.file_upload_confirm, { id: id })
|
|
29
|
+
File.new(data['fileUploadConfirm'], client: client)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Delete a file. Requires a server-side secret credential.
|
|
33
|
+
# rubocop:disable Naming/PredicateMethod
|
|
34
|
+
def delete(id:)
|
|
35
|
+
config.require_secret_credential!('files.delete')
|
|
36
|
+
|
|
37
|
+
data = connection.execute(QueryBuilder.file_delete, { id: id })
|
|
38
|
+
!!data['fileDelete']
|
|
39
|
+
end
|
|
40
|
+
# rubocop:enable Naming/PredicateMethod
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -4,6 +4,76 @@ module CardDB
|
|
|
4
4
|
module Resources
|
|
5
5
|
# Games resource for searching and fetching games
|
|
6
6
|
class Games < Base
|
|
7
|
+
# List games for a publisher-management context.
|
|
8
|
+
def list(publisher_id:, first: nil, after: nil, include_archived: nil, cache: nil)
|
|
9
|
+
query = QueryBuilder.list_games(
|
|
10
|
+
publisher_id: publisher_id,
|
|
11
|
+
first: first,
|
|
12
|
+
after: after,
|
|
13
|
+
include_archived: include_archived
|
|
14
|
+
)
|
|
15
|
+
variables = build_variables(
|
|
16
|
+
publisherId: publisher_id,
|
|
17
|
+
first: first,
|
|
18
|
+
after: after,
|
|
19
|
+
includeArchived: include_archived
|
|
20
|
+
)
|
|
21
|
+
key = cache_key('games', 'list', **variables)
|
|
22
|
+
|
|
23
|
+
data = with_cache(key, resource: :games, cache: cache) { connection.execute(query, variables) }
|
|
24
|
+
|
|
25
|
+
Collection.new(
|
|
26
|
+
data['games'],
|
|
27
|
+
item_class: Game,
|
|
28
|
+
next_page_loader: lambda { |cursor|
|
|
29
|
+
list(publisher_id: publisher_id, first: first, after: cursor, include_archived: include_archived, cache: cache)
|
|
30
|
+
},
|
|
31
|
+
client: client
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get a publisher-managed game by stable key.
|
|
36
|
+
def get_by_key(game_key:, publisher_id: nil, publisher_slug: nil, cache: nil)
|
|
37
|
+
key = cache_key('games', 'get_by_key', publisher_id: publisher_id, publisher_slug: publisher_slug,
|
|
38
|
+
game_key: game_key)
|
|
39
|
+
with_cache(key, resource: :games, cache: cache) do
|
|
40
|
+
query = QueryBuilder.game(publisher_id: publisher_id, publisher_slug: publisher_slug, game_key: game_key)
|
|
41
|
+
variables = build_variables(publisherId: publisher_id, publisherSlug: publisher_slug, gameKey: game_key)
|
|
42
|
+
data = connection.execute(query, variables)
|
|
43
|
+
|
|
44
|
+
data['game'] ? Game.new(data['game'], client: client) : nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Get a publisher-managed game by slug.
|
|
49
|
+
def get_by_slug(game_slug:, publisher_id: nil, publisher_slug: nil, cache: nil)
|
|
50
|
+
key = cache_key('games', 'get_by_slug', publisher_id: publisher_id, publisher_slug: publisher_slug,
|
|
51
|
+
game_slug: game_slug)
|
|
52
|
+
with_cache(key, resource: :games, cache: cache) do
|
|
53
|
+
query = QueryBuilder.game(publisher_id: publisher_id, publisher_slug: publisher_slug, game_slug: game_slug)
|
|
54
|
+
variables = build_variables(publisherId: publisher_id, publisherSlug: publisher_slug, gameSlug: game_slug)
|
|
55
|
+
data = connection.execute(query, variables)
|
|
56
|
+
|
|
57
|
+
data['game'] ? Game.new(data['game'], client: client) : nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Create a publisher-managed game. Requires a server-side secret credential.
|
|
62
|
+
def create(input:)
|
|
63
|
+
config.require_secret_credential!('games.create')
|
|
64
|
+
|
|
65
|
+
data = connection.execute(QueryBuilder.create_game, { input: input })
|
|
66
|
+
Game.new(data['gameCreate'], client: client)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Update a publisher-managed game. Requires a server-side secret credential.
|
|
70
|
+
def update(id:, input:)
|
|
71
|
+
config.require_secret_credential!('games.update')
|
|
72
|
+
|
|
73
|
+
data = connection.execute(QueryBuilder.update_game, { id: id, input: input })
|
|
74
|
+
Game.new(data['gameUpdate'], client: client)
|
|
75
|
+
end
|
|
76
|
+
|
|
7
77
|
# Search for games
|
|
8
78
|
#
|
|
9
79
|
# @param publisher_slug [String, nil] Filter by publisher slug
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Publisher-managed import format resource.
|
|
6
|
+
class ImportFormats < Base
|
|
7
|
+
# List import formats scoped by publisher or game.
|
|
8
|
+
def list(publisher_id: nil, game_id: nil, include_archived: nil, first: nil, after: nil, cache: nil)
|
|
9
|
+
query = QueryBuilder.deck_import_formats(
|
|
10
|
+
publisher_id: publisher_id,
|
|
11
|
+
game_id: game_id,
|
|
12
|
+
include_archived: include_archived,
|
|
13
|
+
first: first,
|
|
14
|
+
after: after
|
|
15
|
+
)
|
|
16
|
+
variables = build_variables(
|
|
17
|
+
publisherId: publisher_id,
|
|
18
|
+
gameId: game_id,
|
|
19
|
+
includeArchived: include_archived,
|
|
20
|
+
first: first,
|
|
21
|
+
after: after
|
|
22
|
+
)
|
|
23
|
+
key = cache_key('import_formats', 'list', **variables)
|
|
24
|
+
|
|
25
|
+
data = with_cache(key, resource: :import_formats, cache: cache) do
|
|
26
|
+
connection.execute(query, variables)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Collection.new(
|
|
30
|
+
data['deckImportFormats'],
|
|
31
|
+
item_class: DeckImportFormatDefinition,
|
|
32
|
+
next_page_loader: lambda { |cursor|
|
|
33
|
+
list(
|
|
34
|
+
publisher_id: publisher_id,
|
|
35
|
+
game_id: game_id,
|
|
36
|
+
include_archived: include_archived,
|
|
37
|
+
first: first,
|
|
38
|
+
after: cursor,
|
|
39
|
+
cache: cache
|
|
40
|
+
)
|
|
41
|
+
},
|
|
42
|
+
client: client
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get one import format by UUID.
|
|
47
|
+
def get(id, cache: nil)
|
|
48
|
+
key = cache_key('import_formats', 'get', id: id)
|
|
49
|
+
with_cache(key, resource: :import_formats, cache: cache) do
|
|
50
|
+
data = connection.execute(QueryBuilder.deck_import_format, { id: id })
|
|
51
|
+
data['deckImportFormat'] ? DeckImportFormatDefinition.new(data['deckImportFormat'], client: client) : nil
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get one import format by game ID and stable key.
|
|
56
|
+
def get_by_key(game_id:, key:, cache: nil)
|
|
57
|
+
cache_key_value = cache_key('import_formats', 'get_by_key', game_id: game_id, key: key)
|
|
58
|
+
with_cache(cache_key_value, resource: :import_formats, cache: cache) do
|
|
59
|
+
data = connection.execute(QueryBuilder.deck_import_format, { gameId: game_id, key: key })
|
|
60
|
+
data['deckImportFormat'] ? DeckImportFormatDefinition.new(data['deckImportFormat'], client: client) : nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Preview parsing and record resolution for a configured import format.
|
|
65
|
+
def test(input:)
|
|
66
|
+
data = connection.execute(QueryBuilder.deck_import_format_test, { input: input })
|
|
67
|
+
DeckImportFormatTestPayload.new(data['deckImportFormatTest'], client: client)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Create an import format. Requires a server-side secret credential.
|
|
71
|
+
def create(input:)
|
|
72
|
+
config.require_secret_credential!('import_formats.create')
|
|
73
|
+
|
|
74
|
+
data = connection.execute(QueryBuilder.create_deck_import_format, { input: input })
|
|
75
|
+
DeckImportFormatDefinition.new(data['deckImportFormatCreate'], client: client)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Update an import format. Requires a server-side secret credential.
|
|
79
|
+
def update(id:, input:)
|
|
80
|
+
config.require_secret_credential!('import_formats.update')
|
|
81
|
+
|
|
82
|
+
data = connection.execute(QueryBuilder.update_deck_import_format, { id: id, input: input })
|
|
83
|
+
DeckImportFormatDefinition.new(data['deckImportFormatUpdate'], client: client)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Archive an import format. Requires a server-side secret credential.
|
|
87
|
+
def archive(id:)
|
|
88
|
+
config.require_secret_credential!('import_formats.archive')
|
|
89
|
+
|
|
90
|
+
data = connection.execute(QueryBuilder.archive_deck_import_format, { id: id })
|
|
91
|
+
DeckImportFormatDefinition.new(data['deckImportFormatArchive'], client: client)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Restore an archived import format. Requires a server-side secret credential.
|
|
95
|
+
def unarchive(id:)
|
|
96
|
+
config.require_secret_credential!('import_formats.unarchive')
|
|
97
|
+
|
|
98
|
+
data = connection.execute(QueryBuilder.unarchive_deck_import_format, { id: id })
|
|
99
|
+
DeckImportFormatDefinition.new(data['deckImportFormatUnarchive'], client: client)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Publisher import job resource for single-dataset and game-level imports.
|
|
6
|
+
class Imports < Base
|
|
7
|
+
EMPTY_CONNECTION = {
|
|
8
|
+
'totalCount' => 0,
|
|
9
|
+
'pageInfo' => {
|
|
10
|
+
'hasNextPage' => false,
|
|
11
|
+
'hasPreviousPage' => false,
|
|
12
|
+
'startCursor' => nil,
|
|
13
|
+
'endCursor' => nil
|
|
14
|
+
},
|
|
15
|
+
'edges' => []
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
# Preview a single-dataset import source without writing records or schema.
|
|
19
|
+
def preview(input:)
|
|
20
|
+
data = connection.execute(QueryBuilder.dataset_import_preview, { input: input })
|
|
21
|
+
DatasetImportPreviewResult.new(data['datasetImportPreview'], client: client)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Validate a single-dataset import source with dry-run enforced.
|
|
25
|
+
def validate(input:)
|
|
26
|
+
data = connection.execute(QueryBuilder.dataset_import_validate, { input: input })
|
|
27
|
+
BulkImportResult.new(data['datasetImportValidate'], client: client)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Start an async single-dataset import from fileId, data, or sourceUrl.
|
|
31
|
+
def run(input:)
|
|
32
|
+
config.require_secret_credential!('imports.run')
|
|
33
|
+
|
|
34
|
+
query, response_key = import_query_for(input)
|
|
35
|
+
data = connection.execute(query, { input: input })
|
|
36
|
+
ImportJob.new(data[response_key], client: client)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Fetch one single-dataset import job.
|
|
40
|
+
def get_job(id, cache: nil)
|
|
41
|
+
key = cache_key('imports', 'get_job', id: id)
|
|
42
|
+
with_cache(key, resource: :imports, cache: cache) do
|
|
43
|
+
data = connection.execute(QueryBuilder.import_job, { id: id })
|
|
44
|
+
data['importJob'] ? ImportJob.new(data['importJob'], client: client) : nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# List single-dataset import jobs for a publisher.
|
|
49
|
+
def list_jobs(publisher_id:, dataset_id: nil, status: nil, first: nil, after: nil, cache: nil)
|
|
50
|
+
query = QueryBuilder.import_jobs(
|
|
51
|
+
publisher_id: publisher_id,
|
|
52
|
+
dataset_id: dataset_id,
|
|
53
|
+
status: status,
|
|
54
|
+
first: first,
|
|
55
|
+
after: after
|
|
56
|
+
)
|
|
57
|
+
variables = build_variables(
|
|
58
|
+
publisherId: publisher_id,
|
|
59
|
+
datasetId: dataset_id,
|
|
60
|
+
status: status,
|
|
61
|
+
first: first,
|
|
62
|
+
after: after
|
|
63
|
+
)
|
|
64
|
+
key = cache_key('imports', 'list_jobs', **variables)
|
|
65
|
+
|
|
66
|
+
data = with_cache(key, resource: :imports, cache: cache) { connection.execute(query, variables) }
|
|
67
|
+
|
|
68
|
+
Collection.new(
|
|
69
|
+
data['importJobs'],
|
|
70
|
+
item_class: ImportJob,
|
|
71
|
+
next_page_loader: lambda { |cursor|
|
|
72
|
+
list_jobs(
|
|
73
|
+
publisher_id: publisher_id,
|
|
74
|
+
dataset_id: dataset_id,
|
|
75
|
+
status: status,
|
|
76
|
+
first: first,
|
|
77
|
+
after: cursor,
|
|
78
|
+
cache: cache
|
|
79
|
+
)
|
|
80
|
+
},
|
|
81
|
+
client: client
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Cancel a pending or processing single-dataset import job.
|
|
86
|
+
def cancel_job(id:)
|
|
87
|
+
config.require_secret_credential!('imports.cancel_job')
|
|
88
|
+
|
|
89
|
+
data = connection.execute(QueryBuilder.import_job_cancel, { id: id })
|
|
90
|
+
ImportJob.new(data['importJobCancel'], client: client)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# List structured logs for one single-dataset import job.
|
|
94
|
+
def list_job_logs(import_job_id:, level: nil, dataset_key: nil, first: nil, after: nil, cache: nil)
|
|
95
|
+
query = QueryBuilder.import_job_logs(level: level, dataset_key: dataset_key, first: first, after: after)
|
|
96
|
+
variables = build_variables(
|
|
97
|
+
importJobId: import_job_id,
|
|
98
|
+
level: level,
|
|
99
|
+
datasetKey: dataset_key,
|
|
100
|
+
first: first,
|
|
101
|
+
after: after
|
|
102
|
+
)
|
|
103
|
+
key = cache_key('imports', 'list_job_logs', **variables)
|
|
104
|
+
|
|
105
|
+
data = with_cache(key, resource: :imports, cache: cache) { connection.execute(query, variables) }
|
|
106
|
+
logs = data.dig('importJob', 'logs') || EMPTY_CONNECTION
|
|
107
|
+
|
|
108
|
+
Collection.new(
|
|
109
|
+
logs,
|
|
110
|
+
item_class: ImportJobLog,
|
|
111
|
+
next_page_loader: lambda { |cursor|
|
|
112
|
+
list_job_logs(
|
|
113
|
+
import_job_id: import_job_id,
|
|
114
|
+
level: level,
|
|
115
|
+
dataset_key: dataset_key,
|
|
116
|
+
first: first,
|
|
117
|
+
after: cursor,
|
|
118
|
+
cache: cache
|
|
119
|
+
)
|
|
120
|
+
},
|
|
121
|
+
client: client
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Poll a single-dataset import job until completion or failure.
|
|
126
|
+
def wait_for_job(id, interval: 1, timeout: 300)
|
|
127
|
+
wait_until_terminal(id, interval: interval, timeout: timeout) do
|
|
128
|
+
get_job(id, cache: false)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Preview an advanced game-level import source without writing records or schema.
|
|
133
|
+
def preview_game(input:)
|
|
134
|
+
data = connection.execute(QueryBuilder.game_import_preview, { input: input })
|
|
135
|
+
GameImportPreview.new(data['gameImportPreview'], client: client)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Start an async advanced game-level import from fileId, data, or sourceUrl.
|
|
139
|
+
def run_game(input:)
|
|
140
|
+
config.require_secret_credential!('imports.run_game')
|
|
141
|
+
|
|
142
|
+
query, response_key = game_import_query_for(input)
|
|
143
|
+
data = connection.execute(query, { input: input })
|
|
144
|
+
GameImportJob.new(data[response_key], client: client)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Fetch one advanced game-level import job.
|
|
148
|
+
def get_game_job(id, cache: nil)
|
|
149
|
+
key = cache_key('imports', 'get_game_job', id: id)
|
|
150
|
+
with_cache(key, resource: :imports, cache: cache) do
|
|
151
|
+
data = connection.execute(QueryBuilder.game_import_job, { id: id })
|
|
152
|
+
data['gameImportJob'] ? GameImportJob.new(data['gameImportJob'], client: client) : nil
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# List advanced game-level import jobs.
|
|
157
|
+
def list_game_jobs(game_id:, status: nil, first: nil, after: nil, cache: nil)
|
|
158
|
+
query = QueryBuilder.game_import_jobs(game_id: game_id, status: status, first: first, after: after)
|
|
159
|
+
variables = build_variables(gameId: game_id, status: status, first: first, after: after)
|
|
160
|
+
key = cache_key('imports', 'list_game_jobs', **variables)
|
|
161
|
+
|
|
162
|
+
data = with_cache(key, resource: :imports, cache: cache) { connection.execute(query, variables) }
|
|
163
|
+
|
|
164
|
+
Collection.new(
|
|
165
|
+
data['gameImportJobs'],
|
|
166
|
+
item_class: GameImportJob,
|
|
167
|
+
next_page_loader: lambda { |cursor|
|
|
168
|
+
list_game_jobs(game_id: game_id, status: status, first: first, after: cursor, cache: cache)
|
|
169
|
+
},
|
|
170
|
+
client: client
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Cancel a pending or processing advanced game-level import job.
|
|
175
|
+
def cancel_game_job(id:)
|
|
176
|
+
config.require_secret_credential!('imports.cancel_game_job')
|
|
177
|
+
|
|
178
|
+
data = connection.execute(QueryBuilder.game_import_job_cancel, { id: id })
|
|
179
|
+
GameImportJob.new(data['gameImportJobCancel'], client: client)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Poll an advanced game-level import job until completion or failure.
|
|
183
|
+
def wait_for_game_job(id, interval: 1, timeout: 300)
|
|
184
|
+
wait_until_terminal(id, interval: interval, timeout: timeout) do
|
|
185
|
+
get_game_job(id, cache: false)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
def import_query_for(input)
|
|
192
|
+
return [QueryBuilder.dataset_import_from_file, 'datasetImportFromFile'] if input_value(input, :fileId, 'fileId')
|
|
193
|
+
return [QueryBuilder.dataset_import_from_url, 'datasetImportFromUrl'] if input_value(input, :sourceUrl, 'sourceUrl')
|
|
194
|
+
|
|
195
|
+
[QueryBuilder.dataset_import_from_payload, 'datasetImportFromPayload']
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def game_import_query_for(input)
|
|
199
|
+
return [QueryBuilder.game_import_from_file, 'gameImportFromFile'] if input_value(input, :fileId, 'fileId')
|
|
200
|
+
return [QueryBuilder.game_import_from_url, 'gameImportFromUrl'] if input_value(input, :sourceUrl, 'sourceUrl')
|
|
201
|
+
|
|
202
|
+
[QueryBuilder.game_import_from_payload, 'gameImportFromPayload']
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def input_value(input, *keys)
|
|
206
|
+
keys.find { |key| input.key?(key) && input[key] }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def wait_until_terminal(id, interval:, timeout:)
|
|
210
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
211
|
+
|
|
212
|
+
loop do
|
|
213
|
+
job = yield
|
|
214
|
+
raise Error, "Job '#{id}' was not found" unless job
|
|
215
|
+
return job if job.completed? || job.failed?
|
|
216
|
+
|
|
217
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
218
|
+
raise Error, "Timed out waiting for job '#{id}'" if elapsed >= timeout
|
|
219
|
+
|
|
220
|
+
sleep interval
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|