bas 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +68 -147
  4. data/lib/bas/bot/base.rb +74 -0
  5. data/lib/bas/bot/compare_wip_limit_count.rb +92 -0
  6. data/lib/bas/bot/fetch_birthdays_from_notion.rb +128 -0
  7. data/lib/bas/bot/fetch_domains_wip_counts_from_notion.rb +121 -0
  8. data/lib/bas/bot/fetch_domains_wip_limit_from_notion.rb +134 -0
  9. data/lib/bas/bot/fetch_emails_from_imap.rb +99 -0
  10. data/lib/bas/bot/fetch_next_week_birthdays_from_notion.rb +142 -0
  11. data/lib/bas/bot/fetch_next_week_ptos_from_notion.rb +162 -0
  12. data/lib/bas/bot/fetch_ptos_from_notion.rb +138 -0
  13. data/lib/bas/bot/format_birthdays.rb +97 -0
  14. data/lib/bas/bot/format_emails.rb +124 -0
  15. data/lib/bas/bot/format_wip_limit_exceeded.rb +97 -0
  16. data/lib/bas/bot/garbage_collector.rb +85 -0
  17. data/lib/bas/bot/humanize_pto.rb +119 -0
  18. data/lib/bas/bot/notify_discord.rb +96 -0
  19. data/lib/bas/read/base.rb +10 -23
  20. data/lib/bas/read/default.rb +16 -0
  21. data/lib/bas/read/postgres.rb +44 -0
  22. data/lib/bas/read/types/response.rb +18 -0
  23. data/lib/bas/utils/discord/integration.rb +43 -0
  24. data/lib/bas/utils/exceptions/function_not_implemented.rb +16 -0
  25. data/lib/bas/utils/exceptions/invalid_process_response.rb +16 -0
  26. data/lib/bas/utils/imap/request.rb +76 -0
  27. data/lib/bas/utils/notion/request.rb +45 -0
  28. data/lib/bas/utils/openai/run_assistant.rb +99 -0
  29. data/lib/bas/utils/postgres/request.rb +50 -0
  30. data/lib/bas/version.rb +1 -1
  31. data/lib/bas/write/base.rb +12 -17
  32. data/lib/bas/write/postgres.rb +45 -0
  33. data/lib/bas/write/postgres_update.rb +49 -0
  34. data/lib/bas.rb +1 -3
  35. metadata +30 -67
  36. data/lib/bas/domain/birthday.rb +0 -25
  37. data/lib/bas/domain/email.rb +0 -34
  38. data/lib/bas/domain/exceptions/function_not_implemented.rb +0 -18
  39. data/lib/bas/domain/issue.rb +0 -22
  40. data/lib/bas/domain/notification.rb +0 -23
  41. data/lib/bas/domain/pto.rb +0 -69
  42. data/lib/bas/domain/work_items_limit.rb +0 -25
  43. data/lib/bas/formatter/base.rb +0 -53
  44. data/lib/bas/formatter/birthday.rb +0 -38
  45. data/lib/bas/formatter/exceptions/invalid_data.rb +0 -15
  46. data/lib/bas/formatter/notification.rb +0 -34
  47. data/lib/bas/formatter/pto.rb +0 -89
  48. data/lib/bas/formatter/support_emails.rb +0 -73
  49. data/lib/bas/formatter/types/response.rb +0 -16
  50. data/lib/bas/formatter/work_items_limit.rb +0 -68
  51. data/lib/bas/process/base.rb +0 -39
  52. data/lib/bas/process/discord/exceptions/invalid_webhook_token.rb +0 -16
  53. data/lib/bas/process/discord/implementation.rb +0 -71
  54. data/lib/bas/process/discord/types/response.rb +0 -22
  55. data/lib/bas/process/openai/base.rb +0 -72
  56. data/lib/bas/process/openai/helper.rb +0 -19
  57. data/lib/bas/process/openai/types/response.rb +0 -27
  58. data/lib/bas/process/openai/use_case/humanize_pto.rb +0 -53
  59. data/lib/bas/process/slack/exceptions/invalid_webhook_token.rb +0 -16
  60. data/lib/bas/process/slack/implementation.rb +0 -70
  61. data/lib/bas/process/slack/types/response.rb +0 -21
  62. data/lib/bas/process/types/response.rb +0 -16
  63. data/lib/bas/read/github/base.rb +0 -57
  64. data/lib/bas/read/github/types/response.rb +0 -27
  65. data/lib/bas/read/github/use_case/repo_issues.rb +0 -17
  66. data/lib/bas/read/imap/base.rb +0 -70
  67. data/lib/bas/read/imap/types/response.rb +0 -27
  68. data/lib/bas/read/imap/use_case/support_emails.rb +0 -26
  69. data/lib/bas/read/notion/base.rb +0 -52
  70. data/lib/bas/read/notion/exceptions/invalid_api_key.rb +0 -15
  71. data/lib/bas/read/notion/exceptions/invalid_database_id.rb +0 -15
  72. data/lib/bas/read/notion/helper.rb +0 -21
  73. data/lib/bas/read/notion/types/response.rb +0 -26
  74. data/lib/bas/read/notion/use_case/birthday_next_week.rb +0 -41
  75. data/lib/bas/read/notion/use_case/birthday_today.rb +0 -29
  76. data/lib/bas/read/notion/use_case/notification.rb +0 -28
  77. data/lib/bas/read/notion/use_case/pto_next_week.rb +0 -71
  78. data/lib/bas/read/notion/use_case/pto_today.rb +0 -30
  79. data/lib/bas/read/notion/use_case/work_items_limit.rb +0 -37
  80. data/lib/bas/read/postgres/base.rb +0 -46
  81. data/lib/bas/read/postgres/helper.rb +0 -16
  82. data/lib/bas/read/postgres/types/response.rb +0 -42
  83. data/lib/bas/read/postgres/use_case/pto_today.rb +0 -32
  84. data/lib/bas/serialize/base.rb +0 -30
  85. data/lib/bas/serialize/github/issues.rb +0 -57
  86. data/lib/bas/serialize/imap/support_emails.rb +0 -56
  87. data/lib/bas/serialize/notion/birthday_today.rb +0 -68
  88. data/lib/bas/serialize/notion/notification.rb +0 -56
  89. data/lib/bas/serialize/notion/pto_today.rb +0 -75
  90. data/lib/bas/serialize/notion/work_items_limit.rb +0 -65
  91. data/lib/bas/serialize/postgres/pto_today.rb +0 -47
  92. data/lib/bas/use_cases/types/config.rb +0 -20
  93. data/lib/bas/use_cases/use_case.rb +0 -42
  94. data/lib/bas/use_cases/use_cases.rb +0 -465
  95. data/lib/bas/write/logs/base.rb +0 -33
  96. data/lib/bas/write/logs/use_case/console_log.rb +0 -22
  97. data/lib/bas/write/notion/base.rb +0 -36
  98. data/lib/bas/write/notion/use_case/empty_notification.rb +0 -38
  99. data/lib/bas/write/notion/use_case/notification.rb +0 -38
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/default"
5
+ require_relative "../utils/notion/request"
6
+ require_relative "../write/postgres"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::FetchDomainsWipCountsFromNotion class serves as a bot implementation to fetch work items
11
+ # in progress or in hold from a Notion database, count how many are by domain, and write them on a
12
+ # PostgresDB table with a specific format.
13
+ #
14
+ # <br>
15
+ # <b>Example</b>
16
+ #
17
+ # options = {
18
+ # process_options: {
19
+ # database_id: "notion database id",
20
+ # secret: "notion secret"
21
+ # },
22
+ # write_options: {
23
+ # connection: {
24
+ # host: "host",
25
+ # port: 5432,
26
+ # dbname: "bas",
27
+ # user: "postgres",
28
+ # password: "postgres"
29
+ # },
30
+ # db_table: "use_cases",
31
+ # tag: "FetchDomainsWipCountsFromNotion"
32
+ # }
33
+ # }
34
+ #
35
+ # bot = Bot::FetchDomainsWipCountsFromNotion.new(options)
36
+ # bot.execute
37
+ #
38
+ class FetchDomainsWipCountsFromNotion < Bot::Base
39
+ # Read function to execute the default Read component
40
+ #
41
+ def read
42
+ reader = Read::Default.new
43
+
44
+ reader.execute
45
+ end
46
+
47
+ # Process function to execute the Notion utility to fetch work item from the notion database
48
+ #
49
+ def process
50
+ response = Utils::Notion::Request.execute(params)
51
+
52
+ if response.code == 200
53
+ work_items_domains = normalize_response(response.parsed_response["results"])
54
+ domain_wip_count = count_domain_items(work_items_domains)
55
+
56
+ { success: { domain_wip_count: } }
57
+ else
58
+ { error: { message: response.parsed_response, status_code: response.code } }
59
+ end
60
+ end
61
+
62
+ # Write function to execute the PostgresDB write component
63
+ #
64
+ def write
65
+ write = Write::Postgres.new(write_options, process_response)
66
+
67
+ write.execute
68
+ end
69
+
70
+ private
71
+
72
+ def params
73
+ {
74
+ endpoint: "databases/#{process_options[:database_id]}/query",
75
+ secret: process_options[:secret],
76
+ method: "post",
77
+ body:
78
+ }
79
+ end
80
+
81
+ def body
82
+ {
83
+ filter: {
84
+ "and": [
85
+ { property: "OK", formula: { string: { contains: "✅" } } },
86
+ { "or": status_conditions }
87
+ ]
88
+ }
89
+ }
90
+ end
91
+
92
+ def status_conditions
93
+ [
94
+ { property: "Status", status: { equals: "In Progress" } },
95
+ { property: "Status", status: { equals: "On Hold" } }
96
+ ]
97
+ end
98
+
99
+ def normalize_response(results)
100
+ return [] if results.nil?
101
+
102
+ results.map do |pto|
103
+ work_item_fields = pto["properties"]
104
+
105
+ {
106
+ "domain" => extract_domain_field_value(work_item_fields["Responsible domain"])
107
+ }
108
+ end
109
+ end
110
+
111
+ def extract_domain_field_value(data)
112
+ data["select"]["name"]
113
+ end
114
+
115
+ def count_domain_items(work_items_list)
116
+ domain_work_items = work_items_list.group_by { |work_item| work_item["domain"] }
117
+
118
+ domain_work_items.transform_values(&:count)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../utils/notion/request"
6
+ require_relative "../write/postgres"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::FetchDomainsWipLimitFromNotion class serves as a bot implementation to fetch domains wip
11
+ # limits from a Notion database, merge them with the count of how many are by domain, and write them
12
+ # on a PostgresDB table with a specific format.
13
+ #
14
+ # <br>
15
+ # <b>Example</b>
16
+ #
17
+ # options = {
18
+ # read_options: {
19
+ # connection: {
20
+ # host: "host",
21
+ # port: 5432,
22
+ # dbname: "bas",
23
+ # user: "postgres",
24
+ # password: "postgres"
25
+ # },
26
+ # db_table: "use_cases",
27
+ # tag: "FetchDomainsWipCountsFromNotion"
28
+ # },
29
+ # process_options: {
30
+ # database_id: "notion database id",
31
+ # secret: "notion secret"
32
+ # },
33
+ # write_options: {
34
+ # connection: {
35
+ # host: "host",
36
+ # port: 5432,
37
+ # dbname: "bas",
38
+ # user: "postgres",
39
+ # password: "postgres"
40
+ # },
41
+ # db_table: "use_cases",
42
+ # tag: "FetchDomainsWipLimitFromNotion"
43
+ # }
44
+ # }
45
+ #
46
+ # bot = Bot::FetchDomainsWipLimitFromNotion.new(options)
47
+ # bot.execute
48
+ #
49
+ class FetchDomainsWipLimitFromNotion < Bot::Base
50
+ # read function to execute the PostgresDB Read component
51
+ #
52
+ def read
53
+ reader = Read::Postgres.new(read_options.merge(conditions))
54
+
55
+ reader.execute
56
+ end
57
+
58
+ # Process function to execute the Notion utility to fetch domain wip limits from the notion database
59
+ #
60
+ def process
61
+ response = Utils::Notion::Request.execute(params)
62
+
63
+ if response.code == 200
64
+ domains_limits = normalize_response(response.parsed_response["results"])
65
+
66
+ wip_limit_data = wip_count.merge({ domains_limits: })
67
+
68
+ { success: wip_limit_data }
69
+ else
70
+ { error: { message: response.parsed_response, status_code: response.code } }
71
+ end
72
+ end
73
+
74
+ # Write function to execute the PostgresDB write component
75
+ #
76
+ def write
77
+ write = Write::Postgres.new(write_options, process_response)
78
+
79
+ write.execute
80
+ end
81
+
82
+ private
83
+
84
+ def conditions
85
+ {
86
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
87
+ params: [false, read_options[:tag], "unprocessed"]
88
+ }
89
+ end
90
+
91
+ def params
92
+ {
93
+ endpoint: "databases/#{process_options[:database_id]}/query",
94
+ secret: process_options[:secret],
95
+ method: "post",
96
+ body:
97
+ }
98
+ end
99
+
100
+ def body
101
+ {
102
+ filter: {
103
+ property: "WIP + On Hold limit",
104
+ number: { is_not_empty: true }
105
+ }
106
+ }
107
+ end
108
+
109
+ def normalize_response(results)
110
+ return [] if results.nil?
111
+
112
+ results.reduce({}) do |domains_limits, domain_wip_limit|
113
+ domain_fields = domain_wip_limit["properties"]
114
+
115
+ domain = extract_domain_name_value(domain_fields["Name"])
116
+ limit = extract_domain_limit_value(domain_fields["WIP + On Hold limit"])
117
+
118
+ domains_limits.merge({ domain => limit })
119
+ end
120
+ end
121
+
122
+ def extract_domain_name_value(data)
123
+ data["title"].first["plain_text"]
124
+ end
125
+
126
+ def extract_domain_limit_value(data)
127
+ data["number"]
128
+ end
129
+
130
+ def wip_count
131
+ read_response.data.nil? ? {} : read_response.data
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/default"
5
+ require_relative "../utils/imap/request"
6
+ require_relative "../write/postgres"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::FetchEmailsFromImap class serves as a bot implementation to fetch emails from a imap server
11
+ # and write them on a PostgresDB table with a specific format.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # params = {
17
+ # process_options: {
18
+ # refresh_token: "email server refresh token",
19
+ # client_id: "email server client it",
20
+ # client_secret: "email server client secret",
21
+ # token_uri: "email server refresh token uri",
22
+ # email_domain: "email server domain",
23
+ # email_port: "email server port",
24
+ # user_email: "email to be access",
25
+ # search_email: "email to be search",
26
+ # inbox: "inbox to be search"
27
+ # },
28
+ # write_options: {
29
+ # connection:,
30
+ # db_table: "use_cases",
31
+ # tag: "FetchEmailsFromImap"
32
+ # }
33
+ # }
34
+ #
35
+ # bot = Bot::FetchEmailsFromImap.new(options)
36
+ # bot.execute
37
+ #
38
+ class FetchEmailsFromImap < Bot::Base
39
+ # Read function to execute the default Read component
40
+ #
41
+ def read
42
+ reader = Read::Default.new
43
+
44
+ reader.execute
45
+ end
46
+
47
+ # Process function to request email from an imap server using the imap utility
48
+ #
49
+ def process
50
+ response = Utils::Imap::Request.new(process_options, query).execute
51
+
52
+ if response[:error]
53
+ { error: response }
54
+ else
55
+ emails = normalize_response(response[:emails])
56
+
57
+ { success: { emails: } }
58
+ end
59
+ end
60
+
61
+ # Write function to execute the PostgresDB write component
62
+ #
63
+ def write
64
+ write = Write::Postgres.new(write_options, process_response)
65
+
66
+ write.execute
67
+ end
68
+
69
+ private
70
+
71
+ def query
72
+ yesterday = (Time.now - (60 * 60 * 24)).strftime("%d-%b-%Y")
73
+
74
+ ["TO", process_options[:search_email], "SINCE", yesterday]
75
+ end
76
+
77
+ def normalize_response(results)
78
+ return [] if results.nil?
79
+
80
+ results.map do |value|
81
+ message = value[:message]
82
+
83
+ {
84
+ "message_id": value[:message_id],
85
+ "sender" => extract_sender(message),
86
+ "date" => message.date,
87
+ "subject" => message.subject
88
+ }
89
+ end
90
+ end
91
+
92
+ def extract_sender(value)
93
+ mailbox = value.sender[0]["mailbox"]
94
+ host = value.sender[0]["host"]
95
+
96
+ "#{mailbox}@#{host}"
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../utils/notion/request"
6
+ require_relative "../write/postgres"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::FetchNextWeekBirthdaysFromNotion class serves as a bot implementation to read next
11
+ # week birthdays from a notion database and write them on a PostgresDB table with a specific format.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # options = {
17
+ # process_options: {
18
+ # database_id: "notion database id",
19
+ # secret: "notion secret"
20
+ # },
21
+ # write_options: {
22
+ # connection: {
23
+ # host: "host",
24
+ # port: 5432,
25
+ # dbname: "bas",
26
+ # user: "postgres",
27
+ # password: "postgres"
28
+ # },
29
+ # db_table: "birthdays",
30
+ # tag: "FetchNextWeekBirthdaysFromNotion"
31
+ # }
32
+ # }
33
+ #
34
+ # bot = Bot::FetchNextWeekBirthdaysFromNotion.new(options)
35
+ # bot.execute
36
+ #
37
+ class FetchNextWeekBirthdaysFromNotion < Bot::Base
38
+ DAYS_BEFORE = 7
39
+
40
+ # read function to execute the PostgresDB Read component
41
+ #
42
+ def read
43
+ reader = Read::Postgres.new(read_options.merge(conditions))
44
+
45
+ reader.execute
46
+ end
47
+
48
+ # Process function to execute the Notion utility to fetch PTO's from the notion database
49
+ #
50
+ def process
51
+ response = Utils::Notion::Request.execute(params)
52
+
53
+ if response.code == 200
54
+ birthdays_list = normalize_response(response.parsed_response["results"])
55
+
56
+ { success: { birthdays: birthdays_list } }
57
+ else
58
+ { error: { message: response.parsed_response, status_code: response.code } }
59
+ end
60
+ end
61
+
62
+ # Write function to execute the PostgresDB write component
63
+ #
64
+ def write
65
+ write = Write::Postgres.new(write_options, process_response)
66
+
67
+ write.execute
68
+ end
69
+
70
+ private
71
+
72
+ def conditions
73
+ {
74
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
75
+ params: [false, read_options[:tag], "unprocessed"]
76
+ }
77
+ end
78
+
79
+ def params
80
+ {
81
+ endpoint: "databases/#{process_options[:database_id]}/query",
82
+ secret: process_options[:secret],
83
+ method: "post",
84
+ body:
85
+ }
86
+ end
87
+
88
+ def body
89
+ {
90
+ filter: {
91
+ and: [{ property: "BD_this_year", date: { equals: n_days_from_now } }] + last_edited_condition
92
+ }
93
+ }
94
+ end
95
+
96
+ def last_edited_condition
97
+ return [] if read_response.inserted_at.nil?
98
+
99
+ [
100
+ {
101
+ timestamp: "last_edited_time",
102
+ last_edited_time: { on_or_after: read_response.inserted_at }
103
+ }
104
+ ]
105
+ end
106
+
107
+ def n_days_from_now
108
+ date = Time.now.utc + days_in_second(days_before)
109
+
110
+ date.utc.strftime("%F").to_s
111
+ end
112
+
113
+ def days_before
114
+ process_options[:days_before] || DAYS_BEFORE
115
+ end
116
+
117
+ def days_in_second(days)
118
+ days * 24 * 60 * 60
119
+ end
120
+
121
+ def normalize_response(results)
122
+ return [] if results.nil?
123
+
124
+ results.map do |value|
125
+ birthday_fields = value["properties"]
126
+
127
+ {
128
+ "name" => extract_rich_text_field_value(birthday_fields["Complete Name"]),
129
+ "birthday_date" => extract_date_field_value(birthday_fields["BD_this_year"])
130
+ }
131
+ end
132
+ end
133
+
134
+ def extract_rich_text_field_value(data)
135
+ data["rich_text"][0]["plain_text"]
136
+ end
137
+
138
+ def extract_date_field_value(data)
139
+ data["formula"]["date"]["start"]
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/default"
5
+ require_relative "../utils/notion/request"
6
+ require_relative "../write/postgres"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::FetchNextWeekPtosFromNotion class serves as a bot implementation to read next week
11
+ # PTO's from a notion database and write them on a PostgresDB table with a specific format.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # options = {
17
+ # process_options: {
18
+ # database_id: "notion database id",
19
+ # secret: "notion secret"
20
+ # },
21
+ # write_options: {
22
+ # connection: {
23
+ # host: "host",
24
+ # port: 5432,
25
+ # dbname: "bas",
26
+ # user: "postgres",
27
+ # password: "postgres"
28
+ # },
29
+ # db_table: "pto",
30
+ # tag: "FetchNextWeekPtosFromNotion"
31
+ # }
32
+ # }
33
+ #
34
+ # bot = Bot::FetchNextWeekPtosFromNotion.new(options)
35
+ # bot.execute
36
+ #
37
+ class FetchNextWeekPtosFromNotion < Bot::Base
38
+ # Read function to execute the default Read component
39
+ #
40
+ def read
41
+ reader = Read::Default.new
42
+
43
+ reader.execute
44
+ end
45
+
46
+ # Process function to execute the Notion utility to fetch next week PTO's from the notion database
47
+ #
48
+ def process
49
+ response = Utils::Notion::Request.execute(params)
50
+
51
+ if response.code == 200
52
+ ptos_list = normalize_response(response.parsed_response["results"])
53
+
54
+ { success: { ptos: ptos_list } }
55
+ else
56
+ { error: { message: response.parsed_response, status_code: response.code } }
57
+ end
58
+ end
59
+
60
+ # Write function to execute the PostgresDB write component
61
+ #
62
+ def write
63
+ write = Write::Postgres.new(write_options, process_response)
64
+
65
+ write.execute
66
+ end
67
+
68
+ private
69
+
70
+ def params
71
+ {
72
+ endpoint: "databases/#{process_options[:database_id]}/query",
73
+ secret: process_options[:secret],
74
+ method: "post",
75
+ body:
76
+ }
77
+ end
78
+
79
+ def body
80
+ monday, sunday = next_week_dates
81
+
82
+ {
83
+ filter: {
84
+ or: [
85
+ belong_next_week("StartDateTime", monday, sunday),
86
+ belong_next_week("EndDateTime", monday, sunday),
87
+ cover_next_week(monday, sunday)
88
+ ]
89
+ }
90
+ }
91
+ end
92
+
93
+ def next_week_dates
94
+ monday = next_week_monday
95
+ sunday = monday + 6
96
+
97
+ [monday, sunday]
98
+ end
99
+
100
+ def next_week_monday
101
+ today = Date.today
102
+ week_day = today.wday
103
+
104
+ days = week_day.zero? ? 1 : 8 - week_day
105
+
106
+ today + days
107
+ end
108
+
109
+ def belong_next_week(property, after_day, before_day)
110
+ {
111
+ and: [
112
+ { property:, date: { on_or_after: after_day } },
113
+ { property:, date: { on_or_before: before_day } }
114
+ ]
115
+ }
116
+ end
117
+
118
+ def cover_next_week(monday, sunday)
119
+ {
120
+ and: [
121
+ { property: "EndDateTime", date: { on_or_after: sunday } },
122
+ { property: "StartDateTime", date: { on_or_before: monday } }
123
+ ]
124
+ }
125
+ end
126
+
127
+ def normalize_response(results)
128
+ return [] if results.nil?
129
+
130
+ results.map do |pto|
131
+ pto_fields = pto["properties"]
132
+
133
+ {
134
+ "Name" => extract_description_field_value(pto_fields["Description"]),
135
+ "StartDateTime" => extract_date_field_value(pto_fields["StartDateTime"]),
136
+ "EndDateTime" => extract_date_field_value(pto_fields["EndDateTime"])
137
+ }
138
+ end
139
+ end
140
+
141
+ def extract_description_field_value(data)
142
+ names = data["title"].map { |name| name["plain_text"] }
143
+
144
+ names.join(" ")
145
+ end
146
+
147
+ def extract_date_field_value(date)
148
+ {
149
+ from: extract_start_date(date),
150
+ to: extract_end_date(date)
151
+ }
152
+ end
153
+
154
+ def extract_start_date(data)
155
+ data["date"]["start"]
156
+ end
157
+
158
+ def extract_end_date(data)
159
+ data["date"]["end"]
160
+ end
161
+ end
162
+ end