zenaton 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +79 -52
  3. data/.gitignore +7 -0
  4. data/.rubocop.yml +4 -0
  5. data/Appraisals +13 -0
  6. data/CHANGELOG.md +17 -1
  7. data/Gemfile.lock +17 -8
  8. data/gemfiles/.bundle/config +2 -0
  9. data/gemfiles/rails_5.0.gemfile +7 -0
  10. data/gemfiles/rails_5.1.gemfile +7 -0
  11. data/gemfiles/rails_5.2.gemfile +7 -0
  12. data/lib/zenaton/client.rb +63 -0
  13. data/lib/zenaton/contexts.rb +7 -0
  14. data/lib/zenaton/contexts/task.rb +33 -0
  15. data/lib/zenaton/contexts/workflow.rb +17 -0
  16. data/lib/zenaton/engine.rb +16 -0
  17. data/lib/zenaton/interfaces/task.rb +30 -1
  18. data/lib/zenaton/interfaces/workflow.rb +15 -0
  19. data/lib/zenaton/refinements.rb +16 -0
  20. data/lib/zenaton/refinements/big_decimal.rb +25 -0
  21. data/lib/zenaton/refinements/class.rb +21 -0
  22. data/lib/zenaton/refinements/complex.rb +22 -0
  23. data/lib/zenaton/refinements/date.rb +26 -0
  24. data/lib/zenaton/refinements/date_time.rb +40 -0
  25. data/lib/zenaton/refinements/exception.rb +24 -0
  26. data/lib/zenaton/refinements/object.rb +15 -0
  27. data/lib/zenaton/refinements/open_struct.rb +26 -0
  28. data/lib/zenaton/refinements/range.rb +21 -0
  29. data/lib/zenaton/refinements/rational.rb +22 -0
  30. data/lib/zenaton/refinements/regexp.rb +22 -0
  31. data/lib/zenaton/refinements/struct.rb +24 -0
  32. data/lib/zenaton/refinements/symbol.rb +21 -0
  33. data/lib/zenaton/refinements/time.rb +32 -0
  34. data/lib/zenaton/services/graphql.rb +71 -0
  35. data/lib/zenaton/services/http.rb +14 -9
  36. data/lib/zenaton/services/properties.rb +11 -57
  37. data/lib/zenaton/services/serializer.rb +2 -0
  38. data/lib/zenaton/traits/with_timestamp.rb +4 -2
  39. data/lib/zenaton/traits/zenatonable.rb +9 -0
  40. data/lib/zenaton/version.rb +1 -1
  41. data/zenaton.gemspec +2 -0
  42. metadata +54 -2
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ # Represents the current runtime context of a Workflow or Task.
5
+ module Contexts
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ module Contexts
5
+ # Represents the current runtime context of a Task.
6
+ #
7
+ # The information provided by the context can be useful to alter the
8
+ # behaviour of the task.
9
+ #
10
+ # For example, you can use the attempt index to know if a task has been
11
+ # automatically retried or not and how many times, and decide to do
12
+ # something when you did not expect the task to be retried more than X
13
+ # times.
14
+ #
15
+ # You can also use the attempt number in the `on_error_retry_delay` method
16
+ # of a task in order to implement complex retry strategies.
17
+ class Task
18
+ # @return [String] The UUID identifying the current task
19
+ attr_reader :id
20
+
21
+ # @return [Integer] The number of times this task has been automatically
22
+ # retried. This counter is reset if you issue a manual retry from your
23
+ # dashboard
24
+ attr_reader :retry_index
25
+
26
+ # @return [Zenaton::Contexts::Task] a new execution context for a task
27
+ def initialize(**kwargs)
28
+ @id = kwargs[:id]
29
+ @retry_index = kwargs[:retry_index]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ module Contexts
5
+ # Represents the current runtime context of a Workflow.
6
+ class Workflow
7
+ # @return [String] The UUID identifying the current workflow
8
+ attr_reader :id
9
+
10
+ # @return [Zenaton::Contexts::Workflow] a new execution context for a
11
+ # workflow
12
+ def initialize(**kwargs)
13
+ @id = kwargs[:id]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -24,6 +24,22 @@ module Zenaton
24
24
  @processor = nil
25
25
  end
26
26
 
