bns 0.1.0 → 0.2.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/._.rspec_status +0 -0
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile +2 -0
  5. data/README.md +163 -4
  6. data/SECURITY.md +13 -0
  7. data/lib/bns/dispatcher/discord/exceptions/invalid_webhook_token.rb +2 -2
  8. data/lib/bns/dispatcher/discord/implementation.rb +1 -1
  9. data/lib/bns/dispatcher/slack/exceptions/invalid_webhook_token.rb +16 -0
  10. data/lib/bns/dispatcher/slack/implementation.rb +51 -0
  11. data/lib/bns/dispatcher/slack/types/response.rb +21 -0
  12. data/lib/bns/domain/birthday.rb +2 -0
  13. data/lib/bns/domain/pto.rb +2 -0
  14. data/lib/bns/domain/work_items_limit.rb +39 -0
  15. data/lib/bns/fetcher/base.rb +13 -0
  16. data/lib/bns/fetcher/notion/{pto.rb → base.rb} +11 -7
  17. data/lib/bns/fetcher/notion/types/response.rb +1 -1
  18. data/lib/bns/fetcher/notion/use_case/birthday_next_week.rb +41 -0
  19. data/lib/bns/fetcher/notion/use_case/birthday_today.rb +29 -0
  20. data/lib/bns/fetcher/notion/use_case/pto_next_week.rb +71 -0
  21. data/lib/bns/fetcher/notion/use_case/pto_today.rb +30 -0
  22. data/lib/bns/fetcher/notion/use_case/work_items_limit.rb +37 -0
  23. data/lib/bns/fetcher/postgres/base.rb +46 -0
  24. data/lib/bns/fetcher/postgres/helper.rb +16 -0
  25. data/lib/bns/fetcher/postgres/types/response.rb +42 -0
  26. data/lib/bns/fetcher/postgres/use_case/pto_today.rb +32 -0
  27. data/lib/bns/formatter/base.rb +27 -3
  28. data/lib/bns/formatter/birthday.rb +34 -0
  29. data/lib/bns/formatter/exceptions/invalid_data.rb +15 -0
  30. data/lib/bns/formatter/pto.rb +76 -0
  31. data/lib/bns/formatter/work_items_limit.rb +43 -0
  32. data/lib/bns/mapper/notion/{birthday.rb → birthday_today.rb} +13 -21
  33. data/lib/bns/mapper/notion/{pto.rb → pto_today.rb} +15 -41
  34. data/lib/bns/mapper/notion/work_items_limit.rb +65 -0
  35. data/lib/bns/mapper/postgres/pto_today.rb +47 -0
  36. data/lib/bns/use_cases/use_cases.rb +227 -49
  37. data/lib/bns/version.rb +1 -1
  38. data/renovate.json +6 -0
  39. metadata +27 -10
  40. data/Gemfile.lock +0 -91
  41. data/lib/bns/fetcher/notion/birthday.rb +0 -53
  42. data/lib/bns/formatter/discord/birthday.rb +0 -43
  43. data/lib/bns/formatter/discord/exceptions/invalid_data.rb +0 -17
  44. data/lib/bns/formatter/discord/pto.rb +0 -52
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Fetcher
6
+ module Notion
7
+ ##
8
+ # This class is an implementation of the Fetcher::Notion::Base interface, specifically designed
9
+ # for counting "in progress" work items from work item database in Notion.
10
+ #
11
+ class WorkItemsLimit < Notion::Base
12
+ # Implements the data fetching count of "in progress" work items from Notion.
13
+ #
14
+ def fetch
15
+ filter = {
16
+ filter: {
17
+ "and": [
18
+ { property: "OK", formula: { string: { contains: "✅" } } },
19
+ { "or": status_conditions }
20
+ ]
21
+ }
22
+ }
23
+
24
+ execute(filter)
25
+ end
26
+
27
+ private
28
+
29
+ def status_conditions
30
+ [
31
+ { property: "Status", status: { equals: "In Progress" } },
32
+ { property: "Status", status: { equals: "On Hold" } }
33
+ ]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+
5
+ require_relative "../base"
6
+ require_relative "./types/response"
7
+ require_relative "./helper"
8
+
9
+ module Fetcher
10
+ module Postgres
11
+ ##
12
+ # This class is an implementation of the Fetcher::Base interface, specifically designed
13
+ # for fetching data from Postgres.
14
+ #
15
+ class Base < Fetcher::Base
16
+ protected
17
+
18
+ # Implements the data fetching logic from a Postgres database. It use the PG gem
19
+ # to request data from a local or external database and returns a validated response.
20
+ #
21
+ # Gem: pg (https://rubygems.org/gems/pg)
22
+ #
23
+ def execute(query)
24
+ pg_connection = PG::Connection.new(config[:connection])
25
+
26
+ pg_result = execute_query(pg_connection, query)
27
+
28
+ postgres_response = Fetcher::Postgres::Types::Response.new(pg_result)
29
+
30
+ Fetcher::Postgres::Helper.validate_response(postgres_response)
31
+ end
32
+
33
+ private
34
+
35
+ def execute_query(pg_connection, query)
36
+ if query.is_a? String
37
+ pg_connection.exec(query)
38
+ else
39
+ sentence, params = query
40
+
41
+ pg_connection.exec_params(sentence, params)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fetcher
4
+ module Postgres
5
+ ##
6
+ # Provides common fuctionalities along the Postgres domain.
7
+ #
8
+ module Helper
9
+ def self.validate_response(response)
10
+ response.response.check_result
11
+
12
+ response
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fetcher
4
+ module Postgres
5
+ module Types
6
+ ##
7
+ # Represents a response received from the Postgres API. It encapsulates essential information about the response,
8
+ # providing a structured way to handle and analyze it's responses.
9
+ class Response
10
+ attr_reader :status, :message, :response, :fields, :records
11
+
12
+ SUCCESS_STATUS = "PGRES_TUPLES_OK"
13
+
14
+ def initialize(response)
15
+ if response.res_status == SUCCESS_STATUS
16
+ success_response(response)
17
+ else
18
+ failure_response(response)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def success_response(response)
25
+ @status = response.res_status
26
+ @message = "success"
27
+ @response = response
28
+ @fields = response.fields
29
+ @records = response.values
30
+ end
31
+
32
+ def failure_response(response)
33
+ @status = response.res_status
34
+ @message = response.result_error_message
35
+ @response = response
36
+ @fields = nil
37
+ @records = nil
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Fetcher
6
+ module Postgres
7
+ ##
8
+ # This class is an implementation of the Fetcher::Postgres::Base interface, specifically designed
9
+ # for fetching Paid Time Off (PTO) data from a Postgres Database.
10
+ #
11
+ class PtoToday < Base
12
+ # Implements the data fetching query for todays PTO data from a Postgres database.
13
+ #
14
+ def fetch
15
+ execute(build_query)
16
+ end
17
+
18
+ private
19
+
20
+ def build_query
21
+ today = Time.now.utc.strftime("%F").to_s
22
+
23
+ start_time = "#{today}T00:00:00"
24
+ end_time = "#{today}T23:59:59"
25
+
26
+ where = "(start_date <= $1 AND end_date >= $1) OR (start_date>= $2 AND end_date <= $3)"
27
+
28
+ ["SELECT * FROM pto WHERE #{where}", [today, start_time, end_time]]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../domain/exceptions/function_not_implemented"
4
+ require "erb"
4
5
 
