Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solve #29 - Refactor PTO today Use Case #36

Merged
merged 14 commits into from
May 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Exceptions
# It inherits from StandardError # and allows developers to raise a specific exception when a required function
# remains unimplemented in a subclass.
#
class FunctionNotImplemented < StandardError
class FunctionNotImplement < StandardError
FelipeGuzmanSierra marked this conversation as resolved.
Show resolved Hide resolved
# Initializes the exception with an optional custom error message.
#
def initialize(message = "The function haven't been implemented yet.")
Expand Down
6 changes: 3 additions & 3 deletions lib/bas/formatter/base.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative "../domain/exceptions/function_not_implemented"
require_relative "../domain/exceptions/function_not_implement"
require "erb"

module Formatter
Expand Down Expand Up @@ -28,12 +28,12 @@ def initialize(config = {})
# * <tt>List<Domain::></tt> domain_data: List of specific domain objects depending on the use case.
#
# <br>
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplement</tt> when missing implementation.
#
# <b>returns</b> <tt>String</tt> Formatted payload suitable for a Process::Base implementation.
#
def format(_domain_data)
raise Domain::Exceptions::FunctionNotImplemented
raise Domain::Exceptions::FunctionNotImplement
end

protected
Expand Down
2 changes: 1 addition & 1 deletion lib/bas/process/base.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative "../domain/exceptions/function_not_implemented"
require_relative "../domain/exceptions/function_not_implement"
require_relative "./types/response"

module Process
Expand Down
10 changes: 5 additions & 5 deletions lib/bas/read/base.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative "../domain/exceptions/function_not_implemented"
require_relative "../domain/exceptions/function_not_implement"

module Read
##
Expand All @@ -21,10 +21,10 @@ def initialize(config)
# Must be overridden by subclasses, with specific logic based on the use case.
#
# <br>
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplement</tt> when missing implementation.
#
def execute
raise Domain::Exceptions::FunctionNotImplemented
raise Domain::Exceptions::FunctionNotImplement
end

protected
Expand All @@ -34,10 +34,10 @@ def execute
# Must be overridden by subclasses, with specific logic based on the use case.
#
# <br>
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplement</tt> when missing implementation.
#
def read(*_filters)
raise Domain::Exceptions::FunctionNotImplemented
raise Domain::Exceptions::FunctionNotImplement
end
end
end
6 changes: 3 additions & 3 deletions lib/bas/read/postgres/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
require_relative "./helper"

module Read
module Postgres
module PostgresBas
##
# This class is an implementation of the Read::Base interface, specifically designed
# for reading data from Postgres.
Expand All @@ -25,9 +25,9 @@ def read(query)

pg_result = execute_query(pg_connection, query)

postgres_response = Read::Postgres::Types::Response.new(pg_result)
postgres_response = Read::PostgresBas::Types::Response.new(pg_result)

Read::Postgres::Helper.validate_response(postgres_response)
Read::PostgresBas::Helper.validate_response(postgres_response)
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/bas/read/postgres/helper.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Read
module Postgres
module PostgresBas
##
# Provides common fuctionalities along the Read::Postgres domain.
#
Expand Down
2 changes: 1 addition & 1 deletion lib/bas/read/postgres/types/response.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Read
module Postgres
module PostgresBas
module Types
##
# Represents a response received from the Postgres API. It encapsulates essential information about the response,
Expand Down
2 changes: 1 addition & 1 deletion lib/bas/read/postgres/use_case/pto_today.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require_relative "../base"

module Read
module Postgres
module PostgresBas
##
# This class is an implementation of the Read::Postgres::Base interface, specifically designed
# for reading Paid Time Off (PTO) data from a Postgres Database.
Expand Down
6 changes: 3 additions & 3 deletions lib/bas/serialize/base.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative "../domain/exceptions/function_not_implemented"
require_relative "../domain/exceptions/function_not_implement"

module Serialize
##
Expand All @@ -18,13 +18,13 @@ module Base
#
# <br>
#
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplement</tt> when missing implementation.
# <br>
#
# <b>returns</b> <tt>List<Domain::></tt> Serialize list of data, ready to be formatted.
#
def execute(_response)
raise Domain::Exceptions::FunctionNotImplemented
raise Domain::Exceptions::FunctionNotImplement
end
end
end
2 changes: 1 addition & 1 deletion lib/bas/use_cases/use_cases.rb
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ def self.notify_next_week_pto_from_notion_to_discord(options)
# https://api.slack.com/messaging/webhooks#create_a_webhook
#
def self.notify_pto_from_postgres_to_slack(options)
read = Read::Postgres::PtoToday.new(options[:read_options])
read = Read::PostgresBas::PtoToday.new(options[:read_options])
serialize = Serialize::Postgres::PtoToday.new
formatter = Formatter::Pto.new(options[:format_options])
process = Process::Slack::Implementation.new(options[:process_options])
Expand Down
8 changes: 4 additions & 4 deletions lib/bas/write/base.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative "../domain/exceptions/function_not_implemented"
require_relative "../domain/exceptions/function_not_implement"

