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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/lib/gooddata/cli/commands/project_cmd.rb +1 -1
  4. data/lib/gooddata/core/logging.rb +15 -5
  5. data/lib/gooddata/core/rest.rb +4 -28
  6. data/lib/gooddata/helpers/global_helpers.rb +14 -138
  7. data/lib/gooddata/helpers/global_helpers_params.rb +145 -0
  8. data/lib/gooddata/mixins/md_object_indexer.rb +2 -2
  9. data/lib/gooddata/models/domain.rb +1 -1
  10. data/lib/gooddata/models/execution.rb +29 -1
  11. data/lib/gooddata/models/from_wire.rb +6 -0
  12. data/lib/gooddata/models/from_wire_parse.rb +125 -0
  13. data/lib/gooddata/models/metadata/attribute.rb +1 -1
  14. data/lib/gooddata/models/metadata/label.rb +11 -10
  15. data/lib/gooddata/models/model.rb +4 -0
  16. data/lib/gooddata/models/profile.rb +12 -2
  17. data/lib/gooddata/models/project.rb +6 -3
  18. data/lib/gooddata/models/project_blueprint.rb +4 -4
  19. data/lib/gooddata/models/project_creator.rb +8 -10
  20. data/lib/gooddata/models/report_data_result.rb +4 -2
  21. data/lib/gooddata/models/schedule.rb +121 -66
  22. data/lib/gooddata/models/to_wire.rb +12 -3
  23. data/lib/gooddata/models/user_filters/user_filter_builder.rb +3 -234
  24. data/lib/gooddata/models/user_filters/user_filter_builder_create.rb +115 -0
  25. data/lib/gooddata/models/user_filters/user_filter_builder_execute.rb +133 -0
  26. data/lib/gooddata/rest/client.rb +27 -13
  27. data/lib/gooddata/rest/connection.rb +102 -23
  28. data/lib/gooddata/version.rb +1 -1
  29. data/spec/data/gd_gse_data_blueprint.json +1 -0
  30. data/spec/data/test_project_model_spec.json +5 -2
  31. data/spec/data/wire_models/model_view.json +3 -0
  32. data/spec/data/wire_test_project.json +8 -1
  33. data/spec/integration/full_project_spec.rb +1 -1
  34. data/spec/unit/core/connection_spec.rb +16 -0
  35. data/spec/unit/core/logging_spec.rb +54 -6
  36. data/spec/unit/models/domain_spec.rb +10 -4
  37. data/spec/unit/models/execution_spec.rb +102 -0
  38. data/spec/unit/models/from_wire_spec.rb +11 -2
  39. data/spec/unit/models/model_spec.rb +2 -2
  40. data/spec/unit/models/project_blueprint_spec.rb +1 -1
  41. data/spec/unit/models/schedule_spec.rb +34 -24
  42. data/spec/unit/models/to_wire_spec.rb +9 -1
  43. 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'
@@ -142,7 +142,7 @@ module GoodData
142
142
 
143
143
  # Optional sso provider
144
144
  tmp = user_data[:sso_provider]
145
- data['ssoProvider'] = tmp if tmp && !tmp.empty?
145
+ data['ssoProvider'] = tmp if tmp
146
146
 
147
147
  # Optional timezone
148
148
  tmp = user_data[:timezone]
@@ -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(/(.*)\/elements\?id=(\d+)$/)
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=30&offset=0&order=asc&filter=#{escaped_value}", {})
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=#{limit}&offset=#{offset}&order=asc", {})
62
- x = results['validElements']['items'].map do |el|
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(x)
70
- break if vals.length < offset
71
- offset += limit
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(options = {})
77
- limit = options[:limit] || 100
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
- u = URI(links['uploads'])
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
- GoodData.with_project(project, opts) do |p|
29
- migrate_datasets(spec, opts.merge(project: p, client: client))
30
- load(p, spec)
31
- migrate_metrics(p, spec[:metrics] || [])
32
- migrate_reports(p, spec[:reports] || [])
33
- migrate_dashboards(p, spec[:dashboards] || [])
34
- execute_tests(p, spec[:assert_tests] || [])
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 = Project[p, { :client => client }]
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, size = [[nil]], 0
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, size = [[nil]], 0
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
- :PROCESS_ID => process_id,
87
- :EXECUTABLE => executable
98
+ 'PROCESS_ID' => process_id,
99
+ 'EXECUTABLE' => executable
88
100
  },
89
- :hiddenParams => {},
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
- tmp = json['schedule'][:params][:PROCESS_ID]
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
- tmp = json['schedule'][:timezone]
120
- fail 'A timezone has to be provided' if tmp.nil? || tmp.empty?
121
-
122
- tmp = json['schedule'][:type]
123
- fail 'Schedule type has to be provided' if tmp.nil? || tmp.empty?
124
-
125
- params = json['schedule'][:params]
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=(new_param)
330
- @json['schedule']['params'].merge!(new_param)
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=(new_hidden_param)
345
- @json['schedule']['hiddenParams'] = hidden_params.merge(new_hidden_param)
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
- 'params' => @json['schedule']['params'],
361
- 'hiddenParams' => @json['schedule']['hiddenParams'],
362
- 'reschedule' => @json['schedule']['reschedule'] || 0
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
- res = client.put(uri, update_json)
366
- @json = res
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
- false
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