27
+ # Executes scheduling jobs synchronously
28
+ # @param jobs [Array<Zenaton::Interfaces::Job>]
29
+ # @param cron String
30
+ # @return [Array<String>, nil] the results if executed locally, or nil
31
+ def schedule(jobs, cron)
32
+ jobs.map(&method(:check_argument))
33
+ jobs.map do |job|
34
+ if job.is_a? Interfaces::Workflow
35
+ @client.start_scheduled_workflow(job, cron)
36
+ else
37
+ @client.start_scheduled_task(job, cron)
38
+ end
39
+ end
40
+ nil
41
+ end
42
+
27
43
  # Executes jobs synchronously
28
44
  # @param jobs [Array<Zenaton::Interfaces::Job>]
29
45
  # @return [Array<String>, nil] the results if executed locally, or nil
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'zenaton/interfaces/job'
4
+ require 'zenaton/contexts/task'
4
5
 
5
6
  module Zenaton
6
7
  module Interfaces
@@ -9,7 +10,35 @@ module Zenaton
9
10
  # Child classes should implement the handle method
10
11
  def handle
11
12
  raise NotImplemented,
12
- "Your workflow does not implement the `handle' method"
13
+ "Your workflow does not implement the `#handle' method"
14
+ end
15
+
16
+ # (Optional) Implement this method for automatic retrial of task in
17
+ # case of failures.
18
+ # @param _exception [Exception] the reason for the task failure.
19
+ # @return [Integer, FalseClass, NilClass] the non-negative amount of
20
+ # seconds to wait before automatically retrying this task. Falsy values
21
+ # will avoid retrial. Other values will cause the retrial to fail.
22
+ def on_error_retry_delay(_exception)
23
+ nil
24
+ end
25
+
26
+ # @return [Zenaton::Contexts::Task] the task execution context
27
+ def context
28
+ @context || Contexts::Task.new
29
+ end
30
+
31
+ # @private
32
+ # Sets a new context if none has been set yet.
33
+ # This is called from the zenaton agent and will raise if called twice.
34
+ # @raise [ArgumentError] when the context was already set.
35
+ def add_context(**attributes)
36
+ message = <<~ERROR
37
+ Context has already been set and cannot be mutated.
38
+ ERROR
39
+ raise ArgumentError, message if @context
40
+
41
+ @context = Contexts::Task.new(attributes)
13
42
  end
14
43
  end
15
44
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'zenaton/interfaces/job'
4
+ require 'zenaton/contexts/workflow'
4
5
 
5
6
  module Zenaton
6
7
  module Interfaces
@@ -18,6 +19,20 @@ module Zenaton
18
19
  def id
19
20
  nil
20
21
  end
22
+
23
+ # @return [Zenaton::Contexts::Workflow] the workflow execution context
24
+ def context
25
+ @context || Contexts::Workflow.new
26
+ end
27
+
28
+ # @private
29
+ # Sets a new context if none has been set yet.
30
+ # This is called from the zenaton agent and will raise if called twice.
31
+ # @raise [ArgumentError] when the context was already set.
32
+ def add_context(**attributes)
33
+ raise ArgumentError if @context
34
+ @context = Contexts::Workflow.new(attributes)
35
+ end
21
36
  end
22
37
  end
