bas 0.1.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.
- checksums.yaml +7 -0
- data/._.rspec_status +0 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/CHANGELOG.md +4 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +66 -0
- data/Gemfile +24 -0
- data/LICENSE +21 -0
- data/README.md +362 -0
- data/Rakefile +12 -0
- data/SECURITY.md +13 -0
- data/lib/bas/dispatcher/base.rb +31 -0
- data/lib/bas/dispatcher/discord/exceptions/invalid_webhook_token.rb +16 -0
- data/lib/bas/dispatcher/discord/implementation.rb +51 -0
- data/lib/bas/dispatcher/discord/types/response.rb +22 -0
- data/lib/bas/dispatcher/slack/exceptions/invalid_webhook_token.rb +16 -0
- data/lib/bas/dispatcher/slack/implementation.rb +51 -0
- data/lib/bas/dispatcher/slack/types/response.rb +21 -0
- data/lib/bas/domain/birthday.rb +25 -0
- data/lib/bas/domain/email.rb +34 -0
- data/lib/bas/domain/exceptions/function_not_implemented.rb +18 -0
- data/lib/bas/domain/pto.rb +28 -0
- data/lib/bas/domain/work_items_limit.rb +25 -0
- data/lib/bas/fetcher/base.rb +43 -0
- data/lib/bas/fetcher/imap/base.rb +70 -0
- data/lib/bas/fetcher/imap/types/response.rb +27 -0
- data/lib/bas/fetcher/imap/use_case/support_emails.rb +26 -0
- data/lib/bas/fetcher/notion/base.rb +52 -0
- data/lib/bas/fetcher/notion/exceptions/invalid_api_key.rb +15 -0
- data/lib/bas/fetcher/notion/exceptions/invalid_database_id.rb +15 -0
- data/lib/bas/fetcher/notion/helper.rb +21 -0
- data/lib/bas/fetcher/notion/types/response.rb +26 -0
- data/lib/bas/fetcher/notion/use_case/birthday_next_week.rb +41 -0
- data/lib/bas/fetcher/notion/use_case/birthday_today.rb +29 -0
- data/lib/bas/fetcher/notion/use_case/pto_next_week.rb +71 -0
- data/lib/bas/fetcher/notion/use_case/pto_today.rb +30 -0
- data/lib/bas/fetcher/notion/use_case/work_items_limit.rb +37 -0
- data/lib/bas/fetcher/postgres/base.rb +46 -0
- data/lib/bas/fetcher/postgres/helper.rb +16 -0
- data/lib/bas/fetcher/postgres/types/response.rb +42 -0
- data/lib/bas/fetcher/postgres/use_case/pto_today.rb +32 -0
- data/lib/bas/formatter/base.rb +53 -0
- data/lib/bas/formatter/birthday.rb +34 -0
- data/lib/bas/formatter/exceptions/invalid_data.rb +15 -0
- data/lib/bas/formatter/pto.rb +88 -0
- data/lib/bas/formatter/support_emails.rb +69 -0
- data/lib/bas/formatter/work_items_limit.rb +64 -0
- data/lib/bas/mapper/base.rb +30 -0
- data/lib/bas/mapper/imap/support_emails.rb +56 -0
- data/lib/bas/mapper/notion/birthday_today.rb +68 -0
- data/lib/bas/mapper/notion/pto_today.rb +70 -0
- data/lib/bas/mapper/notion/work_items_limit.rb +65 -0
- data/lib/bas/mapper/postgres/pto_today.rb +47 -0
- data/lib/bas/use_cases/types/config.rb +19 -0
- data/lib/bas/use_cases/use_case.rb +39 -0
- data/lib/bas/use_cases/use_cases.rb +377 -0
- data/lib/bas/version.rb +6 -0
- data/lib/bas.rb +9 -0
- data/renovate.json +6 -0
- data/sig/business_automation_system.rbs +4 -0
- metadata +107 -0
@@ -0,0 +1,41 @@
|
|
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 fetching next week birthdays data from Notion.
|
10
|
+
#
|
11
|
+
class BirthdayNextWeek < Notion::Base
|
12
|
+
DAYS_BEFORE_NOTIFY = 8
|
13
|
+
|
14
|
+
# Implements the data fetching filter for next week Birthdays data from Notion.
|
15
|
+
#
|
16
|
+
def fetch
|
17
|
+
filter = {
|
18
|
+
filter: {
|
19
|
+
or: [
|
20
|
+
{ property: "BD_this_year", date: { equals: eight_days_from_now } }
|
21
|
+
]
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
execute(filter)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def eight_days_from_now
|
31
|
+
date = Time.now.utc + days_in_second(DAYS_BEFORE_NOTIFY)
|
32
|
+
|
33
|
+
date.utc.strftime("%F").to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def days_in_second(days)
|
37
|
+
days * 24 * 60 * 60
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
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 fetching birthday data from Notion.
|
10
|
+
#
|
11
|
+
class BirthdayToday < Notion::Base
|
12
|
+
# Implements the data fetching filter for todays Birthdays data from Notion.
|
13
|
+
#
|
14
|
+
def fetch
|
15
|
+
today = Time.now.utc.strftime("%F").to_s
|
16
|
+
|
17
|
+
filter = {
|
18
|
+
filter: {
|
19
|
+
or: [
|
20
|
+
{ property: "BD_this_year", date: { equals: today } }
|
21
|
+
]
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
execute(filter)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,71 @@
|
|
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 fetching next week Paid Time Off (PTO) data from Notion.
|
10
|
+
#
|
11
|
+
class PtoNextWeek < Notion::Base
|
12
|
+
# Implements the data fetching filter for next week PTO's data from Notion.
|
13
|
+
#
|
14
|
+
def fetch
|
15
|
+
filter = build_filter
|
16
|
+
|
17
|
+
execute(filter)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def next_week_dates
|
23
|
+
monday = next_week_monday
|
24
|
+
sunday = monday + 6
|
25
|
+
|
26
|
+
[monday, sunday]
|
27
|
+
end
|
28
|
+
|
29
|
+
def next_week_monday
|
30
|
+
today = Date.today
|
31
|
+
week_day = today.wday
|
32
|
+
|
33
|
+
days = week_day.zero? ? 1 : 8 - week_day
|
34
|
+
|
35
|
+
today + days
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_filter
|
39
|
+
monday, sunday = next_week_dates
|
40
|
+
|
41
|
+
{
|
42
|
+
filter: {
|
43
|
+
or: [
|
44
|
+
belong_next_week("Desde?", monday, sunday),
|
45
|
+
belong_next_week("Hasta?", monday, sunday),
|
46
|
+
cover_next_week(monday, sunday)
|
47
|
+
]
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def belong_next_week(property, after_day, before_day)
|
53
|
+
{
|
54
|
+
and: [
|
55
|
+
{ property: property, date: { on_or_after: after_day } },
|
56
|
+
{ property: property, date: { on_or_before: before_day } }
|
57
|
+
]
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def cover_next_week(monday, sunday)
|
62
|
+
{
|
63
|
+
and: [
|
64
|
+
{ property: "Hasta?", date: { on_or_after: sunday } },
|
65
|
+
{ property: "Desde?", date: { on_or_before: monday } }
|
66
|
+
]
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,30 @@
|
|
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 fetching Paid Time Off (PTO) data from Notion.
|
10
|
+
#
|
11
|
+
class PtoToday < Notion::Base
|
12
|
+
# Implements the data fetching filter for todays PTO's data from Notion.
|
13
|
+
#
|
14
|
+
def fetch
|
15
|
+
today = Time.now.utc.strftime("%F").to_s
|
16
|
+
|
17
|
+
filter = {
|
18
|
+
filter: {
|
19
|
+
"and": [
|
20
|
+
{ property: "Desde?", date: { on_or_before: today } },
|
21
|
+
{ property: "Hasta?", date: { on_or_after: today } }
|
22
|
+
]
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
execute(filter)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -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
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../domain/exceptions/function_not_implemented"
|
4
|
+
require "erb"
|
5
|
+
|
6
|
+
module Formatter
|
7
|
+
##
|
8
|
+
# The Formatter::Base module serves as the foundation for implementing specific data presentation logic
|
9
|
+
# within the Formatter module. Defines essential methods, that provide a blueprint for creating custom
|
10
|
+
# formatters tailored to different use cases.
|
11
|
+
#
|
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.
|
24
|
+
# Must be overridden by subclasses, with specific logic based on the use case.
|
25
|
+
#
|
26
|
+
# <br>
|
27
|
+
# <b>Params:</b>
|
28
|
+
# * <tt>List<Domain::></tt> domain_data: List of specific domain objects depending on the use case.
|
29
|
+
#
|
30
|
+
# <br>
|
31
|
+
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
|
32
|
+
#
|
33
|
+
# <b>returns</b> <tt>String</tt> Formatted payload suitable for a Dispatcher::Base implementation.
|
34
|
+
#
|
35
|
+
def format(_domain_data)
|
36
|
+
raise Domain::Exceptions::FunctionNotImplemented
|
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
|
52
|
+
end
|
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,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
require_relative "../domain/pto"
|
6
|
+
require_relative "./exceptions/invalid_data"
|
7
|
+
require_relative "./base"
|
8
|
+
|
9
|
+
module Formatter
|
10
|
+
##
|
11
|
+
# This class implements methods from the Formatter::Base module, tailored to format the
|
12
|
+
# Domain::Pto structure for a dispatcher.
|
13
|
+
class Pto < Base
|
14
|
+
DEFAULT_TIME_ZONE = "+00:00"
|
15
|
+
|
16
|
+
# Initializes the Slack formatter with essential configuration parameters.
|
17
|
+
#
|
18
|
+
# <b>timezone</b> : expect an string with the time difference relative to the UTC. Example: "-05:00"
|
19
|
+
def initialize(config = {})
|
20
|
+
super(config)
|
21
|
+
|
22
|
+
@timezone = config[:timezone] || DEFAULT_TIME_ZONE
|
23
|
+
end
|
24
|
+
|
25
|
+
# Implements the logic for building a formatted payload with the given template for PTO's.
|
26
|
+
#
|
27
|
+
# <br>
|
28
|
+
# <b>Params:</b>
|
29
|
+
# * <tt>List<Domain::Pto></tt> pto_list: List of mapped PTO's.
|
30
|
+
#
|
31
|
+
# <br>
|
32
|
+
# <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
|
33
|
+
#
|
34
|
+
# <br>
|
35
|
+
# <b>returns</b> <tt>String</tt> payload, formatted payload suitable for a Dispatcher.
|
36
|
+
#
|
37
|
+
|
38
|
+
def format(ptos_list)
|
39
|
+
raise Formatter::Exceptions::InvalidData unless ptos_list.all? { |pto| pto.is_a?(Domain::Pto) }
|
40
|
+
|
41
|
+
ptos_list.reduce("") do |payload, pto|
|
42
|
+
built_template = build_template(Domain::Pto::ATTRIBUTES, pto)
|
43
|
+
payload + format_message_by_case(built_template.gsub("\n", ""), pto)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def format_message_by_case(built_template, pto)
|
50
|
+
date_start = format_timezone(pto.start_date).strftime("%F")
|
51
|
+
date_end = format_timezone(pto.end_date).strftime("%F")
|
52
|
+
|
53
|
+
if date_start == date_end
|
54
|
+
interval = same_day_interval(pto)
|
55
|
+
day_message = today?(date_start) ? "today" : "the day #{date_start}"
|
56
|
+
|
57
|
+
"#{built_template} #{day_message} #{interval}\n"
|
58
|
+
else
|
59
|
+
"#{built_template} from #{date_start} to #{date_end}\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def same_day_interval(pto)
|
64
|
+
time_start = format_timezone(pto.start_date).strftime("%I:%M %P")
|
65
|
+
time_end = format_timezone(pto.end_date).strftime("%I:%M %P")
|
66
|
+
|
67
|
+
time_start == time_end ? "all day" : "from #{time_start} to #{time_end}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_timezone(date)
|
71
|
+
date_time = build_date(date)
|
72
|
+
|
73
|
+
Time.at(date_time, in: @timezone)
|
74
|
+
end
|
75
|
+
|
76
|
+
def today?(date)
|
77
|
+
time_now = Time.now.strftime("%F")
|
78
|
+
|
79
|
+
date == format_timezone(time_now).strftime("%F")
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_date(date)
|
83
|
+
date_time = date.include?("T") ? date : "#{date}T00:00:00.000#{@timezone}"
|
84
|
+
|
85
|
+
DateTime.parse(date_time).to_time
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../domain/email"
|
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::Email structure for a dispatcher.
|
11
|
+
class SupportEmails < Base
|
12
|
+
DEFAULT_TIME_ZONE = "+00:00"
|
13
|
+
|
14
|
+
# Initializes the formatter with essential configuration parameters.
|
15
|
+
#
|
16
|
+
# <b>timezone</b> : expect an string with the time difference relative to the UTC. Example: "-05:00"
|
17
|
+
def initialize(config = {})
|
18
|
+
super(config)
|
19
|
+
|
20
|
+
@timezone = config[:timezone] || DEFAULT_TIME_ZONE
|
21
|
+
@frecuency = config[:frecuency]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Implements the logic for building a formatted payload with the given template for support emails.
|
25
|
+
#
|
26
|
+
# <br>
|
27
|
+
# <b>Params:</b>
|
28
|
+
# * <tt>List<Domain::Email></tt> support_emails_list: list of support emails.
|
29
|
+
#
|
30
|
+
# <br>
|
31
|
+
# <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
|
32
|
+
#
|
33
|
+
# <br>
|
34
|
+
# <b>returns</b> <tt>String</tt> payload: formatted payload suitable for a Dispatcher.
|
35
|
+
#
|
36
|
+
def format(support_emails_list)
|
37
|
+
raise Formatter::Exceptions::InvalidData unless support_emails_list.all? do |support_email|
|
38
|
+
support_email.is_a?(Domain::Email)
|
39
|
+
end
|
40
|
+
|
41
|
+
process_emails(support_emails_list).reduce("") do |payload, support_email|
|
42
|
+
payload + build_template(Domain::Email::ATTRIBUTES, support_email)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def process_emails(emails)
|
49
|
+
emails.each { |email| email.date = at_timezone(email.date) }
|
50
|
+
emails.filter! { |email| email.date > time_window } unless @frecuency.nil?
|
51
|
+
|
52
|
+
format_timestamp(emails)
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_timestamp(emails)
|
56
|
+
emails.each { |email| email.date = email.date.strftime("%F %r") }
|
57
|
+
end
|
58
|
+
|
59
|
+
def time_window
|
60
|
+
date_time = Time.now - (60 * 60 * @frecuency)
|
61
|
+
|
62
|
+
at_timezone(date_time)
|
63
|
+
end
|
64
|
+
|
65
|
+
def at_timezone(date)
|
66
|
+
Time.at(date, in: @timezone)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,64 @@
|
|
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
|
+
DEFAULT_DOMAIN_LIMIT = 6
|
13
|
+
|
14
|
+
# Initializes the formatter with essential configuration parameters.
|
15
|
+
#
|
16
|
+
# <b>limits</b> : expect a map with the wip limits by domain. Example: { "ops": 5 }
|
17
|
+
def initialize(config = {})
|
18
|
+
super(config)
|
19
|
+
|
20
|
+
@limits = config[:limits]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Implements the logic for building a formatted payload with the given template for wip limits.
|
24
|
+
#
|
25
|
+
# <br>
|
26
|
+
# <b>Params:</b>
|
27
|
+
# * <tt>List<Domain::WorkItemsLimit></tt> work_items_list: List of mapped work items limits.
|
28
|
+
#
|
29
|
+
# <br>
|
30
|
+
# <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
|
31
|
+
#
|
32
|
+
# <br>
|
33
|
+
# <b>returns</b> <tt>String</tt> payload, formatted payload suitable for a Dispatcher.
|
34
|
+
#
|
35
|
+
|
36
|
+
def format(work_items_list)
|
37
|
+
raise Formatter::Exceptions::InvalidData unless work_items_list.all? do |work_item|
|
38
|
+
work_item.is_a?(Domain::WorkItemsLimit)
|
39
|
+
end
|
40
|
+
|
41
|
+
exceeded_domains(work_items_list).reduce("") do |payload, work_items_limit|
|
42
|
+
built_template = build_template(Domain::WorkItemsLimit::ATTRIBUTES, work_items_limit)
|
43
|
+
payload + format_message_by_case(built_template.gsub("\n", ""), work_items_limit)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def format_message_by_case(template, work_items_limit)
|
50
|
+
total_items = work_items_limit.total
|
51
|
+
limit = domain_limit(work_items_limit.domain)
|
52
|
+
|
53
|
+
template + ", #{total_items} of #{limit}\n"
|
54
|
+
end
|
55
|
+
|
56
|
+
def exceeded_domains(work_items_list)
|
57
|
+
work_items_list.filter { |work_item| work_item.total > domain_limit(work_item.domain) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def domain_limit(domain)
|
61
|
+
@limits[domain.to_sym] || DEFAULT_DOMAIN_LIMIT
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|