bas 1.2.1 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3574c150bdecb2fd88551db1b4add9bd5d0458d6b44eec87bd2c3efd44ff8a83
4
- data.tar.gz: ffeac8bae610aec6449161f9c267385e84921141d953f98175a05c6186f2e641
3
+ metadata.gz: 4ab201fef0200fb13b92ff49a3925c0fc2a10efd26a473d987cc9eef4ddbeae6
4
+ data.tar.gz: 93724237d9d6865c369e46f528a2a92dc63b7dd4239d5b0d37725cdb57d96ff9
5
5
  SHA512:
6
- metadata.gz: 8588edfeb8aec0ec204a040e79bd801d1563bf45b94b7ccd50c89ed160a2a2334f4285cd54d5c001228a9de5d14d02a64800c36b10b54f31d01ed1c877c0ae34
7
- data.tar.gz: 921b8280f7c071a8aaa7752b99b0fffabb0e1e434f6b051433a44d49f2c137e5092889250f7301e41b678ca763e9f445237a4252bb22a0731920f4731536006e
6
+ metadata.gz: a958b5d144da8fa1e162d5d44a18dd30472d2cd7bbd9c6d3fe6991d860a2b5199b9c68aff3b190fbdc48cc275b686c6707b304b39552a6776945e391b4bcb5b1
7
+ data.tar.gz: 30d014adce784050342b51663a583ee7474e2f00b28ef60210254c5f3fdf43d614607bff763325d93016d5b6bd320a5cdb9363a86b03082d37e28d896248979a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ # 1.4.0 (26.07.2024)
4
+ - [Add bots to synchronize issues with notion work items](https://github.com/kommitters/bas/issues/87)
5
+ - [Update fetch pto from notion bot](https://github.com/kommitters/bas/issues/75)
6
+
7
+ # 1.3.0 (18.07.2024)
8
+ - [Add bots to check webs availability](https://github.com/kommitters/bas/issues/83)
9
+
3
10
  # 1.2.1 (16.07.2024)
4
11
  - [Update format message for the digital ocean alert bots use case](https://github.com/kommitters/bas/pull/84)
5
12
 
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require_relative "./base"
6
+ require_relative "../read/postgres"
7
+ require_relative "../utils/notion/request"
8
+ require_relative "../utils/notion/types"
9
+ require_relative "../write/postgres"
10
+
11
+ module Bot
12
+ ##
13
+ # The Bot::CreateWorkItem class serves as a bot implementation to create "work items" on a
14
+ # notion database using information of a GitHub issue.
15
+ #
16
+ # <br>
17
+ # <b>Example</b>
18
+ #
19
+ # options = {
20
+ # read_options: {
21
+ # connection: {
22
+ # host: "localhost",
23
+ # port: 5432,
24
+ # dbname: "bas",
25
+ # user: "postgres",
26
+ # password: "postgres"
27
+ # },
28
+ # db_table: "github_issues",
29
+ # tag: "CreateWorkItemRequest"
30
+ # },
31
+ # process_options: {
32
+ # database_id: "notion database id",
33
+ # secret: "notion secret",
34
+ # domain: "domain association",
35
+ # status: "default status",
36
+ # work_item_type: "work_item_type",
37
+ # project: "project id"
38
+ # },
39
+ # write_options: {
40
+ # connection: {
41
+ # host: "localhost",
42
+ # port: 5432,
43
+ # dbname: "bas",
44
+ # user: "postgres",
45
+ # password: "postgres"
46
+ # },
47
+ # db_table: "github_issues",
48
+ # tag: "CreateWorkItem"
49
+ # }
50
+ # }
51
+ #
52
+ # bot = Bot::VerifyIssueExistanceInNotion.new(options)
53
+ # bot.execute
54
+ #
55
+ class CreateWorkItem < Bot::Base
56
+ include Utils::Notion::Types
57
+
58
+ UPDATE_REQUEST = "UpdateWorkItemRequest"
59
+
60
+ # read function to execute the PostgresDB Read component
61
+ #
62
+ def read
63
+ reader = Read::Postgres.new(read_options.merge(conditions))
64
+
65
+ reader.execute
66
+ end
67
+
68
+ # process function to execute the Notion utility to create work items on a notion
69
+ # database
70
+ #
71
+ def process
72
+ return { success: { created: nil } } if unprocessable_response
73
+
74
+ response = Utils::Notion::Request.execute(params)
75
+
76
+ if response.code == 200
77
+ { success: { issue: read_response.data["issue"], notion_wi: response["id"] } }
78
+ else
79
+ { error: { message: response.parsed_response, status_code: response.code } }
80
+ end
81
+ end
82
+
83
+ # write function to execute the PostgresDB write component
84
+ #
85
+ def write
86
+ options = write_options.merge({ tag: })
87
+
88
+ write = Write::Postgres.new(options, process_response)
89
+
90
+ write.execute
91
+ end
92
+
93
+ private
94
+
95
+ def conditions
96
+ {
97
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
98
+ params: [false, read_options[:tag], "unprocessed"]
99
+ }
100
+ end
101
+
102
+ def params
103
+ {
104
+ endpoint: "pages",
105
+ secret: process_options[:secret],
106
+ method: "post",
107
+ body:
108
+ }
109
+ end
110
+
111
+ def body
112
+ {
113
+ parent: { database_id: process_options[:database_id] },
114
+ properties:
115
+ }
116
+ end
117
+
118
+ def properties # rubocop:disable Metrics/AbcSize
119
+ {
120
+ "Responsible domain": select(process_options[:domain]),
121
+ "Github Issue id": rich_text(read_response.data["issue"]["id"].to_s),
122
+ "Status": status(process_options[:status]),
123
+ "Detail": title(read_response.data["issue"]["title"])
124
+ }.merge(work_item_type)
125
+ end
126
+
127
+ def work_item_type
128
+ case process_options[:work_item_type]
129
+ when "activity" then { "Activity": relation(process_options[:activity]) }
130
+ when "project" then { "Project": relation(process_options[:project]) }
131
+ else {}
132
+ end
133
+ end
134
+
135
+ def tag
136
+ return write_options[:tag] if process_response[:success].nil? || process_response[:success][:notion_wi].nil?
137
+
138
+ UPDATE_REQUEST
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,93 @@
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::FetchDomainServicesFromNotion class serves as a bot implementation to read
11
+ # web domains 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: "localhost",
24
+ # port: 5432,
25
+ # dbname: "bas",
26
+ # user: "postgres",
27
+ # password: "postgres"
28
+ # },
29
+ # db_table: "web_availability",
30
+ # tag: "FetchDomainServicesFromNotion"
31
+ # }
32
+ # }
33
+ #
34
+ # bot = Bot::FetchDomainServicesFromNotion.new(options)
35
+ # bot.execute
36
+ #
37
+ class FetchDomainServicesFromNotion < Bot::Base
38
+ def read
39
+ reader = Read::Default.new
40
+
41
+ reader.execute
42
+ end
43
+
44
+ # Process function to execute the Notion utility to fetch web domains from a notion database
45
+ #
46
+ def process
47
+ response = Utils::Notion::Request.execute(params)
48
+
49
+ if response.code == 200
50
+ urls_list = normalize_response(response.parsed_response["results"])
51
+
52
+ { success: { urls: urls_list } }
53
+ else
54
+ { error: { message: response.parsed_response, status_code: response.code } }
55
+ end
56
+ end
57
+
58
+ # Write function to execute the PostgresDB write component
59
+ #
60
+ def write
61
+ write = Write::Postgres.new(write_options, process_response)
62
+
63
+ write.execute
64
+ end
65
+
66
+ private
67
+
68
+ def params
69
+ {
70
+ endpoint: "databases/#{process_options[:database_id]}/query",
71
+ secret: process_options[:secret],
72
+ method: "post",
73
+ body: {}
74
+ }
75
+ end
76
+
77
+ def normalize_response(results)
78
+ return [] if results.nil?
79
+
80
+ results.map do |value|
81
+ properties = value["properties"]
82
+
83
+ {
84
+ "url" => extract_rich_text_field_value(properties["domain"])
85
+ }
86
+ end
87
+ end
88
+
89
+ def extract_rich_text_field_value(data)
90
+ data["rich_text"][0]["plain_text"]
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/default"
5
+ require_relative "../utils/github/octokit_client"
6
+ require_relative "../write/postgres"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::FetchGithubIssues class serves as a bot implementation to fetch GitHub issues from a
11
+ # repository and write them on 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: "github_issues",
26
+ # tag: "FetchGithubIssues",
27
+ # avoid_process: true
28
+ # },
29
+ # process_options: {
30
+ # private_pem: "Github App private token",
31
+ # app_id: "Github App id",
32
+ # repo: "repository name",
33
+ # filters: "hash with filters",
34
+ # organization: "GitHub organization name"
35
+ # },
36
+ # write_options: {
37
+ # connection: {
38
+ # host: "localhost",
39
+ # port: 5432,
40
+ # dbname: "bas",
41
+ # user: "postgres",
42
+ # password: "postgres"
43
+ # },
44
+ # db_table: "github_issues",
45
+ # tag: "FetchGithubIssues"
46
+ # }
47
+ # }
48
+ #
49
+ # bot = Bot::FetchGithubIssues.new(options)
50
+ # bot.execute
51
+ #
52
+ class FetchGithubIssues < Bot::Base
53
+ ISSUE_PARAMS = %i[id html_url title body labels state created_at updated_at].freeze
54
+ PER_PAGE = 100
55
+
56
+ # read function to execute the PostgresDB Read component
57
+ #
58
+ def read
59
+ reader = Read::Postgres.new(read_options.merge(conditions))
60
+
61
+ reader.execute
62
+ end
63
+
64
+ # Process function to request GitHub issues using the octokit utility
65
+ #
66
+ def process
67
+ octokit = Utils::Github::OctokitClient.new(params).execute
68
+
69
+ if octokit[:client]
70
+ repo_issues = octokit[:client].issues(@process_options[:repo], filters)
71
+
72
+ issues = normalize_response(repo_issues)
73
+
74
+ { success: { issues: } }
75
+ else
76
+ { error: octokit[:error] }
77
+ end
78
+ end
79
+
80
+ # Write function to execute the PostgresDB write component
81
+ #
82
+ def write
83
+ write = Write::Postgres.new(write_options, process_response)
84
+
85
+ write.execute
86
+ end
87
+
88
+ private
89
+
90
+ def conditions
91
+ {
92
+ where: "tag=$1 ORDER BY inserted_at DESC",
93
+ params: [read_options[:tag]]
94
+ }
95
+ end
96
+
97
+ def params
98
+ {
99
+ private_pem: process_options[:private_pem],
100
+ app_id: process_options[:app_id],
101
+ method: process_options[:method],
102
+ method_params: process_options[:method_params],
103
+ organization: process_options[:organization]
104
+ }
105
+ end
106
+
107
+ def filters
108
+ default_filter = { per_page: PER_PAGE }
109
+
110
+ filters = @process_options[:filters]
111
+ filters = filters.merge({ since: read_response.inserted_at }) unless read_response.nil?
112
+
113
+ filters.is_a?(Hash) ? default_filter.merge(filters) : default_filter
114
+ end
115
+
116
+ def normalize_response(issues)
117
+ issues.map do |issue|
118
+ ISSUE_PARAMS.reduce({}) do |hash, param|
119
+ hash.merge({ param => issue.send(param) })
120
+ .merge({ assignees: issue.assignees.map(&:login) })
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
4
+
3
5
  require_relative "./base"
4
6
  require_relative "../read/default"
5
7
  require_relative "../utils/notion/request"
@@ -34,7 +36,7 @@ module Bot
34
36
  # bot = Bot::FetchNextWeekPtosFromNotion.new(options)
35
37
  # bot.execute
36
38
  #
37
- class FetchNextWeekPtosFromNotion < Bot::Base
39
+ class FetchNextWeekPtosFromNotion < Bot::Base # rubocop:disable Metrics/ClassLength
38
40
  # Read function to execute the default Read component
39
41
  #
40
42
  def read
@@ -130,14 +132,47 @@ module Bot
130
132
  results.map do |pto|
131
133
  pto_fields = pto["properties"]
132
134
 
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
- }
135
+ name = extract_description_field_value(pto_fields["Description"])
136
+ start_date = extract_date_field_value(pto_fields["StartDateTime"])
137
+ end_date = extract_date_field_value(pto_fields["EndDateTime"])
138
+
139
+ description(name, start_date, end_date)
138
140
  end
139
141
  end
140
142
 
143
+ def description(name, start_date, end_date)
144
+ start = start_description(start_date)
145
+ finish = end_description(end_date)
146
+
147
+ "#{name} will not be working between #{start} and #{finish}. And returns the #{returns(finish)}"
148
+ end
149
+
150
+ def start_description(date)
151
+ date[:from]
152
+ end
153
+
154
+ def end_description(date)
155
+ return date[:from] if date[:to].nil?
156
+
157
+ date[:to]
158
+ end
159
+
160
+ def returns(date)
161
+ date.include?("T12") ? "#{date} in the afternoon" : next_work_day(date)
162
+ end
163
+
164
+ def next_work_day(date)
165
+ datetime = DateTime.parse(date)
166
+
167
+ return_day = case datetime.wday
168
+ when 5 then datetime + 3
169
+ when 6 then datetime + 2
170
+ else datetime + 1
171
+ end
172
+
173
+ return_day.strftime("%A %B %d of %Y").to_s
174
+ end
175
+
141
176
  def extract_description_field_value(data)
142
177
  names = data["title"].map { |name| name["plain_text"] }
143
178
 
@@ -74,26 +74,13 @@ module Bot
74
74
  endpoint: "databases/#{process_options[:database_id]}/query",
75
75
  secret: process_options[:secret],
76
76
  method: "post",
77
- body:
77
+ body: { filter: today_condition }
78
78
  }
79
79
  end
80
80
 
81
- def body
82
- { filter: { "or": conditions } }
83
- end
84
-
85
- def conditions
86
- [
87
- today_condition,
88
- { property: "StartDateTime", date: { this_week: {} } },
89
- { property: "EndDateTime", date: { this_week: {} } },
90
- { property: "StartDateTime", date: { next_week: {} } },
91
- { property: "EndDateTime", date: { next_week: {} } }
92
- ]
93
- end
94
-
95
81
  def today_condition
96
82
  today = Time.now.utc.strftime("%F").to_s
83
+
97
84
  {
98
85
  "and": [
99
86
  { property: "StartDateTime", date: { on_or_before: today } },
@@ -134,7 +121,7 @@ module Bot
134
121
  end
135
122
 
136
123
  def returns(date)
137
- date.include?("T") ? "#{date} in the afternoon" : next_work_day(date)
124
+ date.include?("T12") ? "#{date} in the afternoon" : next_work_day(date)
138
125
  end
139
126
 
140
127
  def next_work_day(date)
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httparty"
4
+
5
+ require_relative "./base"
6
+ require_relative "../read/postgres"
7
+ require_relative "../write/postgres"
8
+ require_relative "../utils/openai/run_assistant"
9
+
10
+ module Bot
11
+ ##
12
+ # The Bot::ReviewDomainAvailability class serves as a bot implementation to read from a postgres
13
+ # shared storage a domain requests and review its availability.
14
+ #
15
+ # <br>
16
+ # <b>Example</b>
17
+ #
18
+ # options = {
19
+ # read_options: {
20
+ # connection: {
21
+ # host: "localhost",
22
+ # port: 5432,
23
+ # dbname: "bas",
24
+ # user: "postgres",
25
+ # password: "postgres"
26
+ # },
27
+ # db_table: "web_availability",
28
+ # tag: "ReviewDomainRequest"
29
+ # },
30
+ # write_options: {
31
+ # connection: {
32
+ # host: "localhost",
33
+ # port: 5432,
34
+ # dbname: "bas",
35
+ # user: "postgres",
36
+ # password: "postgres"
37
+ # },
38
+ # db_table: "web_availability",
39
+ # tag: "ReviewDomainAvailability"
40
+ # }
41
+ # }
42
+ #
43
+ # bot = Bot::ReviewDomainAvailability.new(options)
44
+ # bot.execute
45
+ #
46
+ class ReviewDomainAvailability < Bot::Base
47
+ # read function to execute the PostgresDB Read component
48
+ #
49
+ def read
50
+ reader = Read::Postgres.new(read_options.merge(conditions))
51
+
52
+ reader.execute
53
+ end
54
+
55
+ # process function to make a http request to the domain and check the status
56
+ #
57
+ def process
58
+ return { success: { review: nil } } if unprocessable_response
59
+
60
+ response = availability
61
+
62
+ if response.code == 200
63
+ { success: { review: nil } }
64
+ else
65
+ { success: { notification: notification(response) } }
66
+ end
67
+ end
68
+
69
+ # write function to execute the PostgresDB write component
70
+ #
71
+ def write
72
+ write = Write::Postgres.new(write_options, process_response)
73
+
74
+ write.execute
75
+ end
76
+
77
+ private
78
+
79
+ def conditions
80
+ {
81
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
82
+ params: [false, read_options[:tag], "unprocessed"]
83
+ }
84
+ end
85
+
86
+ def availability
87
+ url = read_response.data["url"]
88
+
89
+ HTTParty.get(url, {})
90
+ end
91
+
92
+ def notification(response)
93
+ data = {
94
+ domain: read_response.data["url"],
95
+ status_code: response.code
96
+ }
97
+
98
+ ":warning: Domain is down: #{data}"
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "md_to_notion"
5
+
6
+ require_relative "./base"
7
+ require_relative "../read/postgres"
8
+ require_relative "../utils/notion/request"
9
+ require_relative "../utils/notion/types"
10
+ require_relative "../utils/notion/delete_page_blocks"
11
+ require_relative "../write/postgres"
12
+
13
+ module Bot
14
+ ##
15
+ # The Bot::UpdateWorkItem class serves as a bot implementation to update "work items" on a
16
+ # notion database using information of a GitHub issue.
17
+ #
18
+ # <br>
19
+ # <b>Example</b>
20
+ #
21
+ # options = {
22
+ # read_options: {
23
+ # connection: {
24
+ # host: "localhost",
25
+ # port: 5432,
26
+ # dbname: "bas",
27
+ # user: "postgres",
28
+ # password: "postgres"
29
+ # },
30
+ # db_table: "github_issues",
31
+ # tag: "UpdateWorkItemRequest"
32
+ # },
33
+ # process_options: {
34
+ # secret: "notion secret"
35
+ # },
36
+ # write_options: {
37
+ # connection: {
38
+ # host: "localhost",
39
+ # port: 5432,
40
+ # dbname: "bas",
41
+ # user: "postgres",
42
+ # password: "postgres"
43
+ # },
44
+ # db_table: "github_issues",
45
+ # tag: "UpdateWorkItem"
46
+ # }
47
+ # }
48
+ #
49
+ # bot = Bot::UpdateWorkItem.new(options)
50
+ # bot.execute
51
+ #
52
+ class UpdateWorkItem < Bot::Base
53
+ include Utils::Notion::Types
54
+
55
+ DESCRIPTION = "Issue Description"
56
+
57
+ # read function to execute the PostgresDB Read component
58
+ #
59
+ def read
60
+ reader = Read::Postgres.new(read_options.merge(conditions))
61
+
62
+ reader.execute
63
+ end
64
+
65
+ # process function to execute the Notion utility to update work items on a notion
66
+ # database
67
+ def process
68
+ return { success: { updated: nil } } if unprocessable_response
69
+
70
+ delete_wi
71
+
72
+ response = Utils::Notion::Request.execute(params)
73
+
74
+ if response.code == 200
75
+ { success: { issue: read_response.data["issue"] } }
76
+ else
77
+ { error: { message: response.parsed_response, status_code: response.code } }
78
+ end
79
+ end
80
+
81
+ # write function to execute the PostgresDB write component
82
+ #
83
+ def write
84
+ write = Write::Postgres.new(write_options, process_response)
85
+
86
+ write.execute
87
+ end
88
+
89
+ private
90
+
91
+ def conditions
92
+ {
93
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
94
+ params: [false, read_options[:tag], "unprocessed"]
95
+ }
96
+ end
97
+
98
+ def params
99
+ {
100
+ endpoint: "blocks/#{read_response.data["notion_wi"]}/children",
101
+ secret: process_options[:secret],
102
+ method: "patch",
103
+ body:
104
+ }
105
+ end
106
+
107
+ def body
108
+ { children: description + [issue_reference] }
109
+ end
110
+
111
+ def description
112
+ MdToNotion::Parser.markdown_to_notion_blocks(read_response.data["issue"]["body"])
113
+ end
114
+
115
+ def issue_reference
116
+ {
117
+ object: "block",
118
+ type: "paragraph",
119
+ paragraph: rich_text("issue", read_response.data["issue"]["html_url"])
120
+ }
121
+ end
122
+
123
+ def delete_wi
124
+ params = {
125
+ page_id: read_response.data["notion_wi"],
126
+ secret: process_options[:secret]
127
+ }
128
+
129
+ Utils::Notion::DeletePageBlocks.new(params).execute
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require_relative "./base"
6
+ require_relative "../read/postgres"
7
+ require_relative "../utils/notion/request"
8
+ require_relative "../utils/notion/update_db_state"
9
+ require_relative "../write/postgres"
10
+
11
+ module Bot
12
+ ##
13
+ # The Bot::VerifyIssueExistanceInNotion class serves as a bot implementation to verify if a
14
+ # GitHub issue was already created on a notion database base on a column with the issue id.
15
+ #
16
+ # <br>
17
+ # <b>Example</b>
18
+ #
19
+ # options = {
20
+ # read_options: {
21
+ # connection: {
22
+ # host: "localhost",
23
+ # port: 5432,
24
+ # dbname: "bas",
25
+ # user: "postgres",
26
+ # password: "postgres"
27
+ # },
28
+ # db_table: "github_issues",
29
+ # tag: "GithubIssueRequest"
30
+ # },
31
+ # process_options: {
32
+ # database_id: "notion database id",
33
+ # secret: "notion secret"
34
+ # },
35
+ # write_options: {
36
+ # connection: {
37
+ # host: "localhost",
38
+ # port: 5432,
39
+ # dbname: "bas",
40
+ # user: "postgres",
41
+ # password: "postgres"
42
+ # },
43
+ # db_table: "github_issues",
44
+ # tag: "VerifyIssueExistanceInNotio"
45
+ # }
46
+ # }
47
+ #
48
+ # bot = Bot::VerifyIssueExistanceInNotion.new(options)
49
+ # bot.execute
50
+ #
51
+ class VerifyIssueExistanceInNotion < Bot::Base
52
+ NOT_FOUND = "not found"
53
+
54
+ # read function to execute the PostgresDB Read component
55
+ #
56
+ def read
57
+ reader = Read::Postgres.new(read_options.merge(conditions))
58
+
59
+ reader.execute
60
+ end
61
+
62
+ # process function to execute the Notion utility to verify GitHub issues existance
63
+ # on a notion database
64
+ #
65
+ def process
66
+ return { success: { issue: nil } } if unprocessable_response
67
+
68
+ response = Utils::Notion::Request.execute(params)
69
+
70
+ if response.code == 200
71
+ result = response.parsed_response["results"].first
72
+
73
+ { success: { issue: read_response.data["request"], notion_wi: notion_wi_id(result) } }
74
+ else
75
+ { error: { message: response.parsed_response, status_code: response.code } }
76
+ end
77
+ end
78
+
79
+ # write function to execute the PostgresDB write component
80
+ #
81
+ def write
82
+ options = write_options.merge({ tag: })
83
+
84
+ write = Write::Postgres.new(options, process_response)
85
+
86
+ write.execute
87
+ end
88
+
89
+ private
90
+
91
+ def conditions
92
+ {
93
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
94
+ params: [false, read_options[:tag], "unprocessed"]
95
+ }
96
+ end
97
+
98
+ def params
99
+ {
100
+ endpoint: "databases/#{process_options[:database_id]}/query",
101
+ secret: process_options[:secret],
102
+ method: "post",
103
+ body:
104
+ }
105
+ end
106
+
107
+ def body
108
+ {
109
+ filter: {
110
+ property: "Github Issue id",
111
+ rich_text: { equals: read_response.data["request"]["id"].to_s }
112
+ }
113
+ }
114
+ end
115
+
116
+ def notion_wi_id(result)
117
+ return NOT_FOUND if result.nil?
118
+
119
+ result["id"]
120
+ end
121
+
122
+ def tag
123
+ issue = process_response[:success]
124
+
125
+ return write_options[:tag] if issue.nil? || issue[:notion_wi].nil?
126
+
127
+ issue[:notion_wi].eql?(NOT_FOUND) ? "CreateWorkItemRequest" : "UpdateWorkItemRequest"
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../utils/notion/update_db_state"
6
+ require_relative "../write/postgres"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::WriteDomainReviewRequests class serves as a bot implementation to read from a postgres
11
+ # shared storage a set of review domain requests and create single request on the shared storage to
12
+ # be processed one by one.
13
+ #
14
+ # <br>
15
+ # <b>Example</b>
16
+ #
17
+ # options = {
18
+ # read_options: {
19
+ # connection: {
20
+ # host: "localhost",
21
+ # port: 5432,
22
+ # dbname: "bas",
23
+ # user: "postgres",
24
+ # password: "postgres"
25
+ # },
26
+ # db_table: "web_availability",
27
+ # tag: "FetchDomainServicesFromNotion"
28
+ # },
29
+ # process_options: {
30
+ # connection: {
31
+ # host: "localhost",
32
+ # port: 5432,
33
+ # dbname: "bas",
34
+ # user: "postgres",
35
+ # password: "postgres"
36
+ # },
37
+ # db_table: "web_availability",
38
+ # tag: "ReviewDomainRequest"
39
+ # },
40
+ # write_options: {
41
+ # connection: {
42
+ # host: "localhost",
43
+ # port: 5432,
44
+ # dbname: "bas",
45
+ # user: "postgres",
46
+ # password: "postgres"
47
+ # },
48
+ # db_table: "web_availability",
49
+ # tag: "WriteDomainReviewRequests"
50
+ # }
51
+ # }
52
+ #
53
+ # bot = Bot::WriteDomainReviewRequests.new(options)
54
+ # bot.execute
55
+ #
56
+ class WriteDomainReviewRequests < Bot::Base
57
+ # read function to execute the PostgresDB Read component
58
+ #
59
+ def read
60
+ reader = Read::Postgres.new(read_options.merge(conditions))
61
+
62
+ reader.execute
63
+ end
64
+
65
+ # Process function to execute the Notion utility create single review requests
66
+ #
67
+ def process
68
+ return { success: { created: nil } } if unprocessable_response
69
+
70
+ read_response.data["urls"].each { |request| create_request(request) }
71
+
72
+ { success: { created: true } }
73
+ end
74
+
75
+ # Write function to execute the PostgresDB write component
76
+ #
77
+ def write
78
+ write = Write::Postgres.new(write_options, process_response)
79
+
80
+ write.execute
81
+ end
82
+
83
+ private
84
+
85
+ def conditions
86
+ {
87
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
88
+ params: [false, read_options[:tag], "unprocessed"]
89
+ }
90
+ end
91
+
92
+ def create_request(request)
93
+ write_data = write_request(request)
94
+
95
+ Write::Postgres.new(process_options, write_data).execute
96
+ end
97
+
98
+ def write_request(request)
99
+ return { error: request } if request["url"].empty? || !request["error"].nil?
100
+
101
+ { success: request }
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,96 @@
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::WriteGithubIssueRequests class serves as a bot implementation to write GitHub issues
10
+ # request to be sent individually.
11
+ #
12
+ # <br>
13
+ # <b>Example</b>
14
+ #
15
+ # options = {
16
+ # read_options: {
17
+ # connection: {
18
+ # host: "localhost",
19
+ # port: 5432,
20
+ # dbname: "bas",
21
+ # user: "postgres",
22
+ # password: "postgres"
23
+ # },
24
+ # db_table: "github_issues",
25
+ # tag: "FetchGithubIssues"
26
+ # },
27
+ # process_options: {
28
+ # connection: {
29
+ # host: "localhost",
30
+ # port: 5432,
31
+ # dbname: "bas",
32
+ # user: "postgres",
33
+ # password: "postgres"
34
+ # },
35
+ # db_table: "github_issues",
36
+ # tag: "GithubIssueRequest"
37
+ # },
38
+ # write_options: {
39
+ # connection: {
40
+ # host: "localhost",
41
+ # port: 5432,
42
+ # dbname: "bas",
43
+ # user: "postgres",
44
+ # password: "postgres"
45
+ # },
46
+ # db_table: "github_issues",
47
+ # tag: "WriteGithubIssueRequests"
48
+ # }
49
+ # }
50
+ #
51
+ # bot = Bot::WriteGithubIssueRequests.new(options)
52
+ # bot.execute
53
+ #
54
+ class WriteGithubIssueRequests < Bot::Base
55
+ # read function to execute the PostgresDB Read component
56
+ #
57
+ def read
58
+ reader = Read::Postgres.new(read_options.merge(conditions))
59
+
60
+ reader.execute
61
+ end
62
+
63
+ # Process function to write GitHub issues requests.
64
+ #
65
+ def process
66
+ return { success: { created: nil } } if unprocessable_response
67
+
68
+ read_response.data["issues"].each { |request| create_request(request) }
69
+
70
+ { success: { created: true } }
71
+ end
72
+
73
+ # Write function to execute the PostgresDB write component
74
+ #
75
+ def write
76
+ write = Write::Postgres.new(write_options, process_response)
77
+
78
+ write.execute
79
+ end
80
+
81
+ private
82
+
83
+ def conditions
84
+ {
85
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
86
+ params: [false, read_options[:tag], "unprocessed"]
87
+ }
88
+ end
89
+
90
+ def create_request(request)
91
+ write_data = { success: { request: } }
92
+
93
+ Write::Postgres.new(process_options, write_data).execute
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "octokit"
4
+ require "openssl"
5
+ require "jwt"
6
+
7
+ module Utils
8
+ module Github
9
+ ##
10
+ # This module is a Github utility for making requests to the Github API using the octokit module
11
+ #
12
+ class OctokitClient
13
+ def initialize(params)
14
+ @params = params
15
+ end
16
+
17
+ # Build the octokit client using a Github app access token
18
+ #
19
+ def execute
20
+ { client: octokit }
21
+ rescue StandardError => e
22
+ { error: e.to_s }
23
+ end
24
+
25
+ private
26
+
27
+ def octokit
28
+ Octokit::Client.new(bearer_token: access_token)
29
+ end
30
+
31
+ def access_token
32
+ app = Octokit::Client.new(client_id: @params[:app_id], bearer_token: jwt)
33
+
34
+ installation_id = app.find_organization_installation(@params[:organization]).id
35
+
36
+ app.create_app_installation_access_token(installation_id)[:token]
37
+ end
38
+
39
+ def jwt
40
+ private_key = OpenSSL::PKey::RSA.new(@params[:private_pem])
41
+
42
+ JWT.encode(jwt_payload, private_key, "RS256")
43
+ end
44
+
45
+ def jwt_payload
46
+ {
47
+ iat: Time.now.to_i - 60,
48
+ exp: Time.now.to_i + (10 * 60),
49
+ iss: @params[:app_id]
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httparty"
4
+ require_relative "request"
5
+
6
+ module Utils
7
+ module Notion
8
+ ##
9
+ # This module is a Notion utility for deleting page blocks.
10
+ #
11
+ class DeletePageBlocks
12
+ # Implements the delete page blocks process logic to Notion.
13
+ #
14
+ # <br>
15
+ # <b>Params:</b>
16
+ # * <tt>page_id</tt> Id of the notion page.
17
+ # * <tt>secret</tt> Notion secret.
18
+ #
19
+ # <br>
20
+ # <b>returns</b> <tt>HTTParty::Response</tt>
21
+ #
22
+ #
23
+ def initialize(params)
24
+ @params = params
25
+ end
26
+
27
+ def execute
28
+ page_blocks_ids.each { |block_id| delete_block(block_id) }
29
+ end
30
+
31
+ private
32
+
33
+ def page_blocks_ids
34
+ page = Utils::Notion::Request.execute(page_params)
35
+
36
+ page.parsed_response["results"].map { |block| block["id"] }
37
+ end
38
+
39
+ def page_params
40
+ {
41
+ endpoint: "blocks/#{@params[:page_id]}/children",
42
+ secret: @params[:secret],
43
+ method: "get",
44
+ body: {}
45
+ }
46
+ end
47
+
48
+ def delete_block(block_id)
49
+ params = delete_params(block_id)
50
+
51
+ Utils::Notion::Request.execute(params)
52
+ end
53
+
54
+ def delete_params(block_id)
55
+ {
56
+ endpoint: "blocks/#{block_id}",
57
+ secret: @params[:secret],
58
+ method: "delete",
59
+ body: {}
60
+ }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "httparty"
4
+ require "json"
4
5
 
5
6
  module Utils
6
7
  module Notion
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Utils
4
+ module Notion
5
+ ##
6
+ # This module is a Notion utility for formating standard notion types to
7
+ # filter or create.
8
+ #
9
+ module Types
10
+ def multi_select(name)
11
+ { "multi_select" => [{ "name" => name }] }
12
+ end
13
+
14
+ def relation(id)
15
+ { relation: [{ id: }] }
16
+ end
17
+
18
+ def select(name)
19
+ { select: { name: } }
20
+ end
21
+
22
+ def rich_text(content, url = nil)
23
+ text = { content: }
24
+
25
+ text = text.merge({ link: { url: } }) unless url.nil?
26
+
27
+ { rich_text: [{ text: }] }
28
+ end
29
+
30
+ def status(name)
31
+ { status: { name: } }
32
+ end
33
+
34
+ def title(content)
35
+ { title: [{ text: { content: } }] }
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/bas/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bas
4
4
  # Gem version
5
- VERSION = "1.2.1"
5
+ VERSION = "1.4.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bas
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kommitters Open Source
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-16 00:00:00.000000000 Z
11
+ date: 2024-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gmail_xoauth
@@ -187,11 +187,14 @@ files:
187
187
  - lib/bas.rb
188
188
  - lib/bas/bot/base.rb
189
189
  - lib/bas/bot/compare_wip_limit_count.rb
190
+ - lib/bas/bot/create_work_item.rb
190
191
  - lib/bas/bot/fetch_billing_from_digital_ocean.rb
191
192
  - lib/bas/bot/fetch_birthdays_from_notion.rb
193
+ - lib/bas/bot/fetch_domain_services_from_notion.rb
192
194
  - lib/bas/bot/fetch_domains_wip_counts_from_notion.rb
193
195
  - lib/bas/bot/fetch_domains_wip_limit_from_notion.rb
194
196
  - lib/bas/bot/fetch_emails_from_imap.rb
197
+ - lib/bas/bot/fetch_github_issues.rb
195
198
  - lib/bas/bot/fetch_media_from_notion.rb
196
199
  - lib/bas/bot/fetch_next_week_birthdays_from_notion.rb
197
200
  - lib/bas/bot/fetch_next_week_ptos_from_notion.rb
@@ -204,8 +207,13 @@ files:
204
207
  - lib/bas/bot/humanize_pto.rb
205
208
  - lib/bas/bot/notify_discord.rb
206
209
  - lib/bas/bot/notify_do_bill_alert_email.rb
210
+ - lib/bas/bot/review_domain_availability.rb
207
211
  - lib/bas/bot/review_media.rb
208
212
  - lib/bas/bot/update_review_media_state.rb
213
+ - lib/bas/bot/update_work_item.rb
214
+ - lib/bas/bot/verify_issue_existance_in_notion.rb
215
+ - lib/bas/bot/write_domain_review_requests.rb
216
+ - lib/bas/bot/write_github_issue_requests.rb
209
217
  - lib/bas/bot/write_media_review_in_notion.rb
210
218
  - lib/bas/bot/write_media_review_requests.rb
211
219
  - lib/bas/read/base.rb
@@ -216,9 +224,12 @@ files:
216
224
  - lib/bas/utils/discord/integration.rb
217
225
  - lib/bas/utils/exceptions/function_not_implemented.rb
218
226
  - lib/bas/utils/exceptions/invalid_process_response.rb
227
+ - lib/bas/utils/github/octokit_client.rb
219
228
  - lib/bas/utils/google/send_email.rb
220
229
  - lib/bas/utils/imap/request.rb
230
+ - lib/bas/utils/notion/delete_page_blocks.rb
221
231
  - lib/bas/utils/notion/request.rb
232
+ - lib/bas/utils/notion/types.rb
222
233
  - lib/bas/utils/notion/update_db_state.rb
223
234
  - lib/bas/utils/openai/run_assistant.rb
224
235
  - lib/bas/utils/postgres/request.rb