5
6
  module Formatter
6
7
  ##
@@ -8,9 +9,18 @@ module Formatter
8
9
  # within the Formatter module. Defines essential methods, that provide a blueprint for creating custom
9
10
  # formatters tailored to different use cases.
10
11
  #
11
- module Base
12
- # A method meant to give an specified format depending on the implementation to the data coming from an
13
- # implementation of the Mapper::Base interface.
12
+ class Base
13
+ attr_reader :template
14
+
15
+ # Initializes the fetcher with essential configuration parameters.
16
+ #
17
+ def initialize(config = {})
18
+ @config = config
19
+ @template = config[:template]
20
+ end
21
+
22
+ # This method is designed to provide a specified format for data from any implementation of
23
+ # the Mapper::Base interface.
14
24
  # Must be overridden by subclasses, with specific logic based on the use case.
15
25
  #
16
26
  # <br>
@@ -25,5 +35,19 @@ module Formatter
25
35
  def format(_domain_data)
26
36
  raise Domain::Exceptions::FunctionNotImplemented
27
37
  end
38
+
39
+ protected
40
+
41
+ def build_template(attributes, instance)
42
+ formated_template = format_template(attributes, instance)
43
+
44
+ "#{ERB.new(formated_template).result(binding)}\n"
45
+ end
46
+
47
+ def format_template(attributes, _instance)
48
+ attributes.reduce(template) do |formated_template, attribute|
49
+ formated_template.gsub(attribute, "<%= instance.#{attribute} %>")
50
+ end
51
+ end
28
52
  end
