gooddata 0.6.16 → 0.6.17

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