bas 0.4.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -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 +173 -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 +117 -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 +31 -68
- 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: 216571ef89c79b718c94679f1eee6890d3f0d278f563cb7b4dc1d3b6e9fc3d5f
|
4
|
+
data.tar.gz: 2ccd756d550b06b8946af3298bc21b6ef4535679f4b74479d5140a99b740424b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8b27047e7c1da06071b4711f434ae3c9313ae8b8bed345724b57c344fb15c5ddb4c2bbe30ecc930ab96431f29c61230cdb8449eb2c1720b40b33e20c02fee20
|
7
|
+
data.tar.gz: 4285d4d6e805cc843f7218e4937bb0e7b5ce24a240b5261a2a75a89253e03538c4a93f7840e120715123a7d19822039a0f7b9ea70b7a2e6d8dac28a806557ebf
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
# 1.0.1 (21.06.2024)
|
4
|
+
- Refactor pto notification format for improving OpenAI response.
|
5
|
+
|
6
|
+
# 1.0.0 (17.05.2024)
|
7
|
+
- [Refactor Project Architecture](https://github.com/kommitters/bas/issues/35)
|
8
|
+
- [Refactor PTO today Use Case](https://github.com/kommitters/bas/issues/29)
|
9
|
+
- [Refactor PTO next week Use Case](https://github.com/kommitters/bas/issues/30)
|
10
|
+
- [Refactor Birthday today Use Case](https://github.com/kommitters/bas/issues/31)
|
11
|
+
- [Refactor Birthday next week Use Case](https://github.com/kommitters/bas/issues/32)
|
12
|
+
- [Refactor WIP limit Use Case](https://github.com/kommitters/bas/issues/33)
|
13
|
+
- [Refactor support email Use Case](https://github.com/kommitters/bas/issues/34)
|
14
|
+
- [Add a garbage collector](https://github.com/kommitters/bas/issues/42)
|
15
|
+
- [Update read Postgres component](https://github.com/kommitters/bas/issues/45)
|
16
|
+
- [Update new table structure](https://github.com/kommitters/bas/issues/46)
|
17
|
+
|
3
18
|
# 0.4.0 (19.04.2024)
|
4
19
|
- [[BAS] Add process for OpenAI](https://github.com/kommitters/bas/issues/25)
|
5
20
|
|
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
|
![Gem Version](https://img.shields.io/gem/v/bas?style=for-the-badge)
|
8
8
|
![Gem Total Downloads](https://img.shields.io/gem/dt/bas?style=for-the-badge)
|
@@ -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
|