module Write
##
Expand All @@ -21,16 +21,16 @@ def initialize(config = {})
# Must be overridden by subclasses, with specific logic based on the use case.
#
# <br>
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplement</tt> when missing implementation.
#
def execute(_process_response)
raise Domain::Exceptions::FunctionNotImplemented
raise Domain::Exceptions::FunctionNotImplement
end

protected

def write(_method, _data)
raise Domain::Exceptions::FunctionNotImplemented
raise Domain::Exceptions::FunctionNotImplement
end
end
end
44 changes: 44 additions & 0 deletions lib/v2/bot/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require_relative "../utils/exceptions/function_not_implemented"
require_relative "../utils/exceptions/invalid_process_response"

module Bot
##
# The Bot::Base class serves as the foundation for implementing specific bots. Operating
# as an interface, this class defines essential attributes and methods, providing a blueprint
# for creating custom bots formed by a Read, Process, and Write components.
#
class Base
attr_reader :read_options, :process_options, :write_options

def initialize(config)
@read_options = config[:read_options]
@process_options = config[:process_options]
@write_options = config[:write_options]
end

def execute
read_response = read

process_response = process(read_response)
raise Utils::Exceptions::InvalidProcessResponse unless process_response.is_a?(Hash)

write(process_response)
end

protected

def read
raise Utils::Exceptions::FunctionNotImplemented
end

def process(_read_response)
raise Utils::Exceptions::FunctionNotImplemented
end

def write(_process_response)
raise Utils::Exceptions::FunctionNotImplemented
end
end
end
127 changes: 127 additions & 0 deletions lib/v2/bot/fetch_ptos_from_notion.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# frozen_string_literal: true

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

module Bot
##
# The Bot::FetchPtosFromNotion class serves as a bot implementation to read PTO's from a
# notion database and write them on a PostgresDB table with a specific format.
#
# <br>
# <b>Example</b>
#
# options = {
# process_options: {
# database_id: "notion database id",
# secret: "notion secret"
# },
# write_options: {
# connection: {
# host: "host",
# port: 5432,
# dbname: "bas",
# user: "postgres",
# password: "postgres"
# },
# db_table: "pto",
# bot_name: "FetchPtosFromNotion"
# }
# }
#
# bot = Bot::FetchPtosFromNotion.new(options)
# bot.execute
#
class FetchPtosFromNotion < Bot::Base
# Read function to execute the default Read component
#
def read
reader = Read::Default.new

reader.execute
end

# Process function to execute the Notion utility to fetch PTO's from the notion database
#
def process(_read_response)
response = Utils::Notion::Request.execute(params)

if response.code == 200
ptos_list = normalize_response(response.parsed_response["results"])

{ success: { ptos: ptos_list } }
else
{ error: { message: response.parsed_response, status_code: response.code } }
end
end

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

write.execute
end

private

def params
{
endpoint: "databases/#{process_options[:database_id]}/query",
secret: process_options[:secret],
method: "post",
body:
}
end

def body
today = Time.now.utc.strftime("%F").to_s

{
filter: {
"and": [
{ property: "StartDateTime", date: { on_or_before: today } },
{ property: "EndDateTime", date: { on_or_after: today } }
]
}
}
end

def normalize_response(results)
return [] if results.nil?

results.map do |pto|
pto_fields = pto["properties"]

{
"Name" => extract_description_field_value(pto_fields["Description"]),
"StartDateTime" => extract_date_field_value(pto_fields["StartDateTime"]),
"EndDateTime" => extract_date_field_value(pto_fields["EndDateTime"])
}
end
end

def extract_description_field_value(data)
names = data["title"].map { |name| name["plain_text"] }

names.join(" ")
end

def extract_date_field_value(date)
{
from: extract_start_date(date),
to: extract_end_date(date)
}
end

def extract_start_date(data)
data["date"]["start"]
end

def extract_end_date(data)
data["date"]["end"]
end
end
end
Loading
Loading