gooddata 0.6.16 → 0.6.17
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/.rubocop.yml +4 -1
- data/lib/gooddata/cli/commands/project_cmd.rb +1 -1
- data/lib/gooddata/core/logging.rb +15 -5
- data/lib/gooddata/core/rest.rb +4 -28
- data/lib/gooddata/helpers/global_helpers.rb +14 -138
- data/lib/gooddata/helpers/global_helpers_params.rb +145 -0
- data/lib/gooddata/mixins/md_object_indexer.rb +2 -2
- data/lib/gooddata/models/domain.rb +1 -1
- data/lib/gooddata/models/execution.rb +29 -1
- data/lib/gooddata/models/from_wire.rb +6 -0
- data/lib/gooddata/models/from_wire_parse.rb +125 -0
- data/lib/gooddata/models/metadata/attribute.rb +1 -1
- data/lib/gooddata/models/metadata/label.rb +11 -10
- data/lib/gooddata/models/model.rb +4 -0
- data/lib/gooddata/models/profile.rb +12 -2
- data/lib/gooddata/models/project.rb +6 -3
- data/lib/gooddata/models/project_blueprint.rb +4 -4
- data/lib/gooddata/models/project_creator.rb +8 -10
- data/lib/gooddata/models/report_data_result.rb +4 -2
- data/lib/gooddata/models/schedule.rb +121 -66
- data/lib/gooddata/models/to_wire.rb +12 -3
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +3 -234
- data/lib/gooddata/models/user_filters/user_filter_builder_create.rb +115 -0
- data/lib/gooddata/models/user_filters/user_filter_builder_execute.rb +133 -0
- data/lib/gooddata/rest/client.rb +27 -13
- data/lib/gooddata/rest/connection.rb +102 -23
- data/lib/gooddata/version.rb +1 -1
- data/spec/data/gd_gse_data_blueprint.json +1 -0
- data/spec/data/test_project_model_spec.json +5 -2
- data/spec/data/wire_models/model_view.json +3 -0
- data/spec/data/wire_test_project.json +8 -1
- data/spec/integration/full_project_spec.rb +1 -1
- data/spec/unit/core/connection_spec.rb +16 -0
- data/spec/unit/core/logging_spec.rb +54 -6
- data/spec/unit/models/domain_spec.rb +10 -4
- data/spec/unit/models/execution_spec.rb +102 -0
- data/spec/unit/models/from_wire_spec.rb +11 -2
- data/spec/unit/models/model_spec.rb +2 -2
- data/spec/unit/models/project_blueprint_spec.rb +1 -1
- data/spec/unit/models/schedule_spec.rb +34 -24
- data/spec/unit/models/to_wire_spec.rb +9 -1
- metadata +8 -3
@@ -27,9 +27,9 @@ module GoodData
|
|
27
27
|
return id if id.is_a?(MdObject)
|
28
28
|
uri = if id.is_a?(Integer) || id =~ /^\d+$/
|
29
29
|
"#{project.md[MD_OBJ_CTG]}/#{id}"
|
30
|
-
elsif id !~
|
30
|
+
elsif id !~ %r{/}
|
31
31
|
identifier_to_uri options, id
|
32
|
-
elsif id =~
|
32
|
+
elsif id =~ %r{^/}
|
33
33
|
id
|
34
34
|
else
|
35
35
|
fail 'Unexpected object id format: expected numeric ID, identifier with no slashes or an URI starting with a slash'
|
@@ -36,7 +36,7 @@ module GoodData
|
|
36
36
|
|
37
37
|
# Timestamp when execution was finished
|
38
38
|
def finished
|
39
|
-
Time.parse(json['execution']['endTime'])
|
39
|
+
json['execution']['endTime'] && Time.parse(json['execution']['endTime'])
|
40
40
|
end
|
41
41
|
|
42
42
|
# Log for execution
|
@@ -49,6 +49,26 @@ module GoodData
|
|
49
49
|
status == :ok
|
50
50
|
end
|
51
51
|
|
52
|
+
# Returns schedule URL
|
53
|
+
#
|
54
|
+
# @return [String] Schedule URL
|
55
|
+
def schedule_uri
|
56
|
+
uri = @json['execution']['links']['self'] if @json && @json['execution'] && @json['execution']['links']
|
57
|
+
uri.split('/')[0..-3].join('/')
|
58
|
+
end
|
59
|
+
|
60
|
+
# Is execution running?
|
61
|
+
def running?
|
62
|
+
status == :running
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns schedule
|
66
|
+
#
|
67
|
+
# @return [String] Schedule URL
|
68
|
+
def schedule
|
69
|
+
schedule_uri && project.schedules(schedule_uri)
|
70
|
+
end
|
71
|
+
|
52
72
|
# Timestamp when execution was started
|
53
73
|
def started
|
54
74
|
Time.parse(json['execution']['startTime'])
|
@@ -77,6 +97,14 @@ module GoodData
|
|
77
97
|
self
|
78
98
|
end
|
79
99
|
|
100
|
+
def duration
|
101
|
+
if running?
|
102
|
+
Time.now - started
|
103
|
+
else
|
104
|
+
finished && finished - started
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
80
108
|
# Compares two executions - based on their URI
|
81
109
|
def ==(other)
|
82
110
|
other.respond_to?(:uri) && other.uri == uri && other.respond_to?(:to_hash) && other.to_hash == to_hash
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
+
require_relative 'from_wire_parse'
|
4
|
+
|
3
5
|
module GoodData
|
4
6
|
module Model
|
5
7
|
module FromWire
|
@@ -105,6 +107,8 @@ module GoodData
|
|
105
107
|
f[:type] = fact['fact']['identifier'] =~ /^dt\./ ? :date_fact : :fact
|
106
108
|
f[:name] = fact['fact']['identifier'].split('.').last
|
107
109
|
f[:title] = fact['fact']['title'] if fact['fact']['title'] != fact['fact']['identifier'].split('.').last.titleize
|
110
|
+
f[:description] = fact['fact']['description'] if fact['fact']['description']
|
111
|
+
f[:folder] = fact['fact']['folder'] if fact['fact']['folder']
|
108
112
|
f[:gd_data_type] = fact['fact']['dataType'] if fact['fact'].key?('dataType')
|
109
113
|
end
|
110
114
|
end
|
@@ -120,6 +124,8 @@ module GoodData
|
|
120
124
|
l[:reference] = attribute['identifier'].split('.').last if type == 'label'
|
121
125
|
l[:name] = label['label']['identifier'].split('.').last
|
122
126
|
l[:title] = label['label']['title'] if label['label']['title'] != label['label']['identifier'].split('.').last.titleize
|
127
|
+
l[:description] = attribute['description'] if %w(attribute anchor).include?(type) && attribute['description']
|
128
|
+
l[:folder] = attribute['folder'] if attribute['folder'] && (type == 'attribute' || type == 'anchor')
|
123
129
|
l[:gd_data_type] = label['label']['dataType'] if label['label'].key?('dataType')
|
124
130
|
l[:gd_type] = label['label']['type'] if label['label'].key?('type')
|
125
131
|
l[:default_label] = true if default_label == label['label']['identifier']
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module GoodData
|
4
|
+
module Model
|
5
|
+
module FromWire
|
6
|
+
# Converts anchor from wire format into an internal blueprint representation
|
7
|
+
#
|
8
|
+
# @param stuff [Hash] Whatever comes from wire
|
9
|
+
# @return [Hash] Manifest for a particular reference
|
10
|
+
def self.parse_anchor(stuff)
|
11
|
+
attribute = stuff['dataset']['anchor']['attribute']
|
12
|
+
if !attribute.key?('labels')
|
13
|
+
[]
|
14
|
+
else
|
15
|
+
labels = attribute['labels'] || []
|
16
|
+
default_label = attribute['defaultLabel']
|
17
|
+
primary_label_name = attribute['identifier'].split('.').last
|
18
|
+
dataset_name = attribute['identifier'].split('.')[1]
|
19
|
+
primary_label_identifier = GoodData::Model.identifier_for({ name: dataset_name }, type: :primary_label, name: primary_label_name)
|
20
|
+
primary_labels, regular_labels = labels.partition { |x| x['label']['identifier'] == primary_label_identifier }
|
21
|
+
dl = primary_labels.map do |label|
|
22
|
+
parse_label(attribute, label, 'anchor', default_label)
|
23
|
+
end
|
24
|
+
rl = regular_labels.map do |label|
|
25
|
+
parse_label(attribute, label, 'label', default_label)
|
26
|
+
end
|
27
|
+
dl + rl
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Converts attrbutes from wire format into an internal blueprint representation
|
32
|
+
#
|
33
|
+
# @param stuff [Hash] Whatever comes from wire
|
34
|
+
# @return [Hash] Manifest for a particular reference
|
35
|
+
def self.parse_attributes(stuff)
|
36
|
+
dataset = stuff['dataset']
|
37
|
+
attributes = dataset['attributes'] || []
|
38
|
+
attributes.mapcat do |a|
|
39
|
+
attribute = a['attribute']
|
40
|
+
labels = attribute['labels'] || []
|
41
|
+
default_label = attribute['defaultLabel']
|
42
|
+
primary_label_name = attribute['identifier'].split('.').last
|
43
|
+
dataset_name = attribute['identifier'].split('.')[1]
|
44
|
+
primary_label_identifier = GoodData::Model.identifier_for({ name: dataset_name }, type: :primary_label, name: primary_label_name)
|
45
|
+
primary_labels, regular_labels = labels.partition { |x| x['label']['identifier'] == primary_label_identifier }
|
46
|
+
dl = primary_labels.map do |label|
|
47
|
+
parse_label(attribute, label, 'attribute', default_label)
|
48
|
+
end
|
49
|
+
rl = regular_labels.map do |label|
|
50
|
+
parse_label(attribute, label, 'label', default_label)
|
51
|
+
end
|
52
|
+
dl + rl
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Converts date dimensions from wire format into an internal blueprint representation
|
57
|
+
#
|
58
|
+
# @param stuff [Hash] Whatever comes from wire
|
59
|
+
# @return [Hash] Manifest for a particular reference
|
60
|
+
def self.parse_date_dimensions(stuff)
|
61
|
+
{}.tap do |d|
|
62
|
+
d[:type] = :date_dimension
|
63
|
+
# d[:urn] = :date_dimension
|
64
|
+
d[:name] = stuff['dateDimension']['name']
|
65
|
+
d[:title] = stuff['dateDimension']['title'] if stuff['dateDimension']['title'] != d[:name].titleize
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Converts facts from wire format into an internal blueprint representation
|
70
|
+
#
|
71
|
+
# @param stuff [Hash] Whatever comes from wire
|
72
|
+
# @return [Hash] Manifest for a particular reference
|
73
|
+
def self.parse_facts(stuff)
|
74
|
+
facts = stuff['dataset']['facts'] || []
|
75
|
+
facts.map do |fact|
|
76
|
+
{}.tap do |f|
|
77
|
+
f[:type] = fact['fact']['identifier'] =~ /^dt\./ ? :date_fact : :fact
|
78
|
+
f[:name] = fact['fact']['identifier'].split('.').last
|
79
|
+
f[:title] = fact['fact']['title'] if fact['fact']['title'] != fact['fact']['identifier'].split('.').last.titleize
|
80
|
+
f[:gd_data_type] = fact['fact']['dataType'] if fact['fact'].key?('dataType')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Converts label from wire format into an internal blueprint representation
|
86
|
+
#
|
87
|
+
# @param stuff [Hash] Whatever comes from wire
|
88
|
+
# @return [Hash] Manifest for a particular reference
|
89
|
+
def self.parse_label(attribute, label, type, default_label = nil)
|
90
|
+
{}.tap do |l|
|
91
|
+
l[:type] = type
|
92
|
+
l[:reference] = attribute['identifier'].split('.').last if type == 'label'
|
93
|
+
l[:name] = label['label']['identifier'].split('.').last
|
94
|
+
l[:title] = label['label']['title'] if label['label']['title'] != label['label']['identifier'].split('.').last.titleize
|
95
|
+
l[:gd_data_type] = label['label']['dataType'] if label['label'].key?('dataType')
|
96
|
+
l[:gd_type] = label['label']['type'] if label['label'].key?('type')
|
97
|
+
l[:default_label] = true if default_label == label['label']['identifier']
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Converts label from wire format into an internal blueprint representation
|
102
|
+
#
|
103
|
+
# @param stuff [Hash] Whatever comes from wire
|
104
|
+
# @return [Hash] Manifest for a particular reference
|
105
|
+
def self.parse_references(stuff)
|
106
|
+
references = stuff['dataset']['references'] || []
|
107
|
+
references.map do |ref|
|
108
|
+
if ref =~ /^dataset\./
|
109
|
+
{
|
110
|
+
:type => :reference,
|
111
|
+
:name => ref.gsub(/^dataset\./, ''),
|
112
|
+
:dataset => ref.gsub(/^dataset\./, '')
|
113
|
+
}
|
114
|
+
else
|
115
|
+
{
|
116
|
+
:type => :date,
|
117
|
+
:name => ref.gsub(/^dataset\./, ''),
|
118
|
+
:dataset => ref.gsub(/^dataset\./, '')
|
119
|
+
}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -29,7 +29,7 @@ module GoodData
|
|
29
29
|
# @param uri [String] Uri of the element. in the form of /gdc/md/PID/obj/OBJ_ID/elements?id=21
|
30
30
|
# @return [String] Textual representation of a particular attribute element
|
31
31
|
def find_element_value(uri, opts = { :client => @client, :project => @project })
|
32
|
-
matches = uri.match(
|
32
|
+
matches = uri.match(%r{(.*)/elements\?id=(\d+)$})
|
33
33
|
opts[:project].attributes(matches[1]).primary_label.find_element_value(uri)
|
34
34
|
end
|
35
35
|
end
|
@@ -15,7 +15,7 @@ module GoodData
|
|
15
15
|
# @return [String]
|
16
16
|
def find_value_uri(value)
|
17
17
|
escaped_value = CGI.escape(value)
|
18
|
-
results = client.post("#{uri}/validElements?limit=
|
18
|
+
results = client.post("#{uri}/validElements?limit=1&offset=0&order=asc&filter=#{escaped_value}", {})
|
19
19
|
items = results['validElements']['items']
|
20
20
|
if items.empty?
|
21
21
|
fail(AttributeElementNotFound, value)
|
@@ -55,27 +55,28 @@ module GoodData
|
|
55
55
|
# @return [Array]
|
56
56
|
def values(options = {})
|
57
57
|
limit = options[:limit] || 100
|
58
|
+
page_limit = 100
|
58
59
|
offset = 0
|
59
60
|
vals = []
|
60
61
|
loop do
|
61
|
-
results = GoodData.post("#{uri}/validElements?limit=#{
|
62
|
-
|
62
|
+
results = GoodData.post("#{uri}/validElements?limit=#{page_limit}&offset=#{offset}&order=asc", {})
|
63
|
+
elements = results['validElements']
|
64
|
+
items = elements['items'].map do |el|
|
63
65
|
v = el['element']
|
64
66
|
{
|
65
67
|
:value => v['title'],
|
66
68
|
:uri => v['uri']
|
67
69
|
}
|
68
70
|
end
|
69
|
-
vals.concat(
|
70
|
-
break if vals.length
|
71
|
-
offset +=
|
71
|
+
vals.concat(items)
|
72
|
+
break if vals.length > limit || vals.length == elements['paging']['total'].to_i
|
73
|
+
offset += page_limit
|
72
74
|
end
|
73
|
-
vals
|
75
|
+
vals.take(limit)
|
74
76
|
end
|
75
77
|
|
76
|
-
def values_count
|
77
|
-
|
78
|
-
results = client.post("#{uri}/validElements?limit=#{limit}&offset=0&order=asc", {})
|
78
|
+
def values_count
|
79
|
+
results = client.post("#{uri}/validElements?limit=1&offset=0&order=asc", {})
|
79
80
|
results['validElements']['paging']['total'].to_i
|
80
81
|
end
|
81
82
|
|
@@ -29,6 +29,10 @@ module GoodData
|
|
29
29
|
item[:title] || item[:name].titleize
|
30
30
|
end
|
31
31
|
|
32
|
+
def description(item)
|
33
|
+
item[:description]
|
34
|
+
end
|
35
|
+
|
32
36
|
def identifier_for(dataset, column = nil, column2 = nil) # rubocop:disable UnusedMethodArgument
|
33
37
|
return "dataset.#{dataset[:name]}" if column.nil?
|
34
38
|
column = DatasetBlueprint.find_column_by_name(dataset, column) if column.is_a?(String)
|
@@ -59,7 +59,7 @@ module GoodData
|
|
59
59
|
fail(ArgumentError, 'wrong type of argument. Should be either project ID or path')
|
60
60
|
end
|
61
61
|
|
62
|
-
id = id.match(/[a-zA-Z\d]+$/)[0] if id =~
|
62
|
+
id = id.match(/[a-zA-Z\d]+$/)[0] if id =~ %r{/}
|
63
63
|
|
64
64
|
c = client(opts)
|
65
65
|
fail ArgumentError, 'No :client specified' if c.nil?
|
@@ -372,6 +372,15 @@ module GoodData
|
|
372
372
|
(first_name || '') + (last_name || '')
|
373
373
|
end
|
374
374
|
|
375
|
+
def sso_provider
|
376
|
+
@json['accountSetting']['ssoProvider']
|
377
|
+
end
|
378
|
+
|
379
|
+
def sso_provider=(an_sso_provider)
|
380
|
+
@dirty = true
|
381
|
+
@json['accountSetting']['ssoProvider'] = an_sso_provider
|
382
|
+
end
|
383
|
+
|
375
384
|
def to_hash
|
376
385
|
tmp = content.merge(uri: uri).symbolize_keys
|
377
386
|
[
|
@@ -379,7 +388,8 @@ module GoodData
|
|
379
388
|
[:phoneNumber, :phone],
|
380
389
|
[:firstName, :first_name],
|
381
390
|
[:lastName, :last_name],
|
382
|
-
[:authenticationModes, :authentication_modes]
|
391
|
+
[:authenticationModes, :authentication_modes],
|
392
|
+
[:ssoProvider, :sso_provider]
|
383
393
|
].each do |vals|
|
384
394
|
wire, rb = vals
|
385
395
|
tmp[rb] = tmp[wire]
|
@@ -73,7 +73,7 @@ module GoodData
|
|
73
73
|
fail(ArgumentError, 'wrong type of argument. Should be either project ID or path')
|
74
74
|
end
|
75
75
|
|
76
|
-
id = id.match(/[a-zA-Z\d]+$/)[0] if id =~
|
76
|
+
id = id.match(/[a-zA-Z\d]+$/)[0] if id =~ %r{/}
|
77
77
|
|
78
78
|
c = client(opts)
|
79
79
|
fail ArgumentError, 'No :client specified' if c.nil?
|
@@ -431,8 +431,7 @@ module GoodData
|
|
431
431
|
# Get WebDav directory for project data
|
432
432
|
# @return [String]
|
433
433
|
def project_webdav_path
|
434
|
-
|
435
|
-
URI.join(u.to_s.chomp(u.path.to_s), '/project-uploads/', "#{pid}/")
|
434
|
+
client.project_webdav_path(:project => self)
|
436
435
|
end
|
437
436
|
|
438
437
|
# Gets project role by its identifier
|
@@ -1083,6 +1082,10 @@ module GoodData
|
|
1083
1082
|
GoodData::Variable[id, options]
|
1084
1083
|
end
|
1085
1084
|
|
1085
|
+
def update_from_blueprint(blueprint, options = {})
|
1086
|
+
GoodData::Model::ProjectCreator.migrate(options.merge(spec: blueprint, token: options[:auth_token], client: client, project: self))
|
1087
|
+
end
|
1088
|
+
|
1086
1089
|
private
|
1087
1090
|
|
1088
1091
|
def generate_user_payload(user_uri, status = 'ENABLED', roles_uri = nil)
|
@@ -161,7 +161,7 @@ module GoodData
|
|
161
161
|
ProjectBlueprint.datasets(to_hash, options).map { |d| DatasetBlueprint.new(d) }
|
162
162
|
end
|
163
163
|
|
164
|
-
def add_dataset(a_dataset, index = nil)
|
164
|
+
def add_dataset!(a_dataset, index = nil)
|
165
165
|
if index.nil? || index > datasets.length
|
166
166
|
data[:datasets] << a_dataset.to_hash
|
167
167
|
else
|
@@ -458,10 +458,10 @@ module GoodData
|
|
458
458
|
local_dataset = temp_blueprint.find_dataset(dataset.name)
|
459
459
|
index = temp_blueprint.datasets.index(local_dataset)
|
460
460
|
local_dataset.merge!(dataset)
|
461
|
-
temp_blueprint.remove_dataset(local_dataset.name)
|
462
|
-
temp_blueprint.add_dataset(local_dataset, index)
|
461
|
+
temp_blueprint.remove_dataset!(local_dataset.name)
|
462
|
+
temp_blueprint.add_dataset!(local_dataset, index)
|
463
463
|
else
|
464
|
-
temp_blueprint.add_dataset(dataset.dup)
|
464
|
+
temp_blueprint.add_dataset!(dataset.dup)
|
465
465
|
end
|
466
466
|
end
|
467
467
|
temp_blueprint
|
@@ -25,15 +25,13 @@ module GoodData
|
|
25
25
|
fail('You need to specify token for project creation') if token.nil? && project.nil?
|
26
26
|
|
27
27
|
begin
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
p
|
36
|
-
end
|
28
|
+
migrate_datasets(spec, opts.merge(project: project, client: client))
|
29
|
+
load(p, spec)
|
30
|
+
migrate_metrics(p, spec[:metrics] || [])
|
31
|
+
migrate_reports(p, spec[:reports] || [])
|
32
|
+
migrate_dashboards(p, spec[:dashboards] || [])
|
33
|
+
execute_tests(p, spec[:assert_tests] || [])
|
34
|
+
project
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
@@ -46,7 +44,7 @@ module GoodData
|
|
46
44
|
p = opts[:project]
|
47
45
|
fail ArgumentError, 'No :project specified' if p.nil?
|
48
46
|
|
49
|
-
project =
|
47
|
+
project = client.projects(p)
|
50
48
|
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
51
49
|
|
52
50
|
bp = ProjectBlueprint.new(spec)
|
@@ -118,7 +118,8 @@ module GoodData
|
|
118
118
|
kids = rows['tree']['children']
|
119
119
|
|
120
120
|
if kids.empty? || (kids.size == 1 && kids.first['type'] == 'metric')
|
121
|
-
headers
|
121
|
+
headers = [[nil]]
|
122
|
+
size = 0
|
122
123
|
else
|
123
124
|
headers = []
|
124
125
|
size = each_level(headers, 0, rows['tree']['children'], rows['lookups'])
|
@@ -131,7 +132,8 @@ module GoodData
|
|
131
132
|
kids = columns['tree']['children']
|
132
133
|
|
133
134
|
if kids.empty? || (kids.size == 1 && kids.first['type'] == 'metric')
|
134
|
-
headers
|
135
|
+
headers = [[nil]]
|
136
|
+
size = 0
|
135
137
|
else
|
136
138
|
headers = []
|
137
139
|
size = each_level(headers, 0, columns['tree']['children'], columns['lookups'])
|
@@ -13,11 +13,20 @@ module GoodData
|
|
13
13
|
|
14
14
|
alias_method :data, :json
|
15
15
|
alias_method :raw_data, :json
|
16
|
-
alias_method :to_hash, :json
|
17
16
|
|
18
17
|
include GoodData::Mixin::RestResource
|
19
18
|
root_key :schedule
|
20
19
|
|
20
|
+
SCHEDULE_TEMPLATE = {
|
21
|
+
:schedule => {
|
22
|
+
:type => nil,
|
23
|
+
:timezone => nil,
|
24
|
+
:params => {},
|
25
|
+
:hiddenParams => {},
|
26
|
+
:reschedule => nil
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
21
30
|
class << self
|
22
31
|
# Looks for schedule
|
23
32
|
# @param id [String] URL, ID of schedule or :all
|
@@ -79,66 +88,28 @@ module GoodData
|
|
79
88
|
project = GoodData::Project[p, options]
|
80
89
|
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
81
90
|
|
91
|
+
fail 'Process ID has to be provided' if process_id.blank?
|
92
|
+
fail 'Executable has to be provided' if executable.blank?
|
93
|
+
|
82
94
|
default_opts = {
|
83
95
|
:type => 'MSETL',
|
84
96
|
:timezone => 'UTC',
|
85
97
|
:params => {
|
86
|
-
|
87
|
-
|
98
|
+
'PROCESS_ID' => process_id,
|
99
|
+
'EXECUTABLE' => executable
|
88
100
|
},
|
89
|
-
:
|
90
|
-
:reschedule => options[:reschedule] || 0
|
91
|
-
}
|
92
|
-
|
93
|
-
if trigger =~ /[a-fA-Z0-9]{24}/
|
94
|
-
default_opts[:triggerScheduleId] = trigger
|
95
|
-
elsif trigger.is_a?(GoodData::Schedule)
|
96
|
-
default_opts[:triggerScheduleId] = trigger.obj_id
|
97
|
-
else
|
98
|
-
default_opts[:cron] = trigger
|
99
|
-
end
|
100
|
-
|
101
|
-
if options.key?(:hidden_params)
|
102
|
-
options[:hiddenParams] = options[:hidden_params]
|
103
|
-
options.delete :hidden_params
|
104
|
-
end
|
105
|
-
|
106
|
-
json = {
|
107
|
-
'schedule' => default_opts.deep_merge(options.except(:project, :client))
|
101
|
+
:reschedule => 0
|
108
102
|
}
|
109
103
|
|
110
|
-
|
111
|
-
fail 'Process ID has to be provided' if tmp.nil? || tmp.empty?
|
112
|
-
|
113
|
-
tmp = json['schedule'][:params][:EXECUTABLE]
|
114
|
-
fail 'Executable has to be provided' if tmp.nil? || tmp.empty?
|
115
|
-
|
116
|
-
tmp = json['schedule'][:cron] || json['schedule'][:triggerScheduleId]
|
117
|
-
fail 'trigger schedule has to be provided' if !tmp || tmp.nil? || tmp.empty?
|
104
|
+
schedule = c.create(GoodData::Schedule, GoodData::Helpers.stringify_keys_deep!(SCHEDULE_TEMPLATE.deep_dup), client: c, project: p)
|
118
105
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
params = GoodData::Helpers.encode_params(params, false)
|
127
|
-
json['schedule'][:params] = params
|
128
|
-
|
129
|
-
hidden_params = json['schedule'][:hiddenParams]
|
130
|
-
if hidden_params && !hidden_params.empty?
|
131
|
-
hidden_params = GoodData::Helpers.encode_params(json['schedule'][:hiddenParams], true)
|
132
|
-
json['schedule'][:hiddenParams] = hidden_params
|
133
|
-
end
|
134
|
-
|
135
|
-
url = "/gdc/projects/#{project.pid}/schedules"
|
136
|
-
res = c.post url, json
|
137
|
-
|
138
|
-
fail 'Unable to create new schedule' if res.nil?
|
139
|
-
|
140
|
-
new_obj_json = c.get res['schedule']['links']['self']
|
141
|
-
c.create(GoodData::Schedule, new_obj_json, client: c, project: p)
|
106
|
+
schedule.hidden_params = options[:hidden_params]
|
107
|
+
schedule.set_trigger(trigger)
|
108
|
+
schedule.params = default_opts[:params].merge(options[:params] || {})
|
109
|
+
schedule.timezone = options[:timezone] || default_opts[:timezone]
|
110
|
+
schedule.schedule_type = options[:type] || default_opts[:type]
|
111
|
+
schedule.reschedule = options[:reschedule] || default_opts[:reschedule]
|
112
|
+
schedule.save
|
142
113
|
end
|
143
114
|
end
|
144
115
|
|
@@ -147,10 +118,24 @@ module GoodData
|
|
147
118
|
# @param json [Object] Raw JSON
|
148
119
|
# @return [GoodData::Schedule] New GoodData::Schedule instance
|
149
120
|
def initialize(json)
|
121
|
+
json = GoodData::Helpers.stringify_keys_deep!(json)
|
150
122
|
super
|
123
|
+
GoodData::Helpers.decode_params(json['schedule']['params'] || {})
|
124
|
+
GoodData::Helpers.decode_params(json['schedule']['hiddenParams'] || {})
|
151
125
|
@json = json
|
152
126
|
end
|
153
127
|
|
128
|
+
def after
|
129
|
+
schedules.find { |s| s.obj_id == trigger_id }
|
130
|
+
end
|
131
|
+
|
132
|
+
def after=(schedule)
|
133
|
+
fail 'After trigger has to be a schedule object' unless schedule.is_a?(Schedule)
|
134
|
+
json['schedule']['triggerScheduleId'] = schedule.obj_id
|
135
|
+
@json['schedule']['cron'] = nil
|
136
|
+
@dirty = true
|
137
|
+
end
|
138
|
+
|
154
139
|
# Deletes schedule
|
155
140
|
def delete
|
156
141
|
client.delete uri
|
@@ -260,6 +245,7 @@ module GoodData
|
|
260
245
|
# @param new_cron [String] Cron settings to be set
|
261
246
|
def cron=(new_cron)
|
262
247
|
@json['schedule']['cron'] = new_cron
|
248
|
+
@json['schedule']['triggerScheduleId'] = nil
|
263
249
|
@dirty = true
|
264
250
|
end
|
265
251
|
|
@@ -312,7 +298,9 @@ module GoodData
|
|
312
298
|
if @json # rubocop:disable Style/GuardClause
|
313
299
|
url = @json['schedule']['links']['executions']
|
314
300
|
res = client.get url
|
315
|
-
res['executions']['items']
|
301
|
+
res['executions']['items'].map do |e|
|
302
|
+
client.create(Execution, e, :project => project)
|
303
|
+
end
|
316
304
|
end
|
317
305
|
end
|
318
306
|
|
@@ -326,8 +314,12 @@ module GoodData
|
|
326
314
|
# Assigns execution parameters
|
327
315
|
#
|
328
316
|
# @param params [String] Params to be set
|
329
|
-
def params=(
|
330
|
-
|
317
|
+
def params=(new_params = {})
|
318
|
+
default_params = {
|
319
|
+
'PROCESS_ID' => process_id,
|
320
|
+
'EXECUTABLE' => executable
|
321
|
+
}
|
322
|
+
@json['schedule']['params'] = default_params.merge(new_params || {})
|
331
323
|
@dirty = true
|
332
324
|
end
|
333
325
|
|
@@ -341,8 +333,8 @@ module GoodData
|
|
341
333
|
# Assigns hidden parameters
|
342
334
|
#
|
343
335
|
# @param new_hidden_param [String] Hidden parameters to be set
|
344
|
-
def hidden_params=(
|
345
|
-
@json['schedule']['hiddenParams'] =
|
336
|
+
def hidden_params=(new_hidden_params = {})
|
337
|
+
@json['schedule']['hiddenParams'] = new_hidden_params || {}
|
346
338
|
@dirty = true
|
347
339
|
end
|
348
340
|
|
@@ -350,24 +342,87 @@ module GoodData
|
|
350
342
|
#
|
351
343
|
# @return [Boolean] True if saved
|
352
344
|
def save
|
345
|
+
fail 'trigger schedule has to be provided' if cron.blank? && trigger_id.blank?
|
346
|
+
fail 'A timezone has to be provided' if timezone.blank?
|
347
|
+
fail 'Schedule type has to be provided' if schedule_type.blank?
|
353
348
|
if @dirty
|
354
349
|
update_json = {
|
355
350
|
'schedule' => {
|
351
|
+
'name' => @json['schedule']['name'],
|
356
352
|
'type' => @json['schedule']['type'],
|
357
353
|
'state' => @json['schedule']['state'],
|
358
354
|
'timezone' => @json['schedule']['timezone'],
|
355
|
+
'reschedule' => @json['schedule']['reschedule'],
|
359
356
|
'cron' => @json['schedule']['cron'],
|
360
|
-
'
|
361
|
-
'
|
362
|
-
'
|
363
|
-
}
|
357
|
+
'triggerScheduleId' => trigger_id,
|
358
|
+
'params' => GoodData::Helpers.encode_params(params, false),
|
359
|
+
'hiddenParams' => GoodData::Helpers.encode_params(hidden_params, true)
|
360
|
+
}.compact
|
364
361
|
}
|
365
|
-
|
366
|
-
|
362
|
+
if uri
|
363
|
+
res = client.put(uri, update_json)
|
364
|
+
@json = res
|
365
|
+
else
|
366
|
+
res = client.post "/gdc/projects/#{project.pid}/schedules", update_json
|
367
|
+
fail 'Unable to create new schedule' if res.nil?
|
368
|
+
new_obj_json = client.get res['schedule']['links']['self']
|
369
|
+
@json = new_obj_json
|
370
|
+
end
|
367
371
|
@dirty = false
|
368
|
-
return true
|
369
372
|
end
|
370
|
-
|
373
|
+
self
|
374
|
+
end
|
375
|
+
|
376
|
+
def time_based?
|
377
|
+
cron != nil
|
378
|
+
end
|
379
|
+
|
380
|
+
def to_hash
|
381
|
+
{
|
382
|
+
name: name,
|
383
|
+
type: type,
|
384
|
+
state: state,
|
385
|
+
params: params,
|
386
|
+
hidden_params: hidden_params,
|
387
|
+
cron: cron,
|
388
|
+
trigger_id: trigger_id,
|
389
|
+
timezone: timezone,
|
390
|
+
uri: uri
|
391
|
+
}.compact
|
392
|
+
end
|
393
|
+
|
394
|
+
def schedule_type
|
395
|
+
json['schedule']['type']
|
396
|
+
end
|
397
|
+
|
398
|
+
def schedule_type=(type)
|
399
|
+
json['schedule']['type'] = type
|
400
|
+
end
|
401
|
+
|
402
|
+
def trigger_id
|
403
|
+
json['schedule']['triggerScheduleId']
|
404
|
+
end
|
405
|
+
|
406
|
+
def trigger_id=(a_trigger)
|
407
|
+
json['schedule']['triggerScheduleId'] = a_trigger
|
408
|
+
end
|
409
|
+
|
410
|
+
def name
|
411
|
+
json['schedule']['name']
|
412
|
+
end
|
413
|
+
|
414
|
+
def name=(name)
|
415
|
+
json['schedule']['name'] = name
|
416
|
+
end
|
417
|
+
|
418
|
+
def set_trigger(trigger) # rubocop:disable Style/AccessorMethodName
|
419
|
+
if trigger.is_a?(String) && trigger =~ /[a-fA-Z0-9]{24}/
|
420
|
+
self.trigger_id = trigger
|
421
|
+
elsif trigger.is_a?(GoodData::Schedule)
|
422
|
+
self.trigger_id = trigger.obj_id
|
423
|
+
else
|
424
|
+
self.cron = trigger
|
425
|
+
end
|
371
426
|
end
|
372
427
|
|
373
428
|
# Returns URL
|