diff --git a/lib/bas/bot/create_work_item.rb b/lib/bas/bot/create_work_item.rb index 8ae532d..9f7f991 100644 --- a/lib/bas/bot/create_work_item.rb +++ b/lib/bas/bot/create_work_item.rb @@ -56,6 +56,7 @@ class CreateWorkItem < Bot::Base include Utils::Notion::Types UPDATE_REQUEST = "UpdateWorkItemRequest" + STATUS = "Backlog" # read function to execute the PostgresDB Read component # @@ -117,17 +118,17 @@ def body 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]), + "Responsible domain": select(read_response.data["domain"]), + "Github Issue Id": rich_text(read_response.data["issue"]["id"].to_s), + "Status": status(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]) } + case read_response.data["work_item_type"] + when "activity" then { "Activity": relation(read_response.data["type_id"]) } + when "project" then { "Project": relation(read_response.data["type_id"]) } else {} end end diff --git a/lib/bas/bot/fetch_github_issues.rb b/lib/bas/bot/fetch_github_issues.rb index 4df8a8b..e81a39f 100644 --- a/lib/bas/bot/fetch_github_issues.rb +++ b/lib/bas/bot/fetch_github_issues.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require_relative "./base" -require_relative "../read/default" +require_relative "../read/postgres" require_relative "../utils/github/octokit_client" require_relative "../write/postgres" @@ -32,6 +32,15 @@ module Bot # repo: "repository name", # filters: "hash with filters", # organization: "GitHub organization name" + # connection: { + # host: "localhost", + # port: 5432, + # dbname: "bas", + # user: "postgres", + # password: "postgres" + # }, + # db_table: "github_issues", + # tag: "GithubIssueRequest" # }, # write_options: { # connection: { @@ -50,7 +59,7 @@ module Bot # bot.execute # class FetchGithubIssues < Bot::Base - ISSUE_PARAMS = %i[id html_url title body labels state created_at updated_at].freeze + ISSUE_PARAMS = %i[id html_url title body labels state created_at updated_at state].freeze PER_PAGE = 100 # read function to execute the PostgresDB Read component @@ -69,9 +78,9 @@ def process if octokit[:client] repo_issues = octokit[:client].issues(@process_options[:repo], filters) - issues = normalize_response(repo_issues) + normalize_response(repo_issues).each { |issue| create_request(issue) } - { success: { issues: } } + { success: { created: true } } else { error: octokit[:error] } end @@ -107,7 +116,7 @@ def params def filters default_filter = { per_page: PER_PAGE } - filters = @process_options[:filters] + 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 @@ -121,5 +130,18 @@ def normalize_response(issues) end end end + + def create_request(issue) + write_data = { + success: { + issue:, + work_item_type: process_options[:work_item_type], + type_id: process_options[:type_id], + domain: process_options[:domain] + } + } + + Write::Postgres.new(process_options, write_data).execute + end end end diff --git a/lib/bas/bot/format_do_bill_alert.rb b/lib/bas/bot/format_do_bill_alert.rb index 8508c11..7514d40 100644 --- a/lib/bas/bot/format_do_bill_alert.rb +++ b/lib/bas/bot/format_do_bill_alert.rb @@ -93,11 +93,9 @@ def message balance = read_response.data["billing"]["month_to_date_balance"] threshold = process_options[:threshold] - """:warning: The **DigitalOcean** daily usage was exceeded. - Current balance: #{balance} - Threshold: #{threshold} - Current daily usage: #{daily_usage.round(3)} - """ + ":warning: The **DigitalOcean** daily usage was exceeded. \ + Current balance: #{balance}, Threshold: #{threshold}, \ + Current daily usage: #{daily_usage.round(3)}" end end end diff --git a/lib/bas/bot/update_work_item.rb b/lib/bas/bot/update_work_item.rb index b34252d..69209e6 100644 --- a/lib/bas/bot/update_work_item.rb +++ b/lib/bas/bot/update_work_item.rb @@ -8,6 +8,8 @@ require_relative "../utils/notion/request" require_relative "../utils/notion/types" require_relative "../utils/notion/delete_page_blocks" +require_relative "../utils/notion/fetch_database_record" +require_relative "../utils/notion/update_db_page" require_relative "../write/postgres" module Bot @@ -53,6 +55,7 @@ class UpdateWorkItem < Bot::Base include Utils::Notion::Types DESCRIPTION = "Issue Description" + GITHUB_COLUMN = "Username" # read function to execute the PostgresDB Read component # @@ -67,11 +70,11 @@ def read def process return { success: { updated: nil } } if unprocessable_response - delete_wi - - response = Utils::Notion::Request.execute(params) + response = process_wi if response.code == 200 + update_assigness + { success: { issue: read_response.data["issue"] } } else { error: { message: response.parsed_response, status_code: response.code } } @@ -95,6 +98,12 @@ def conditions } end + def process_wi + delete_wi + + Utils::Notion::Request.execute(params) + end + def params { endpoint: "blocks/#{read_response.data["notion_wi"]}/children", @@ -121,12 +130,52 @@ def issue_reference end def delete_wi - params = { + options = { page_id: read_response.data["notion_wi"], secret: process_options[:secret] } - Utils::Notion::DeletePageBlocks.new(params).execute + Utils::Notion::DeletePageBlocks.new(options).execute + end + + def update_assigness + relation = users.map { |user| user_id(user) } + + options = { + page_id: read_response.data["notion_wi"], + secret: process_options[:secret], + body: { properties: { People: { relation: } }.merge(status) } + } + + Utils::Notion::UpdateDatabasePage.new(options).execute + end + + def users + options = { + database_id: process_options[:users_database_id], + secret: process_options[:secret], + body: { filter: { or: github_usernames } } + } + + Utils::Notion::FetchDatabaseRecord.new(options).execute + end + + def github_usernames + read_response.data["issue"]["assignees"].map do |username| + { property: GITHUB_COLUMN, rich_text: { equals: username } } + end + end + + def user_id(user) + relation = user.dig("properties", "People", "relation") + + relation.nil? ? {} : relation.first + end + + def status + return {} unless read_response.data["issue"]["state"] == "closed" + + { Status: { status: { name: "Done" } } } end end end diff --git a/lib/bas/bot/verify_issue_existance_in_notion.rb b/lib/bas/bot/verify_issue_existance_in_notion.rb index 433d1db..e53c566 100644 --- a/lib/bas/bot/verify_issue_existance_in_notion.rb +++ b/lib/bas/bot/verify_issue_existance_in_notion.rb @@ -50,6 +50,7 @@ module Bot # class VerifyIssueExistanceInNotion < Bot::Base NOT_FOUND = "not found" + NOTION_PROPERTY = "Github Issue Id" # read function to execute the PostgresDB Read component # @@ -70,7 +71,7 @@ def process if response.code == 200 result = response.parsed_response["results"].first - { success: { issue: read_response.data["request"], notion_wi: notion_wi_id(result) } } + { success: read_response.data.merge({ notion_wi: notion_wi_id(result) }) } else { error: { message: response.parsed_response, status_code: response.code } } end @@ -107,8 +108,8 @@ def params def body { filter: { - property: "Github Issue id", - rich_text: { equals: read_response.data["request"]["id"].to_s } + property: NOTION_PROPERTY, + rich_text: { equals: read_response.data["issue"]["id"].to_s } } } end diff --git a/lib/bas/bot/write_github_issue_requests.rb b/lib/bas/bot/write_github_issue_requests.rb deleted file mode 100644 index a07beb5..0000000 --- a/lib/bas/bot/write_github_issue_requests.rb +++ /dev/null @@ -1,96 +0,0 @@ -# frozen_string_literal: true - -require_relative "./base" -require_relative "../read/postgres" -require_relative "../write/postgres" - -module Bot - ## - # The Bot::WriteGithubIssueRequests class serves as a bot implementation to write GitHub issues - # request to be sent individually. - # - #
- # Example - # - # options = { - # read_options: { - # connection: { - # host: "localhost", - # port: 5432, - # dbname: "bas", - # user: "postgres", - # password: "postgres" - # }, - # db_table: "github_issues", - # tag: "FetchGithubIssues" - # }, - # process_options: { - # connection: { - # host: "localhost", - # port: 5432, - # dbname: "bas", - # user: "postgres", - # password: "postgres" - # }, - # db_table: "github_issues", - # tag: "GithubIssueRequest" - # }, - # write_options: { - # connection: { - # host: "localhost", - # port: 5432, - # dbname: "bas", - # user: "postgres", - # password: "postgres" - # }, - # db_table: "github_issues", - # tag: "WriteGithubIssueRequests" - # } - # } - # - # bot = Bot::WriteGithubIssueRequests.new(options) - # bot.execute - # - class WriteGithubIssueRequests < Bot::Base - # read function to execute the PostgresDB Read component - # - def read - reader = Read::Postgres.new(read_options.merge(conditions)) - - reader.execute - end - - # Process function to write GitHub issues requests. - # - def process - return { success: { created: nil } } if unprocessable_response - - read_response.data["issues"].each { |request| create_request(request) } - - { success: { created: true } } - 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: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC", - params: [false, read_options[:tag], "unprocessed"] - } - end - - def create_request(request) - write_data = { success: { request: } } - - Write::Postgres.new(process_options, write_data).execute - end - end -end diff --git a/lib/bas/utils/notion/fetch_database_record.rb b/lib/bas/utils/notion/fetch_database_record.rb new file mode 100644 index 0000000..d8d98e1 --- /dev/null +++ b/lib/bas/utils/notion/fetch_database_record.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "httparty" +require_relative "request" + +module Utils + module Notion + ## + # This module is a Notion utility for fetching record from a database. + # + class FetchDatabaseRecord + # Implements the fetch page process logic to Notion. + # + #
+ # Params: + # * database_id Id of the notion database. + # * secret Notion secret. + # * body Body with the filters. + # + #
+ # returns HTTParty::Response + # + # + def initialize(options) + @options = options + end + + def execute + records = Utils::Notion::Request.execute(params) + + records.parsed_response["results"] || [] + end + + private + + def params + { + endpoint: "databases/#{@options[:database_id]}/query", + secret: @options[:secret], + method: "post", + body: @options[:body] + } + end + end + end +end diff --git a/lib/bas/utils/notion/types.rb b/lib/bas/utils/notion/types.rb index ec27190..10e38f2 100644 --- a/lib/bas/utils/notion/types.rb +++ b/lib/bas/utils/notion/types.rb @@ -7,10 +7,6 @@ module Notion # filter or create. # module Types - def multi_select(name) - { "multi_select" => [{ "name" => name }] } - end - def relation(id) { relation: [{ id: }] } end diff --git a/lib/bas/utils/notion/update_db_page.rb b/lib/bas/utils/notion/update_db_page.rb new file mode 100644 index 0000000..2fe2a72 --- /dev/null +++ b/lib/bas/utils/notion/update_db_page.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "httparty" +require_relative "request" + +module Utils + module Notion + ## + # This module is a Notion utility for updating database properties. + # + class UpdateDatabasePage + # Implements the update database properties process logic to Notion. + # + #
+ # Params: + # * page_id Id of the notion page. + # * secret Notion secret. + # * body Request body with the properties to be updated. + # + #
+ # returns HTTParty::Response + # + # + def initialize(options) + @options = options + end + + def execute + Utils::Notion::Request.execute(params) + end + + private + + def params + { + endpoint: "pages/#{@options[:page_id]}", + secret: @options[:secret], + method: "patch", + body: @options[:body] + } + end + end + end +end diff --git a/spec/bas/bot/create_work_item_spec.rb b/spec/bas/bot/create_work_item_spec.rb index 9ff3925..d1fb2ef 100644 --- a/spec/bas/bot/create_work_item_spec.rb +++ b/spec/bas/bot/create_work_item_spec.rb @@ -111,18 +111,29 @@ end let(:issue_request) { { "issue" => issue } } + let(:issue_request_activity) { { "issue" => issue, "work_item_type" => "activity", "type_id": "id" } } + let(:issue_request_project) { { "issue" => issue, "work_item_type" => "project", "type_id": "id" } } let(:error_response) { { "object" => "error", "status" => 404, "message" => "not found" } } let(:response) { double("http_response") } before do - @bot.read_response = Read::Types::Response.new(1, issue_request, "date") - allow(HTTParty).to receive(:send).and_return(response) end - it "creates a work item on notion and returns its notion id" do + it "creates a work item on notion and returns its notion id for an activity" do + @bot.read_response = Read::Types::Response.new(1, issue_request_activity, "date") + allow(response).to receive(:code).and_return(200) + allow(response).to receive(:[]).and_return("123456789") + + processed = @bot.process + + expect(processed).to eq({ success: { issue:, notion_wi: "123456789" } }) + end + + it "creates a work item on notion and returns its notion id for a project" do + @bot.read_response = Read::Types::Response.new(1, issue_request_project, "date") allow(response).to receive(:code).and_return(200) allow(response).to receive(:[]).and_return("123456789") @@ -132,6 +143,7 @@ end it "returns an error hash with the error message" do + @bot.read_response = Read::Types::Response.new(1, issue_request, "date") allow(response).to receive(:code).and_return(404) allow(response).to receive(:parsed_response).and_return(error_response) diff --git a/spec/bas/bot/fetch_github_issues_spec.rb b/spec/bas/bot/fetch_github_issues_spec.rb index a474816..422a06e 100644 --- a/spec/bas/bot/fetch_github_issues_spec.rb +++ b/spec/bas/bot/fetch_github_issues_spec.rb @@ -115,25 +115,17 @@ ]}" end - let(:formatted_issues) do - { - id: "12345", - assignees: [], - html_url: "https://github.com/repo/issues", - title: "Issue", - body: "simple description", - labels: [], - state: "open", - created_at: "2024-07-24 20:13:18 UTC", - updated_at: "2024-07-24 20:13:18 UTC" - } - end - let(:error_response) { { "object" => "error", "status" => 404, "message" => "not found" } } + let(:pg_conn) { instance_double(PG::Connection) } let(:octokit) { double("octokit") } before do + pg_result = instance_double(PG::Result) + + allow(PG::Connection).to receive(:new).and_return(pg_conn) + allow(pg_conn).to receive(:exec_params).and_return(pg_result) + @bot.read_response = Read::Types::Response.new allow(OpenSSL::PKey::RSA).to receive(:new).and_return("private key") @@ -149,7 +141,7 @@ processed = @bot.process - expect(processed).to eq({ success: { issues: [formatted_issues] } }) + expect(processed).to eq({ success: { created: true } }) end it "returns a success hash with the list of formatted birthdays" do @@ -159,7 +151,7 @@ processed = @bot.process - expect(processed).to eq({ success: { issues: [formatted_issues] } }) + expect(processed).to eq({ success: { created: true } }) end it "returns an error hash with the error message" do diff --git a/spec/bas/bot/format_do_bill_alert_spec.rb b/spec/bas/bot/format_do_bill_alert_spec.rb index 4096c5f..d279d22 100644 --- a/spec/bas/bot/format_do_bill_alert_spec.rb +++ b/spec/bas/bot/format_do_bill_alert_spec.rb @@ -86,7 +86,7 @@ let(:formatted_alert) do daily_usage = 800.0 / Time.now.utc.mday - ":warning: The **DigitalOcean** daily usage was exceeded.\n Current balance: 800\n Threshold: 7\n Current daily usage: #{daily_usage.round(3)}\n " # rubocop:disable Layout/LineLength + ":warning: The **DigitalOcean** daily usage was exceeded. Current balance: 800, Threshold: 7, Current daily usage: #{daily_usage.round(3)}" # rubocop:disable Layout/LineLength end it "returns an empty success hash when the billing list is empty" do diff --git a/spec/bas/bot/update_work_item_spec.rb b/spec/bas/bot/update_work_item_spec.rb index e5e0abb..bc849b4 100644 --- a/spec/bas/bot/update_work_item_spec.rb +++ b/spec/bas/bot/update_work_item_spec.rb @@ -95,11 +95,11 @@ { "id" => "12345", "body" => "simple description", - "state" => "open", + "state" => "closed", "title" => "Issue", "labels" => [], "html_url" => "https://github.com/repo/issues", - "assignees" => [], + "assignees" => ["GithubUsername"], "created_at" => "2024-07-24 20:13:18 UTC", "updated_at" => "2024-07-24 20:36:57 UTC" } diff --git a/spec/bas/bot/verify_issue_existance_in_notion_spec.rb b/spec/bas/bot/verify_issue_existance_in_notion_spec.rb index b8e0945..e799fab 100644 --- a/spec/bas/bot/verify_issue_existance_in_notion_spec.rb +++ b/spec/bas/bot/verify_issue_existance_in_notion_spec.rb @@ -106,7 +106,7 @@ } end - let(:issue_request) { { "request" => issue } } + let(:issue_request) { { "issue" => issue } } let(:error_response) { { "object" => "error", "status" => 404, "message" => "not found" } } @@ -124,7 +124,7 @@ processed = @bot.process - expect(processed).to eq({ success: { issue:, notion_wi: "123456789" } }) + expect(processed).to eq({ success: { "issue" => issue, notion_wi: "123456789" } }) end it "set the work item id as 'not found' when it doesn't exist on notion" do @@ -133,7 +133,7 @@ processed = @bot.process - expect(processed).to eq({ success: { issue:, notion_wi: "not found" } }) + expect(processed).to eq({ success: { "issue" => issue, notion_wi: "not found" } }) end it "returns an error hash with the error message" do diff --git a/spec/bas/bot/write_github_issue_requests_spec.rb b/spec/bas/bot/write_github_issue_requests_spec.rb deleted file mode 100644 index f993201..0000000 --- a/spec/bas/bot/write_github_issue_requests_spec.rb +++ /dev/null @@ -1,160 +0,0 @@ -# frozen_string_literal: true - -require "bas/bot/write_github_issue_requests" - -RSpec.describe Bot::WriteGithubIssueRequests do - before do - connection = { - host: "localhost", - port: 5432, - dbname: "bas", - user: "postgres", - password: "postgres" - } - - config = { - read_options: { - connection:, - db_table: "github_issues", - tag: "FetchGithubIssues" - }, - process_options: { - connection:, - db_table: "github_issues", - tag: "GithubIssueRequest" - }, - write_options: { - connection:, - db_table: "github_issues", - tag: "WriteGithubIssueRequests" - } - } - - @bot = described_class.new(config) - end - - describe "attributes and arguments" do - it { expect(described_class).to respond_to(:new).with(1).arguments } - - it { expect(@bot).to respond_to(:execute).with(0).arguments } - it { expect(@bot).to respond_to(:read).with(0).arguments } - it { expect(@bot).to respond_to(:process).with(0).arguments } - it { expect(@bot).to respond_to(:write).with(0).arguments } - - it { expect(@bot).to respond_to(:read_options) } - it { expect(@bot).to respond_to(:process_options) } - it { expect(@bot).to respond_to(:write_options) } - end - - describe ".read" do - let(:pg_conn) { instance_double(PG::Connection) } - let(:issues_results) do - "{\"issues\": [\ - {\"id\": \"12345\", \"body\": \"simple description\",\ - \"state\": \"open\", \"title\": \"Issue\", \"labels\": [],\ - \"html_url\":\"https://github.com/repo/issues\", \"assignees\": [],\ - \"created_at\":\"2024-07-24 20:13:18 UTC\",\ - \"updated_at\":\"2024-07-24 20:36:57 UTC\"}\ - ]}" - end - - let(:formatted_issues) do - { "issues" => [{ - "id" => "12345", - "body" => "simple description", - "state" => "open", - "title" => "Issue", - "labels" => [], - "html_url" => "https://github.com/repo/issues", - "assignees" => [], - "created_at" => "2024-07-24 20:13:18 UTC", - "updated_at" => "2024-07-24 20:36:57 UTC" - }] } - end - - before do - @pg_result = double - - allow(PG::Connection).to receive(:new).and_return(pg_conn) - allow(pg_conn).to receive(:exec_params).and_return(@pg_result) - allow(@pg_result).to receive(:values).and_return([[1, issues_results, "date"]]) - end - - it "read the notification from the postgres database" do - read = @bot.read - - expect(read).to be_a Read::Types::Response - expect(read.data).to be_a Hash - expect(read.data).to_not be_nil - expect(read.data).to eq(formatted_issues) - end - end - - describe ".process" do - let(:pg_conn) { instance_double(PG::Connection) } - - let(:issues_request) do - { "issues" => [{ - "id" => "12345", - "assignees" => [], - "html_url" => "https://github.com/repo/issues", - "title" => "Issue", - "body" => "simple description", - "labels" => [], - "state" => "open", - "created_at" => "2024-07-24 20:13:18 UTC", - "updated_at" => "2024-07-24 20:13:18 UTC" - }] } - end - - let(:response) { double("http_response") } - - before do - pg_result = instance_double(PG::Result) - - allow(PG::Connection).to receive(:new).and_return(pg_conn) - allow(pg_conn).to receive(:exec_params).and_return(pg_result) - - @bot.read_response = Read::Types::Response.new - - allow(HTTParty).to receive(:send).and_return(response) - end - - it "returns an empty success hash when the media list is empty" do - @bot.read_response = Read::Types::Response.new(1, { "issues" => [] }, "date") - - expect(@bot.process).to eq({ success: { created: nil } }) - end - - it "returns an empty success hash when the record was not found" do - @bot.read_response = Read::Types::Response.new(1, nil, "date") - - expect(@bot.process).to eq({ success: { created: nil } }) - end - - it "returns a success hash after writting the requests in the shared storage" do - @bot.read_response = Read::Types::Response.new(1, issues_request, "date") - - processed = @bot.process - - expect(processed).to eq({ success: { created: true } }) - end - end - - describe ".write" do - let(:pg_conn) { instance_double(PG::Connection) } - - before do - pg_result = instance_double(PG::Result) - - allow(PG::Connection).to receive(:new).and_return(pg_conn) - allow(pg_conn).to receive(:exec_params).and_return(pg_result) - end - - it "save the process success response in a postgres table" do - @bot.process_response = { success: { created: true } } - - expect(@bot.write).to_not be_nil - end - end -end