23
38
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zenaton/refinements/big_decimal'
4
+ require 'zenaton/refinements/class'
5
+ require 'zenaton/refinements/complex'
6
+ require 'zenaton/refinements/date'
7
+ require 'zenaton/refinements/date_time'
8
+ require 'zenaton/refinements/exception'
9
+ require 'zenaton/refinements/object'
10
+ require 'zenaton/refinements/open_struct'
11
+ require 'zenaton/refinements/range'
12
+ require 'zenaton/refinements/rational'
13
+ require 'zenaton/refinements/regexp'
14
+ require 'zenaton/refinements/struct'
15
+ require 'zenaton/refinements/symbol'
16
+ require 'zenaton/refinements/time'
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/AndOr
4
+ defined?(::BigDecimal) or require 'bigdecimal'
5
+ # rubocop:enable Style/AndOr
6
+
7
+ module Zenaton
8
+ # :nodoc
9
+ module Refinements
10
+ refine BigDecimal do
11
+ def to_zenaton
12
+ {
13
+ 'b' => _dump
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # Reimplements `json/add/bigdecimal`
21
+ class BigDecimal
22
+ def self.from_zenaton(props)
23
+ BigDecimal._load props['b']
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ # :nodoc
5
+ module Refinements
6
+ refine Class do
7
+ def to_zenaton
8
+ {
9
+ 'n' => name
10
+ }
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ # Load an instance of class from zenaton properties
17
+ class Class
18
+ def self.from_zenaton(props)
19
+ Object.const_get(props['n'])
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ # :nodoc
5
+ module Refinements
6
+ refine Complex do
7
+ def to_zenaton
8
+ {
9
+ 'r' => real,
10
+ 'i' => imag
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ # Reimplements `json/add/complex`
18
+ class Complex
19
+ def self.from_zenaton(props)
20
+ Complex(props['r'], props['i'])
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Zenaton
6
+ # :nodoc
7
+ module Refinements
8
+ refine Date do
9
+ def to_zenaton
10
+ {
11
+ 'y' => year,
12
+ 'm' => month,
13
+ 'd' => day,
14
+ 'sg' => start
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ # Reimplements `json/add/date`
22
+ class Date
23
+ def self.from_zenaton(props)
24
+ civil(*props.values_at('y', 'm', 'd', 'sg'))
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Zenaton
6
+ # :nodoc
7
+ module Refinements
8
+ refine DateTime do
9
+ def to_zenaton
10
+ {
11
+ 'y' => year,
12
+ 'm' => month,
13
+ 'd' => day,
14
+ 'H' => hour,
15
+ 'M' => min,
16
+ 'S' => sec,
17
+ 'of' => offset.to_s,
18
+ 'sg' => start
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # Reimplements `json/add/date_time`
26
+ class DateTime
27
+ def self.from_zenaton(props)
28
+ args = props.values_at('y', 'm', 'd', 'H', 'M', 'S')
29
+ of_a, of_b = props['of'].split('/')
30
+ # rubocop:disable Style/ConditionalAssignment
31
+ if of_b && of_b != '0'
32
+ args << Rational(of_a.to_i, of_b.to_i)
33
+ else
34
+ args << of_a
35
+ end
36
+ # rubocop:enable Style/ConditionalAssignment
37
+ args << props['sg']
38
+ civil(*args)
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ # :nodoc
5
+ module Refinements
6
+ refine Exception do
7
+ def to_zenaton
8
+ {
9
+ 'm' => message,
10
+ 'b' => backtrace
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ # Reimplements `json/add/exception`
18
+ class Exception
19
+ def self.from_zenaton(props)
20
+ result = new(props['m'])
21
+ result.set_backtrace props['b']
22
+ result
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ # :nodoc
5
+ module Refinements
6
+ refine Object do
7
+ def to_zenaton
8
+ instance_variables.map do |ivar|
9
+ value = instance_variable_get(ivar)
10
+ [ivar, value]
11
+ end.to_h
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+
5
+ module Zenaton
6
+ # :nodoc
7
+ module Refinements
8
+ refine OpenStruct do
9
+ def to_zenaton
10
+ class_name = self.class.name.to_s
11
+ error_message = 'Only named structs are supported'
12
+ raise ArgumentError, error_message if class_name.empty?
13
+ {
14
+ 't' => table
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ # Reimplements `json/add/ostruct`
22
+ class OpenStruct
23
+ def self.from_zenaton(props)
24
+ new(props['t'] || props[:t])
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ # :nodoc
5
+ module Refinements
6
+ refine Range do
7
+ def to_zenaton
8
+ {
9
+ 'a' => [first, last, exclude_end?]
10
+ }
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ # Reimplements `json/add/range`
17
+ class Range
18
+ def self.from_zenaton(props)
19
+ new(*props['a'])
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ # :nodoc
5
+ module Refinements
6
+ refine Rational do
7
+ def to_zenaton
8
+ {
9
+ 'n' => numerator,
10
+ 'd' => denominator
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ # Reimplements `json/add/rational`
18
+ class Rational
19
+ def self.from_zenaton(props)
20
+ Rational(props['n'], props['d'])
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zenaton
4
+ # :nodoc
5
+ module Refinements
6
+ refine Regexp do
7
+ def to_zenaton
8
+ {
9
+ 'o' => options,
10
+ 's' => source
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ # Reimplements `json/add/regexp`
18
+ class Regexp
19
+ def self.from_zenaton(props)
20
+ new(props['s'], props['o'])
21
+ end
22
+ end