29
53
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../domain/birthday"
4
+ require_relative "./exceptions/invalid_data"
5
+ require_relative "./base"
6
+
7
+ module Formatter
8
+ ##
9
+ # This class implements methods from the Formatter::Base module, tailored to format the
10
+ # Domain::Birthday structure for a dispatcher.
11
+ class Birthday < Base
12
+ # Implements the logic for building a formatted payload with the given template for birthdays.
13
+ #
14
+ # <br>
15
+ # <b>Params:</b>
16
+ # * <tt>List<Domain::Birthday></tt> birthdays_list: list of mapped birthdays.
17
+ #
18
+ # <br>
19
+ # <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
20
+ #
21
+ # <br>
22
+ # <b>returns</b> <tt>String</tt> payload: formatted payload suitable for a Dispatcher.
23
+ #
24
+ def format(birthdays_list)
25
+ raise Formatter::Exceptions::InvalidData unless birthdays_list.all? do |brithday|
26
+ brithday.is_a?(Domain::Birthday)
27
+ end
28
+
29
+ birthdays_list.reduce("") do |payload, birthday|
30
+ payload + build_template(Domain::Birthday::ATTRIBUTES, birthday)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Exceptions
5
+ ##
6
+ # Provides a domain-specific representation for errors that occurs when trying to process invalid
7
+ # data on a Fetcher::Base implementation
8
+ #
9
+ class InvalidData < StandardError
10
+ def initialize(message = "")
11
+ super(message)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../domain/pto"
4
+ require_relative "./exceptions/invalid_data"
5
+ require_relative "./base"
6
+
7
+ module Formatter
8
+ ##
9
+ # This class implements methods from the Formatter::Base module, tailored to format the
10
+ # Domain::Pto structure for a dispatcher.
11
+ class Pto < Base
12
+ # Initializes the Slack formatter with essential configuration parameters.
13
+ #
14
+ # <b>timezone</b> : expect an string with the time difference relative to the UTC. Example: "-05:00"
15
+ def initialize(config = {})
16
+ super(config)
17
+
18
+ @timezone = config[:timezone]
19
+ end
20
+
21
+ # Implements the logic for building a formatted payload with the given template for PTO's.
22
+ #
23
+ # <br>
24
+ # <b>Params:</b>
25
+ # * <tt>List<Domain::Pto></tt> pto_list: List of mapped PTO's.
26
+ #
27
+ # <br>
28
+ # <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
29
+ #
30
+ # <br>
31
+ # <b>returns</b> <tt>String</tt> payload, formatted payload suitable for a Dispatcher.
32
+ #
33
+
34
+ def format(ptos_list)
35
+ raise Formatter::Exceptions::InvalidData unless ptos_list.all? { |pto| pto.is_a?(Domain::Pto) }
36
+
37
+ ptos_list.reduce("") do |payload, pto|
38
+ built_template = build_template(Domain::Pto::ATTRIBUTES, pto)
39
+ payload + format_message_by_case(built_template.gsub("\n", ""), pto)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def format_message_by_case(built_template, pto)
46
+ date_start = format_timezone(pto.start_date).strftime("%F")
47
+ date_end = format_timezone(pto.end_date).strftime("%F")
48
+
49
+ if date_start == date_end
50
+ interval = same_day_interval(pto)
51
+ day_message = today?(date_start) ? "today" : "the day #{date_start}"
52
+
53
+ "#{built_template} #{day_message} #{interval}\n"
54
+ else
55
+ "#{built_template} from #{date_start} to #{date_end}\n"
56
+ end
57
+ end
58
+
59
+ def same_day_interval(pto)
60
+ time_start = format_timezone(pto.start_date).strftime("%I:%M %P")
61
+ time_end = format_timezone(pto.end_date).strftime("%I:%M %P")
62
+
63
+ time_start == time_end ? "all day" : "from #{time_start} to #{time_end}"
64
+ end
65
+
66
+ def format_timezone(date)
67
+ @timezone.nil? ? Time.new(date) : Time.new(date, in: @timezone)
68
+ end
69
+
70
+ def today?(date)
71
+ time_now = Time.now.strftime("%F")
72
+
73
+ date == format_timezone(time_now).strftime("%F")
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../domain/work_items_limit"
4
+ require_relative "./exceptions/invalid_data"
5
+ require_relative "./base"
6
+
7
+ module Formatter
8
+ ##
9
+ # This class implements methods from the Formatter::Base module, tailored to format the
10
+ # Domain::WorkItemsLimit structure for a dispatcher.
11
+ class WorkItemsLimit < Base
12
+ attr_reader :limit
13
+
14
+ # Implements the logic for building a formatted payload with the given template for wip limits.
15
+ #
16
+ # <br>
17
+ # <b>Params:</b>
18
+ # * <tt>List<Domain::WorkItemsLimit></tt> work_items_list: List of mapped work items limits.
19
+ #
20
+ # <br>
21
+ # <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
22
+ #
23
+ # <br>
24
+ # <b>returns</b> <tt>String</tt> payload, formatted payload suitable for a Dispatcher.
25
+ #
26
+
27
+ def format(work_items_list)
28
+ raise Formatter::Exceptions::InvalidData unless work_items_list.all? do |work_item|
29
+ work_item.is_a?(Domain::WorkItemsLimit)
30
+ end
31
+
32
+ exceeded_domains(work_items_list).reduce("") do |payload, work_items_limit|
33
+ payload + build_template(Domain::WorkItemsLimit::ATTRIBUTES, work_items_limit)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def exceeded_domains(work_items_list)
40
+ work_items_list.filter { |work_item| work_item.total > work_item.wip_limit }
41
+ end
42
+ end
43
+ end
@@ -8,9 +8,11 @@ module Mapper
8
8
  ##
