bas 0.4.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +68 -147
- data/lib/bas/bot/base.rb +74 -0
- data/lib/bas/bot/compare_wip_limit_count.rb +92 -0
- data/lib/bas/bot/fetch_birthdays_from_notion.rb +128 -0
- data/lib/bas/bot/fetch_domains_wip_counts_from_notion.rb +121 -0
- data/lib/bas/bot/fetch_domains_wip_limit_from_notion.rb +134 -0
- data/lib/bas/bot/fetch_emails_from_imap.rb +99 -0
- data/lib/bas/bot/fetch_next_week_birthdays_from_notion.rb +142 -0
- data/lib/bas/bot/fetch_next_week_ptos_from_notion.rb +162 -0
- data/lib/bas/bot/fetch_ptos_from_notion.rb +138 -0
- data/lib/bas/bot/format_birthdays.rb +97 -0
- data/lib/bas/bot/format_emails.rb +124 -0
- data/lib/bas/bot/format_wip_limit_exceeded.rb +97 -0
- data/lib/bas/bot/garbage_collector.rb +85 -0
- data/lib/bas/bot/humanize_pto.rb +119 -0
- data/lib/bas/bot/notify_discord.rb +96 -0
- data/lib/bas/read/base.rb +10 -23
- data/lib/bas/read/default.rb +16 -0
- data/lib/bas/read/postgres.rb +44 -0
- data/lib/bas/read/types/response.rb +18 -0
- data/lib/bas/utils/discord/integration.rb +43 -0
- data/lib/bas/utils/exceptions/function_not_implemented.rb +16 -0
- data/lib/bas/utils/exceptions/invalid_process_response.rb +16 -0
- data/lib/bas/utils/imap/request.rb +76 -0
- data/lib/bas/utils/notion/request.rb +45 -0
- data/lib/bas/utils/openai/run_assistant.rb +99 -0
- data/lib/bas/utils/postgres/request.rb +50 -0
- data/lib/bas/version.rb +1 -1
- data/lib/bas/write/base.rb +12 -17
- data/lib/bas/write/postgres.rb +45 -0
- data/lib/bas/write/postgres_update.rb +49 -0
- data/lib/bas.rb +1 -3
- metadata +30 -67
- data/lib/bas/domain/birthday.rb +0 -25
- data/lib/bas/domain/email.rb +0 -34
- data/lib/bas/domain/exceptions/function_not_implemented.rb +0 -18
- data/lib/bas/domain/issue.rb +0 -22
- data/lib/bas/domain/notification.rb +0 -23
- data/lib/bas/domain/pto.rb +0 -69
- data/lib/bas/domain/work_items_limit.rb +0 -25
- data/lib/bas/formatter/base.rb +0 -53
- data/lib/bas/formatter/birthday.rb +0 -38
- data/lib/bas/formatter/exceptions/invalid_data.rb +0 -15
- data/lib/bas/formatter/notification.rb +0 -34
- data/lib/bas/formatter/pto.rb +0 -89
- data/lib/bas/formatter/support_emails.rb +0 -73
- data/lib/bas/formatter/types/response.rb +0 -16
- data/lib/bas/formatter/work_items_limit.rb +0 -68
- data/lib/bas/process/base.rb +0 -39
- data/lib/bas/process/discord/exceptions/invalid_webhook_token.rb +0 -16
- data/lib/bas/process/discord/implementation.rb +0 -71
- data/lib/bas/process/discord/types/response.rb +0 -22
- data/lib/bas/process/openai/base.rb +0 -72
- data/lib/bas/process/openai/helper.rb +0 -19
- data/lib/bas/process/openai/types/response.rb +0 -27
- data/lib/bas/process/openai/use_case/humanize_pto.rb +0 -53
- data/lib/bas/process/slack/exceptions/invalid_webhook_token.rb +0 -16
- data/lib/bas/process/slack/implementation.rb +0 -70
- data/lib/bas/process/slack/types/response.rb +0 -21
- data/lib/bas/process/types/response.rb +0 -16
- data/lib/bas/read/github/base.rb +0 -57
- data/lib/bas/read/github/types/response.rb +0 -27
- data/lib/bas/read/github/use_case/repo_issues.rb +0 -17
- data/lib/bas/read/imap/base.rb +0 -70
- data/lib/bas/read/imap/types/response.rb +0 -27
- data/lib/bas/read/imap/use_case/support_emails.rb +0 -26
- data/lib/bas/read/notion/base.rb +0 -52
- data/lib/bas/read/notion/exceptions/invalid_api_key.rb +0 -15
- data/lib/bas/read/notion/exceptions/invalid_database_id.rb +0 -15
- data/lib/bas/read/notion/helper.rb +0 -21
- data/lib/bas/read/notion/types/response.rb +0 -26
- data/lib/bas/read/notion/use_case/birthday_next_week.rb +0 -41
- data/lib/bas/read/notion/use_case/birthday_today.rb +0 -29
- data/lib/bas/read/notion/use_case/notification.rb +0 -28
- data/lib/bas/read/notion/use_case/pto_next_week.rb +0 -71
- data/lib/bas/read/notion/use_case/pto_today.rb +0 -30
- data/lib/bas/read/notion/use_case/work_items_limit.rb +0 -37
- data/lib/bas/read/postgres/base.rb +0 -46
- data/lib/bas/read/postgres/helper.rb +0 -16
- data/lib/bas/read/postgres/types/response.rb +0 -42
- data/lib/bas/read/postgres/use_case/pto_today.rb +0 -32
- data/lib/bas/serialize/base.rb +0 -30
- data/lib/bas/serialize/github/issues.rb +0 -57
- data/lib/bas/serialize/imap/support_emails.rb +0 -56
- data/lib/bas/serialize/notion/birthday_today.rb +0 -68
- data/lib/bas/serialize/notion/notification.rb +0 -56
- data/lib/bas/serialize/notion/pto_today.rb +0 -75
- data/lib/bas/serialize/notion/work_items_limit.rb +0 -65
- data/lib/bas/serialize/postgres/pto_today.rb +0 -47
- data/lib/bas/use_cases/types/config.rb +0 -20
- data/lib/bas/use_cases/use_case.rb +0 -42
- data/lib/bas/use_cases/use_cases.rb +0 -465
- data/lib/bas/write/logs/base.rb +0 -33
- data/lib/bas/write/logs/use_case/console_log.rb +0 -22
- data/lib/bas/write/notion/base.rb +0 -36
- data/lib/bas/write/notion/use_case/empty_notification.rb +0 -38
- data/lib/bas/write/notion/use_case/notification.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b15ef30bfbc91578e5b2ef5bf19861f9bda796d0296faa65ae80cd2244521655
|
4
|
+
data.tar.gz: 34775b96d78ee72c66a1d7501020d810030bbf6fc0396ec12e0aa39ed58d3735
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f74a4e1963f29cd2f7cc5d5847d4215cd403281394711570d6c612771ed7036ada74941cd449f1c8c1faf82b7d6f33bb0941ce81b4c39884a240404637e6d6d
|
7
|
+
data.tar.gz: 82c7867b2b3af0a16f3ca4780c796bff3ed3ae3e27d5b6e31d91731d2dea7bc367ebd4ac9ad39e54b98c56ef3f686e9bdf5411e54c8dd7fe6359356ca7809e48
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
# 1.0.0 (17.05.2024)
|
4
|
+
- [Refactor Project Architecture](https://github.com/kommitters/bas/issues/35)
|
5
|
+
- [Refactor PTO today Use Case](https://github.com/kommitters/bas/issues/29)
|
6
|
+
- [Refactor PTO next week Use Case](https://github.com/kommitters/bas/issues/30)
|
7
|
+
- [Refactor Birthday today Use Case](https://github.com/kommitters/bas/issues/31)
|
8
|
+
- [Refactor Birthday next week Use Case](https://github.com/kommitters/bas/issues/32)
|
9
|
+
- [Refactor WIP limit Use Case](https://github.com/kommitters/bas/issues/33)
|
10
|
+
- [Refactor support email Use Case](https://github.com/kommitters/bas/issues/34)
|
11
|
+
- [Add a garbage collector](https://github.com/kommitters/bas/issues/42)
|
12
|
+
- [Update read Postgres component](https://github.com/kommitters/bas/issues/45)
|
13
|
+
- [Update new table structure](https://github.com/kommitters/bas/issues/46)
|
14
|
+
|
3
15
|
# 0.4.0 (19.04.2024)
|
4
16
|
- [[BAS] Add process for OpenAI](https://github.com/kommitters/bas/issues/25)
|
5
17
|
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# BAS - Business Automation System
|
2
2
|
|
3
|
-
Many organizations and individuals rely on automation across various contexts in their daily operations. With BAS, we aim to provide an open-source platform that empowers users to create customized automation systems tailored to their unique requirements. BAS consists of a series of abstract components designed to facilitate the creation of diverse
|
3
|
+
Many organizations and individuals rely on automation across various contexts in their daily operations. With BAS, we aim to provide an open-source platform that empowers users to create customized automation systems tailored to their unique requirements. BAS consists of a series of abstract components designed to facilitate the creation of diverse bots, regardless of context.
|
4
4
|
|
5
|
-
The underlying idea is to develop generic components that can serve a wide range of needs, this approach ensures that all members of the community can leverage the platform's evolving suite of components and
|
5
|
+
The underlying idea is to develop generic components that can serve a wide range of needs, this approach ensures that all members of the community can leverage the platform's evolving suite of components and bots to their advantage.
|
6
6
|
|
7
7
|

|
8
8
|

|
@@ -26,59 +26,38 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
26
26
|
|
27
27
|
* Ruby 2.6.0 or higher
|
28
28
|
|
29
|
-
##
|
29
|
+
## BOT
|
30
|
+
A bot is a tool in charge of executing a specific automation task. The pipeline of a bot consists of reading from a data source, processing a specific task, and then writing a result in storage.
|
30
31
|
|
31
|
-
|
32
|
+
## Use case
|
33
|
+
A use case is an automation problem greater than the one managed by a single bot. A use case comprises a set of bots that are agnostic between them and each one solves a specific task (parts of the automation problem). To connect the bots, a shared storage should be used. This shared storage could be a PostgresDB database, an S3 bucket, or any kind of storage where the bots will read and write data so another bot can use it to execute their tasks.
|
32
34
|
|
33
|
-
|
34
|
-
* Birthday notifications - from Notion to Discord
|
35
|
-
* Next Week Birthday notifications - from Notion to Discord
|
36
|
-
* PTO notifications - from Notion to Discord
|
37
|
-
* Next Week PTO notifications - from Notion to Discord
|
38
|
-
* PTO notifications - from Postgres to Slack
|
39
|
-
* WIP limit exceeded - from Notion to Discord
|
40
|
-
* Support email notification - from IMAP to Discord
|
35
|
+
For example, a system to notify birthdays in a company (automation problem) can be solved with three bots: one to fetch the data from an external data source, one to format the birthday message, and the last one to notify somewhere.
|
41
36
|
|
42
|
-
|
43
|
-
notifications to a Discord channel.
|
37
|
+
## Building my own BOT
|
44
38
|
|
45
|
-
|
39
|
+
The gem provides with basic interfaces, types, and methods to shape your own bot in an easy way. Since the process of reading and writing in a shared storage is separated from the main task, two base classes were defined to deal with this executions, leaving the logic of the specific task in the bot file.
|
46
40
|
|
47
|
-
### 1. Read - Obtaining the data
|
41
|
+
### 1. Read - Obtaining the data from the Shared Storage
|
48
42
|
|
49
|
-
Specifically, a reader is an object in charged of bringing data from a
|
50
|
-
for building your own reader for your specific
|
43
|
+
Specifically, a reader is an object in charged of bringing data from a shared storage. The gem already provides the base interface
|
44
|
+
for building your own reader for your specific shared storage, or rely on already built classes if they match your purpose.
|
51
45
|
|
52
|
-
The base interface for a reader can be found under the `bas/read/base.rb` class.
|
53
|
-
for bringing data from a Notion database, it was created on a new namespace for that data source, it can be found under
|
54
|
-
`/bas/read/notion/use_case/birthday_today.rb`. It implements specific logic for reading the data and validating the response.
|
46
|
+
The base interface for a reader can be found under the `bas/read/base.rb` class.
|
55
47
|
|
56
|
-
### 2.
|
48
|
+
### 2. Write - Apply changes in a shared storage
|
49
|
+
The **Write** is in charge of creating or updating information in a shared storage. This is the last step for every BOT. These changes can be a transaction in a database, adding files in a cloud storage, or simply creating logs.
|
57
50
|
|
58
|
-
The
|
59
|
-
common structure understandable for other components, specifically the **Formatter**.
|
51
|
+
The base interface for a writer can be found under the `bas/write/base.rb` class.
|
60
52
|
|
61
|
-
|
62
|
-
|
53
|
+
### 3. Bot - Solve a specific automation task
|
54
|
+
The bot execute the logic to solve an specific task. For this, it can use the data from the read step, and then returns a processed response to be wrote by the write component. Every bot reads from a shared storage and writes in a shared storage.
|
63
55
|
|
64
|
-
|
65
|
-
|
66
|
-
The **Formatter**, is in charge of preparing the message to be sent in our notification, and give it the right format with the right data.
|
67
|
-
The template or 'format' to be used should be included in the use case configurations, which we'll review in a further step. It's
|
68
|
-
implementation can be found under `/bas/formatter/birthday.rb`.
|
69
|
-
|
70
|
-
### 4. Process - Optional Data Process
|
71
|
-
|
72
|
-
Finally, the **Process** basically, allow required data process depending on the use case like sending formatted messages into a destination. In this case, since the use case was implemented for
|
73
|
-
Discord, it implements specific logic to communicate with a Discord channel using a webhook. The webhook configuration and name for the 'Sender'
|
74
|
-
in the channel should be provided with the initial use case configurations. It can be found under `/bas/process/discord/implementation.rb`
|
75
|
-
|
76
|
-
### 5. Write - Apply changes in a destination
|
77
|
-
Finally, the **Write** is in charge of creating or updating information in a destination. This is the last step in the pipeline, and it aims to allow the users to apply changes in a destination depending on the use case. These changes can be a transaction in a database, adding files in a cloud storage, or simply creating logs. For the Birthday use case, after the Process sends the notification, the Write creates a Log in the `$stdout`. It can be found under `lib/bas/write/logs/use_case/console_log.rb`
|
56
|
+
The base interface for a bot can be found under the `bas/bot/base.rb` class.
|
78
57
|
|
79
58
|
## Examples
|
80
59
|
|
81
|
-
In this example, we demonstrate how to instantiate a birthday notification
|
60
|
+
In this example, we demonstrate how to instantiate a birthday notification bot and execute it in a basic Ruby project. We'll also cover its deployment in a serverless configuration, specifically using a simple Lambda deployment.
|
82
61
|
|
83
62
|
### Preparing the configurations
|
84
63
|
|
@@ -94,74 +73,37 @@ With the following formula for the **BD_this_year** column: `dateAdd(prop("BD"),
|
|
94
73
|
|
95
74
|
* A Notion secret, which can be obtained, by creating an integration here: `https://developers.notion.com/`, browsing on the **View my integrations** option, and selecting the **New Integration** or **Create new integration** buttons.
|
96
75
|
|
97
|
-
|
98
|
-
|
99
|
-
* A filter, to determine which data to bring from the database, for this specific case, the filter we used is:
|
100
|
-
|
101
|
-
```
|
102
|
-
#### file.rb ####
|
103
|
-
today = Date.now
|
104
|
-
{
|
105
|
-
"filter": {
|
106
|
-
"or": [
|
107
|
-
{
|
108
|
-
"property": "BD_this_year",
|
109
|
-
"date": {
|
110
|
-
"equals": today
|
111
|
-
}
|
112
|
-
}
|
113
|
-
]
|
114
|
-
},
|
115
|
-
"sorts": []
|
116
|
-
}
|
117
|
-
```
|
118
|
-
|
119
|
-
* A template for the formatter to be used for formatting the payload to be send to Discord. For this specific case, the format of the messages should be:
|
120
|
-
|
121
|
-
`"NAME, Wishing you a very happy birthday! Enjoy your special day! :birthday: :gift:"`
|
122
|
-
|
123
|
-
### Instantiating the Use Case
|
76
|
+
### Instantiating the FetchBirthdayFromNotion Bot
|
124
77
|
|
125
|
-
|
126
|
-
is the desired one.
|
78
|
+
The specific bot can be found in the `bas/bot/fetch_birthdays_from_notion.rb` file.
|
127
79
|
|
128
80
|
**Normal ruby code**
|
129
81
|
```
|
130
|
-
filter = {
|
131
|
-
"filter": {
|
132
|
-
"or": [
|
133
|
-
{
|
134
|
-
"property": "BD_this_year",
|
135
|
-
"date": {
|
136
|
-
"equals": today
|
137
|
-
}
|
138
|
-
}
|
139
|
-
]
|
140
|
-
},
|
141
|
-
"sorts": []
|
142
|
-
}
|
143
|
-
|
144
82
|
options = {
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
83
|
+
process_options: {
|
84
|
+
database_id: "notion database id",
|
85
|
+
secret: "notion secret"
|
86
|
+
},
|
87
|
+
write_options: {
|
88
|
+
connection: {
|
89
|
+
host: "host",
|
90
|
+
port: 5432,
|
91
|
+
dbname: "bas",
|
92
|
+
user: "postgres",
|
93
|
+
password: "postgres"
|
150
94
|
},
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
}
|
95
|
+
db_table: "use_cases",
|
96
|
+
bot_name: "FetchBirthdaysFromNotion"
|
97
|
+
}
|
155
98
|
}
|
156
99
|
|
157
|
-
|
158
|
-
|
159
|
-
use_case.perform
|
100
|
+
bot = Bot::FetchBirthdaysFromNotion.new(options)
|
101
|
+
bot.execute
|
160
102
|
|
161
103
|
```
|
162
104
|
|
163
105
|
### Serverless
|
164
|
-
We'll explain how to configure and deploy a
|
106
|
+
We'll explain how to configure and deploy a bot with serverless.
|
165
107
|
|
166
108
|
#### Configuring environment variables
|
167
109
|
Create the environment variables configuration file.
|
@@ -173,15 +115,11 @@ cp env.yml.example env.yml
|
|
173
115
|
And put the following env variables
|
174
116
|
```
|
175
117
|
dev:
|
176
|
-
|
177
|
-
|
178
|
-
DISCORD_WEBHOOK: DISCORD_WEBHOOK
|
179
|
-
DISCORD_BOT_NAME: DISCORD_BOT_NAME
|
118
|
+
BIRTHDAY_NOTION_DATABASE_ID: "BIRTHDAY_NOTION_DATABASE_ID"
|
119
|
+
BIRTHDAY_NOTION_SECRET: "BIRTHDAY_NOTION_SECRET"
|
180
120
|
prod:
|
181
|
-
|
182
|
-
|
183
|
-
DISCORD_WEBHOOK: DISCORD_WEBHOOK
|
184
|
-
DISCORD_BOT_NAME: DISCORD_BOT_NAME
|
121
|
+
BIRTHDAY_NOTION_DATABASE_ID: "BIRTHDAY_NOTION_DATABASE_ID"
|
122
|
+
BIRTHDAY_NOTION_SECRET: "BIRTHDAY_NOTION_SECRET"
|
185
123
|
|
186
124
|
```
|
187
125
|
|
@@ -225,60 +163,45 @@ On your serverless configuration, create your lambda function, on your serverles
|
|
225
163
|
```ruby
|
226
164
|
# frozen_string_literal: true
|
227
165
|
|
228
|
-
require 'bas'
|
166
|
+
require 'bas/bot/fetch_birthdays_from_notion'
|
229
167
|
|
230
168
|
# Initialize the environment variables
|
231
|
-
|
232
|
-
|
233
|
-
NOTION_SECRET = ENV.fetch('PTO_NOTION_SECRET')
|
234
|
-
DISCORD_WEBHOOK = ENV.fetch('PTO_DISCORD_WEBHOOK')
|
235
|
-
DISCORD_BOT_NAME = ENV.fetch('PTO_DISCORD_BOT_NAME')
|
169
|
+
NOTION_DATABASE_ID = ENV.fetch('NOTION_DATABASE_ID')
|
170
|
+
NOTION_SECRET = ENV.fetch('NOTION_SECRET')
|
236
171
|
|
237
172
|
module Notifier
|
238
173
|
# Service description
|
239
174
|
class UseCaseName
|
240
175
|
def self.notify(*)
|
241
|
-
options = {
|
176
|
+
options = { process_options: , write_options: }
|
242
177
|
|
243
178
|
begin
|
244
|
-
use_case =
|
179
|
+
use_case = Bot::FetchBirthdaysFromNotion.new(options)
|
245
180
|
|
246
|
-
use_case.
|
181
|
+
use_case.execute
|
247
182
|
rescue StandardError => e
|
248
183
|
{ body: { message: e.message } }
|
249
184
|
end
|
250
185
|
end
|
251
186
|
|
252
|
-
def self.read_options
|
253
|
-
{
|
254
|
-
base_url: NOTION_BASE_URL,
|
255
|
-
database_id: NOTION_DATABASE_ID,
|
256
|
-
secret: NOTION_SECRET,
|
257
|
-
filter: {
|
258
|
-
filter: { "and": read_filter }
|
259
|
-
}
|
260
|
-
}
|
261
|
-
end
|
262
|
-
|
263
|
-
def self.read_filter
|
264
|
-
today = Common::TimeZone.set_colombia_time_zone.strftime('%F').to_s
|
265
|
-
|
266
|
-
[
|
267
|
-
{ property: 'Desde?', date: { on_or_before: today } },
|
268
|
-
{ property: 'Hasta?', date: { on_or_after: today } }
|
269
|
-
]
|
270
|
-
end
|
271
|
-
|
272
187
|
def self.process_options
|
273
188
|
{
|
274
|
-
|
275
|
-
|
189
|
+
database_id: NOTION_DATABASE_ID,
|
190
|
+
secret: NOTION_SECRET
|
276
191
|
}
|
277
192
|
end
|
278
193
|
|
279
|
-
def self.
|
194
|
+
def self.write_options
|
280
195
|
{
|
281
|
-
|
196
|
+
connection: {
|
197
|
+
host: "host",
|
198
|
+
port: 5432,
|
199
|
+
dbname: "bas",
|
200
|
+
user: "postgres",
|
201
|
+
password: "postgres"
|
202
|
+
},
|
203
|
+
db_table: "use_cases",
|
204
|
+
bot_name: "FetchBirthdaysFromNotion"
|
282
205
|
}
|
283
206
|
end
|
284
207
|
end
|
@@ -290,18 +213,16 @@ In the `serverless.yml` file, add a new instance in the `functions` block with t
|
|
290
213
|
|
291
214
|
```bash
|
292
215
|
functions:
|
293
|
-
|
294
|
-
handler: src/lambdas/
|
216
|
+
fetchBirthdayFromNotion:
|
217
|
+
handler: src/lambdas/birthday_fetch.Bot::Birthday.fetch
|
295
218
|
environment:
|
296
|
-
|
297
|
-
|
298
|
-
PTO_DISCORD_WEBHOOK: ${file(./env.yml):${env:STAGE}.PTO_DISCORD_WEBHOOK}
|
299
|
-
PTO_DISCORD_BOT_NAME: ${file(./env.yml):${env:STAGE}.DISCORD_BOT_NAME}
|
219
|
+
BIRTHDAY_NOTION_DATABASE_ID: ${file(./env.yml):${env:STAGE}.BIRTHDAY_NOTION_DATABASE_ID}
|
220
|
+
BIRTHDAY_NOTION_SECRET: ${file(./env.yml):${env:STAGE}.BIRTHDAY_NOTION_SECRET}
|
300
221
|
events:
|
301
222
|
- schedule:
|
302
|
-
name:
|
303
|
-
description: "
|
304
|
-
rate: cron(${file(./env.yml):${env:STAGE}.
|
223
|
+
name: birthday-fetch
|
224
|
+
description: "Fetch every 24 hours at 8:30 a.m (UTC-5) from monday to friday"
|
225
|
+
rate: cron(${file(./env.yml):${env:STAGE}.BIRTHDAY_FETCH_SCHEDULER})
|
305
226
|
enabled: true
|
306
227
|
```
|
307
228
|
|
data/lib/bas/bot/base.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../utils/exceptions/function_not_implemented"
|
4
|
+
require_relative "../utils/exceptions/invalid_process_response"
|
5
|
+
require_relative "../write/postgres_update"
|
6
|
+
|
7
|
+
module Bot
|
8
|
+
##
|
9
|
+
# The Bot::Base class serves as the foundation for implementing specific bots. Operating
|
10
|
+
# as an interface, this class defines essential attributes and methods, providing a blueprint
|
11
|
+
# for creating custom bots formed by a Read, Process, and Write components.
|
12
|
+
#
|
13
|
+
class Base
|
14
|
+
attr_reader :read_options, :process_options, :write_options
|
15
|
+
attr_accessor :read_response, :process_response, :write_response
|
16
|
+
|
17
|
+
def initialize(config)
|
18
|
+
@read_options = config[:read_options] || {}
|
19
|
+
@process_options = config[:process_options] || {}
|
20
|
+
@write_options = config[:write_options] || {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
@read_response = read
|
25
|
+
|
26
|
+
write_read_response_in_process
|
27
|
+
|
28
|
+
@process_response = process
|
29
|
+
raise Utils::Exceptions::InvalidProcessResponse unless process_response.is_a?(Hash)
|
30
|
+
|
31
|
+
write_read_response_processed
|
32
|
+
|
33
|
+
@write_response = write
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def read
|
39
|
+
raise Utils::Exceptions::FunctionNotImplemented
|
40
|
+
end
|
41
|
+
|
42
|
+
def process
|
43
|
+
raise Utils::Exceptions::FunctionNotImplemented
|
44
|
+
end
|
45
|
+
|
46
|
+
def write
|
47
|
+
raise Utils::Exceptions::FunctionNotImplemented
|
48
|
+
end
|
49
|
+
|
50
|
+
def unprocessable_response
|
51
|
+
read_data = read_response.data
|
52
|
+
|
53
|
+
read_data.nil? || read_data == {} || read_data.any? { |_key, value| [[], ""].include?(value) }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def write_read_response_in_process
|
59
|
+
return if read_options[:avoid_process].eql?(true) || read_response.id.nil?
|
60
|
+
|
61
|
+
options = { params: { stage: "in process" }, conditions: "id=#{read_response.id}" }
|
62
|
+
|
63
|
+
Write::PostgresUpdate.new(read_options.merge(options)).execute
|
64
|
+
end
|
65
|
+
|
66
|
+
def write_read_response_processed
|
67
|
+
return if read_options[:avoid_process].eql?(true) || read_response.id.nil?
|
68
|
+
|
69
|
+
options = { params: { stage: "processed" }, conditions: "id=#{read_response.id}" }
|
70
|
+
|
71
|
+
Write::PostgresUpdate.new(read_options.merge(options)).execute
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
require_relative "../read/postgres"
|
5
|
+
require_relative "../write/postgres"
|
6
|
+
|
7
|
+
module Bot
|
8
|
+
##
|
9
|
+
# The Bot::CompareWipLimitCount class serves as a bot implementation to read domains wip limits and
|
10
|
+
# counts from a PostgresDB database, compare the values to find exceeded counts, and write them on
|
11
|
+
# a PostgresDB table with a specific format.
|
12
|
+
#
|
13
|
+
# <br>
|
14
|
+
# <b>Example</b>
|
15
|
+
#
|
16
|
+
# options = {
|
17
|
+
# read_options: {
|
18
|
+
# connection: {
|
19
|
+
# host: "localhost",
|
20
|
+
# port: 5432,
|
21
|
+
# dbname: "bas",
|
22
|
+
# user: "postgres",
|
23
|
+
# password: "postgres"
|
24
|
+
# },
|
25
|
+
# db_table: "use_cases",
|
26
|
+
# tag: "FetchDomainsWipLimitFromNotion"
|
27
|
+
# },
|
28
|
+
# write_options: {
|
29
|
+
# connection: {
|
30
|
+
# host: "localhost",
|
31
|
+
# port: 5432,
|
32
|
+
# dbname: "bas",
|
33
|
+
# user: "postgres",
|
34
|
+
# password: "postgres"
|
35
|
+
# },
|
36
|
+
# db_table: "use_cases",
|
37
|
+
# tag: "CompareWipLimitCount"
|
38
|
+
# }
|
39
|
+
# }
|
40
|
+
#
|
41
|
+
# bot = Bot::CompareWipLimitCount.new(options)
|
42
|
+
# bot.execute
|
43
|
+
#
|
44
|
+
class CompareWipLimitCount < Bot::Base
|
45
|
+
# read function to execute the PostgresDB Read component
|
46
|
+
#
|
47
|
+
def read
|
48
|
+
reader = Read::Postgres.new(read_options.merge(conditions))
|
49
|
+
|
50
|
+
reader.execute
|
51
|
+
end
|
52
|
+
|
53
|
+
# Process function to compare the domains wip counts and limits
|
54
|
+
#
|
55
|
+
def process
|
56
|
+
return { success: { exceeded_domain_count: {} } } if unprocessable_response
|
57
|
+
|
58
|
+
domains_limits = read_response.data["domains_limits"]
|
59
|
+
domain_wip_count = read_response.data["domain_wip_count"]
|
60
|
+
|
61
|
+
exceeded_domain_count = exceedded_counts(domains_limits, domain_wip_count)
|
62
|
+
|
63
|
+
{ success: { exceeded_domain_count: } }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Write function to execute the PostgresDB write component
|
67
|
+
#
|
68
|
+
def write
|
69
|
+
write = Write::Postgres.new(write_options, process_response)
|
70
|
+
|
71
|
+
write.execute
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def conditions
|
77
|
+
{
|
78
|
+
where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
|
79
|
+
params: [false, read_options[:tag], "unprocessed"]
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def exceedded_counts(limits, counts)
|
84
|
+
counts.to_a.map do |domain_wip_count|
|
85
|
+
domain, count = domain_wip_count
|
86
|
+
domain_limit = limits[domain]
|
87
|
+
|
88
|
+
{ domain:, exceeded: count - domain_limit } if count > domain_limit
|
89
|
+
end.compact
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,128 @@
|
|
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::FetchBirthdaysFromNotion class serves as a bot implementation to read birthdays from a
|
11
|
+
# 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: "use_cases",
|
30
|
+
# tag: "FetchBirthdaysFromNotion"
|
31
|
+
# }
|
32
|
+
# }
|
33
|
+
#
|
34
|
+
# bot = Bot::FetchBirthdaysFromNotion.new(options)
|
35
|
+
# bot.execute
|
36
|
+
#
|
37
|
+
class FetchBirthdaysFromNotion < Bot::Base
|
38
|
+
# read function to execute the PostgresDB Read component
|
39
|
+
#
|
40
|
+
def read
|
41
|
+
reader = Read::Postgres.new(read_options.merge(conditions))
|
42
|
+
|
43
|
+
reader.execute
|
44
|
+
end
|
45
|
+
|
46
|
+
# Process function to execute the Notion utility to fetch birthdays from a notion database
|
47
|
+
#
|
48
|
+
def process
|
49
|
+
response = Utils::Notion::Request.execute(params)
|
50
|
+
|
51
|
+
if response.code == 200
|
52
|
+
birthdays_list = normalize_response(response.parsed_response["results"])
|
53
|
+
|
54
|
+
{ success: { birthdays: birthdays_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 conditions
|
71
|
+
{
|
72
|
+
where: "archived=$1 AND tag=$2 ORDER BY inserted_at DESC",
|
73
|
+
params: [false, read_options[:tag]]
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def params
|
78
|
+
{
|
79
|
+
endpoint: "databases/#{process_options[:database_id]}/query",
|
80
|
+
secret: process_options[:secret],
|
81
|
+
method: "post",
|
82
|
+
body:
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def body
|
87
|
+
today = Time.now.utc.strftime("%F").to_s
|
88
|
+
|
89
|
+
{
|
90
|
+
filter: {
|
91
|
+
and: [{ property: "BD_this_year", date: { equals: today } }] + 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 normalize_response(results)
|
108
|
+
return [] if results.nil?
|
109
|
+
|
110
|
+
results.map do |value|
|
111
|
+
birthday_fields = value["properties"]
|
112
|
+
|
113
|
+
{
|
114
|
+
"name" => extract_rich_text_field_value(birthday_fields["Complete Name"]),
|
115
|
+
"birthday_date" => extract_date_field_value(birthday_fields["BD_this_year"])
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def extract_rich_text_field_value(data)
|
121
|
+
data["rich_text"][0]["plain_text"]
|
122
|
+
end
|
123
|
+
|
124
|
+
def extract_date_field_value(data)
|
125
|
+
data["formula"]["date"]["start"]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|