gooddata 0.6.16 → 0.6.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|