9
9
  # This class implementats the methods of the Mapper::Base module, specifically designed for preparing or
10
10
  # shaping birthdays data coming from a Fetcher::Base implementation.
11
- class Birthday
11
+ class BirthdayToday
12
12
  include Base
13
13
 
14
+ BIRTHDAY_PARAMS = ["Complete Name", "BD_this_year"].freeze
15
+
14
16
  # Implements the logic for shaping the results from a fetcher response.
15
17
  #
16
18
  # <br>
@@ -27,7 +29,7 @@ module Mapper
27
29
  normalized_notion_data = normalize_response(notion_response.results)
28
30
 
29
31
  normalized_notion_data.map do |birthday|
30
- Domain::Birthday.new(birthday["name"], birthday["birth_date"])
32
+ Domain::Birthday.new(birthday["Complete Name"], birthday["BD_this_year"])
31
33
  end
32
34
  end
33
35
 
@@ -36,32 +38,22 @@ module Mapper
36
38
  def normalize_response(results)
37
39
  return [] if results.nil?
38
40
 
39
- normalized_results = []
40
-
41
41
  results.map do |value|
42
- properties = value["properties"]
43
- properties.delete("Name")
42
+ birthday_fields = value["properties"].slice(*BIRTHDAY_PARAMS)
44
43
 
