Skip to content

Commit

Permalink
Solve #87 - Add bots to synchronize issues with notion work items (#89)
Browse files Browse the repository at this point in the history
* Add github octokit client module

* Add fetch github issues bot

* Add write github issue requests bot

* Add verify issue existance in notion bot

* Add create work item bot

* Add update work item bot

* Improve fetch github issues

* Improve wi update process

* Add documentation for the github issues bots

* Add tests

* Fix typos
  • Loading branch information
FelipeGuzmanSierra authored Jul 26, 2024
1 parent c780f9d commit 4dc8294
Show file tree
Hide file tree
Showing 13 changed files with 1,706 additions and 0 deletions.
141 changes: 141 additions & 0 deletions lib/bas/bot/create_work_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# frozen_string_literal: true

require "json"

require_relative "./base"
require_relative "../read/postgres"
require_relative "../utils/notion/request"
require_relative "../utils/notion/types"
require_relative "../write/postgres"

module Bot
##
# The Bot::CreateWorkItem class serves as a bot implementation to create "work items" on a
# notion database using information of a GitHub issue.
#
# <br>
# <b>Example</b>
#
# options = {
# read_options: {
# connection: {
# host: "localhost",
# port: 5432,
# dbname: "bas",
# user: "postgres",
# password: "postgres"
# },
# db_table: "github_issues",
# tag: "CreateWorkItemRequest"
# },
# process_options: {
# database_id: "notion database id",
# secret: "notion secret",
# domain: "domain association",
# status: "default status",
# work_item_type: "work_item_type",
# project: "project id"
# },
# write_options: {
# connection: {
# host: "localhost",
# port: 5432,
# dbname: "bas",
# user: "postgres",
# password: "postgres"
# },
# db_table: "github_issues",
# tag: "CreateWorkItem"
# }
# }
#
# bot = Bot::VerifyIssueExistanceInNotion.new(options)
# bot.execute
#
class CreateWorkItem < Bot::Base
include Utils::Notion::Types

UPDATE_REQUEST = "UpdateWorkItemRequest"

# read function to execute the PostgresDB Read component
#
def read
reader = Read::Postgres.new(read_options.merge(conditions))

reader.execute
end

# process function to execute the Notion utility to create work items on a notion
# database
#
def process
return { success: { created: nil } } if unprocessable_response

response = Utils::Notion::Request.execute(params)

if response.code == 200
{ success: { issue: read_response.data["issue"], notion_wi: response["id"] } }
else
{ error: { message: response.parsed_response, status_code: response.code } }
end
end

# write function to execute the PostgresDB write component
#
def write
options = write_options.merge({ tag: })

write = Write::Postgres.new(options, process_response)

write.execute
end

private

def conditions
{
where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
params: [false, read_options[:tag], "unprocessed"]
}
end

def params
{
endpoint: "pages",
secret: process_options[:secret],
method: "post",
body:
}
end

def body
{
parent: { database_id: process_options[:database_id] },
properties:
}
end

def properties # rubocop:disable Metrics/AbcSize
{
"Responsible domain": select(process_options[:domain]),
"Github Issue id": rich_text(read_response.data["issue"]["id"].to_s),
"Status": status(process_options[:status]),
"Detail": title(read_response.data["issue"]["title"])
}.merge(work_item_type)
end

def work_item_type
case process_options[:work_item_type]
when "activity" then { "Activity": relation(process_options[:activity]) }
when "project" then { "Project": relation(process_options[:project]) }
else {}
end
end

def tag
return write_options[:tag] if process_response[:success].nil? || process_response[:success][:notion_wi].nil?

UPDATE_REQUEST
end
end
end
125 changes: 125 additions & 0 deletions lib/bas/bot/fetch_github_issues.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# frozen_string_literal: true

require_relative "./base"
require_relative "../read/default"
require_relative "../utils/github/octokit_client"
require_relative "../write/postgres"

module Bot
##
# The Bot::FetchGithubIssues class serves as a bot implementation to fetch GitHub issues from a
# repository and write them on a PostgresDB table with a specific format.
#
# <br>
# <b>Example</b>
#
# options = {
# read_options: {
# connection: {
# host: "localhost",
# port: 5432,
# dbname: "bas",
# user: "postgres",
# password: "postgres"
# },
# db_table: "github_issues",
# tag: "FetchGithubIssues",
# avoid_process: true
# },
# process_options: {
# private_pem: "Github App private token",
# app_id: "Github App id",
# repo: "repository name",
# filters: "hash with filters",
# organization: "GitHub organization name"
# },
# write_options: {
# connection: {
# host: "localhost",
# port: 5432,
# dbname: "bas",
# user: "postgres",
# password: "postgres"
# },
# db_table: "github_issues",
# tag: "FetchGithubIssues"
# }
# }
#
# bot = Bot::FetchGithubIssues.new(options)
# bot.execute
#
class FetchGithubIssues < Bot::Base
ISSUE_PARAMS = %i[id html_url title body labels state created_at updated_at].freeze
PER_PAGE = 100

# read function to execute the PostgresDB Read component
#
def read
reader = Read::Postgres.new(read_options.merge(conditions))

reader.execute
end

# Process function to request GitHub issues using the octokit utility
#
def process
octokit = Utils::Github::OctokitClient.new(params).execute

if octokit[:client]
repo_issues = octokit[:client].issues(@process_options[:repo], filters)

issues = normalize_response(repo_issues)

{ success: { issues: } }
else
{ error: octokit[:error] }
end
end

# Write function to execute the PostgresDB write component
#
def write
write = Write::Postgres.new(write_options, process_response)

write.execute
end

private

def conditions
{
where: "tag=$1 ORDER BY inserted_at DESC",
params: [read_options[:tag]]
}
end

def params
{
private_pem: process_options[:private_pem],
app_id: process_options[:app_id],
method: process_options[:method],
method_params: process_options[:method_params],
organization: process_options[:organization]
}
end

def filters
default_filter = { per_page: PER_PAGE }

filters = @process_options[:filters]
filters = filters.merge({ since: read_response.inserted_at }) unless read_response.nil?

filters.is_a?(Hash) ? default_filter.merge(filters) : default_filter
end

def normalize_response(issues)
issues.map do |issue|
ISSUE_PARAMS.reduce({}) do |hash, param|
hash.merge({ param => issue.send(param) })
.merge({ assignees: issue.assignees.map(&:login) })
end
end
end
end
end
Loading

0 comments on commit 4dc8294

Please sign in to comment.