45
- normalized_value = normalize(properties)
44
+ birthday_fields.each do |field, birthday_value|
45
+ birthday_fields[field] = extract_birthday_value(field, birthday_value)
46
+ end
46
47
 
47
- normalized_results.append(normalized_value)
48
+ birthday_fields
48
49
  end
49
-
50
- normalized_results
51
50
  end
52
51
 
53
- def normalize(properties)
54
- normalized_value = {}
55
-
56
- properties.each do |k, v|
57
- if k == "Complete Name"
58
- normalized_value["name"] = extract_rich_text_field_value(v)
59
- elsif k == "BD_this_year"
60
- normalized_value["birth_date"] = extract_date_field_value(v)
61
- end
52
+ def extract_birthday_value(field, value)
53
+ case field
54
+ when "Complete Name" then extract_rich_text_field_value(value)
55
+ when "BD_this_year" then extract_date_field_value(value)
62
56
  end
63
-
64
- normalized_value
65
57
  end
66
58
 
67
59
  def extract_rich_text_field_value(data)
@@ -9,9 +9,11 @@ module Mapper
9
9
  # This class implementats the methods of the Mapper::Base module, specifically designed for preparing or
10
10
  # shaping PTO's data coming from a Fetcher::Base implementation.
11
11
  #
12
- class Pto
12
+ class PtoToday
13
13
  include Base
14
14
 
15
+ PTO_PARAMS = ["Person", "Desde?", "Hasta?"].freeze
16
+
15
17
  # Implements the logic for shaping the results from a fetcher response.
16
18
  #
17
19
  # <br>
@@ -26,8 +28,9 @@ module Mapper
26
28
  return [] if notion_response.results.empty?
27
29
 
28
30
  normalized_notion_data = normalize_response(notion_response.results)
31
+
29
32
  normalized_notion_data.map do |pto|
30
- Domain::Pto.new(pto["name"], format_date(pto["start"]), format_date(pto["end"]))
33
+ Domain::Pto.new(pto["Person"], pto["Desde?"], pto["Hasta?"])
31
34
  end
32
35
  end
33
36
 
@@ -36,39 +39,22 @@ module Mapper
36
39
  def normalize_response(response)
37
40
  return [] if response.nil?
38
41
 
39
- normalized_response = []
40
-
41
42
  response.map do |value|
42
- properties = value["properties"]
43
- properties.delete("Name")
43
+ pto_fields = value["properties"].slice(*PTO_PARAMS)
44
44
 
45
- normalized_value = normalize(properties)
45
+ pto_fields.each do |field, pto_value|
46
+ pto_fields[field] = extract_pto_value(field, pto_value)
47
+ end
46
48
 
47
- normalized_response.append(normalized_value)
49
+ pto_fields
48
50
  end
49
-
50
- normalized_response
51
51
  end
52
52
 
53
- def normalize(properties)
54
- normalized_value = {}
55
-
56
- properties.each do |k, v|
57
- extract_pto_fields(k, v, normalized_value)
58
- end
59
-
60
- normalized_value
61
- end
62
-
63
- def extract_pto_fields(key, value, normalized_value)
64
- case key
65
- when "Person"
66
- user_name = extract_person_field_value(value)
67
- normalized_value["name"] = user_name
68
- when "Desde?"
69
- normalized_value["start"] = extract_date_field_value(value)
70
- when "Hasta?"
71
- normalized_value["end"] = extract_date_field_value(value)
53
+ def extract_pto_value(field, value)
54
+ case field
55
+ when "Person" then extract_person_field_value(value)
56
+ when "Desde?" then extract_date_field_value(value)
57
+ when "Hasta?" then extract_date_field_value(value)
72
58
  end
73
59
  end
74
60
 
@@ -79,18 +65,6 @@ module Mapper
79
65
  def extract_date_field_value(data)
80
66
  data["date"]["start"]
81
67
  end
82
-
83
- def format_date(str_date)
84
- return "" if str_date.nil?
85
-
86
- if str_date.include?("T")
87
- format = "%Y-%m-%d|%I:%M %p"
88
- datetime = Time.new(str_date)
89
- datetime.strftime(format)
90
- else
91
- str_date
92
- end
93
- end
94
68
  end
95
69
  end
96
70
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../domain/work_items_limit"
4
+ require_relative "../base"
5
+
6
+ module Mapper
7
+ module Notion
8
+ ##
9
+ # This class implementats the methods of the Mapper::Base module, specifically designed
10
+ # for preparing or shaping work items data coming from a Fetcher::Base implementation.
11
+ class WorkItemsLimit
12
+ include Base
13
+
14
+ WORK_ITEM_PARAMS = ["Responsible domain"].freeze
15
+
16
+ # Implements the logic for shaping the results from a fetcher response.
17
+ #
18
+ # <br>
19
+ # <b>Params:</b>
20
+ # * <tt>Fetcher::Notion::Types::Response</tt> notion_response: Notion response object.
21
+ #
22
+ # <br>
23
+ # <b>return</b> <tt>List<Domain::WorkItem></tt> work_items_list, mapped work items to be used by a
24
+ # Formatter::Base implementation.
25
+ #
26
+ def map(notion_response)
27
+ return [] if notion_response.results.empty?
28
+
29
+ normalized_notion_data = normalize_response(notion_response.results)
30
+
31
+ domain_items_count = count_domain_items(normalized_notion_data)
32
+
33
+ domain_items_count.map do |domain, items_count|
34
+ Domain::WorkItemsLimit.new(domain, items_count)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def normalize_response(results)
41
+ return [] if results.nil?
42
+
43
+ results.map do |value|
44
+ work_item_fields = value["properties"].slice(*WORK_ITEM_PARAMS)
45
+
46
+ work_item_fields.each do |field, work_item_value|
47
+ work_item_fields[field] = extract_domain_field_value(work_item_value)
48
+ end
49
+
50
+ work_item_fields
51
+ end
52
+ end
53
+
54
+ def extract_domain_field_value(data)
55
+ data["select"]["name"]
56
+ end
57
+
58
+ def count_domain_items(work_items_list)
59
+ domain_work_items = work_items_list.group_by { |work_item| work_item["Responsible domain"] }
60
+
61
+ domain_work_items.transform_values(&:count)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../domain/pto"
4
+ require_relative "../base"
5
+
6
+ module Mapper
7
+ module Postgres
8
+ ##
9
+ # This class implementats the methods of the Mapper::Base module, specifically designed for preparing or
10
+ # shaping PTO's data coming from the Fetcher::Postgres::Pto class.
11
+ #
12
+ class PtoToday
13
+ # Implements the logic for shaping the results from a fetcher response.
14
+ #
15
+ # <br>
16
+ # <b>Params:</b>
17
+ # * <tt>Fetcher::Postgres::Types::Response</tt> pg_response: Postgres response object.
18
+ #
19
+ # <br>
20
+ # <b>returns</b> <tt>List<Domain::Pto></tt> ptos_list, mapped PTO's to be used by a Formatter::Base
21
+ # implementation.
22
+ #
23
+ def map(pg_response)
24
+ return [] if pg_response.records.empty?
25
+
26
+ ptos = build_map(pg_response)
27
+
28
+ ptos.map do |pto|
29
+ name = pto["name"]
30
+ start_date = pto["start_date"]
31
+ end_date = pto["end_date"]
32
+
33
+ Domain::Pto.new(name, start_date, end_date)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def build_map(pg_response)
40
+ fields = pg_response.fields
41
+ values = pg_response.records
42
+
43
+ values.map { |value| Hash[fields.zip(value)] }
44
+ end
45
+ end
46
+ end
47
+ end