bas 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile +6 -0
- data/README.md +2 -2
- data/lib/bas/domain/issue.rb +22 -0
- data/lib/bas/domain/pto.rb +45 -4
- data/lib/bas/fetcher/github/base.rb +57 -0
- data/lib/bas/fetcher/github/types/response.rb +27 -0
- data/lib/bas/fetcher/github/use_case/repo_issues.rb +17 -0
- data/lib/bas/formatter/pto.rb +19 -22
- data/lib/bas/mapper/github/issues.rb +57 -0
- data/lib/bas/mapper/notion/pto_today.rb +17 -14
- data/lib/bas/mapper/postgres/pto_today.rb +2 -2
- data/lib/bas/use_cases/use_cases.rb +2 -0
- data/lib/bas/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97cc25416cff1a50cad38e85bbc1e7389693ccf2f4004a1c5fb8d5049162b407
|
4
|
+
data.tar.gz: efef410e0d776c8808655dbd20cb7500c81bcf53c16f072a7103d5827def9e58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7d915c41461667d858750e117e63468fba9cdea4fac59dfd80b0dcf1a7c4ce749b773378b06a81e2cdfafbefce92a61a2ee8415a80edd6a1e1a61380c5bdda9
|
7
|
+
data.tar.gz: fa174b7e86b013055efabe32b7dc7c2f213bd8dd18600c35e655d4c7512ce8e8f7ed4e104c091ca5b4def7180d4499a1c37f3dd5f4ad5e0a936ac79d716f03b1
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.2.0 (04.04.2024)
|
4
|
+
- [Add a GitHub fetcher component](https://github.com/kommitters/bas/issues/6)
|
5
|
+
- [Allow daily hours pto for more than one day pto](https://github.com/kommitters/bas/issues/8)
|
6
|
+
|
3
7
|
## 0.1.0 (26.03.2024)
|
4
8
|
- [Add the BNS gem code](https://github.com/kommitters/bas/issues/1)
|
data/Gemfile
CHANGED
@@ -5,10 +5,16 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in bas.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
+
gem "jwt", "~> 2.8.1"
|
9
|
+
|
8
10
|
gem "rake", "~> 13.0"
|
9
11
|
|
10
12
|
gem "net-imap", "~> 0.4.10"
|
11
13
|
gem "net-smtp", "~> 0.4.0.1"
|
14
|
+
|
15
|
+
gem "octokit", "~> 8.1.0"
|
16
|
+
gem "openssl", "~> 3.2"
|
17
|
+
|
12
18
|
gem "rspec", "~> 3.0"
|
13
19
|
gem "rubocop", "~> 1.21"
|
14
20
|
gem "simplecov", require: false, group: :test
|
data/README.md
CHANGED
@@ -6,11 +6,11 @@ The underlying idea is to develop generic components that can serve a wide range
|
|
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)
|
9
|
-
![Build Badge](https://img.shields.io/github/actions/workflow/status/kommitters/bas/ci.yml?
|
9
|
+
![Build Badge](https://img.shields.io/github/actions/workflow/status/kommitters/bas/ci.yml?style=for-the-badge)
|
10
10
|
[![Coverage Status](https://img.shields.io/coveralls/github/kommitters/bas?style=for-the-badge)](https://coveralls.io/github/kommitters/bas?branch=main)
|
11
11
|
![GitHub License](https://img.shields.io/github/license/kommitters/bas?style=for-the-badge)
|
12
12
|
[![OpenSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/kommitters/bas?label=openssf%20scorecard&style=for-the-badge)](https://api.securityscorecards.dev/projects/github.com/kommitters/bas)
|
13
|
-
[![OpenSSF Best Practices](https://img.shields.io/cii/summary/
|
13
|
+
[![OpenSSF Best Practices](https://img.shields.io/cii/summary/8713?label=openssf%20best%20practices&style=for-the-badge)](https://bestpractices.coreinfrastructure.org/projects/8713)
|
14
14
|
|
15
15
|
## Installation
|
16
16
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domain
|
4
|
+
##
|
5
|
+
# The Domain::Issue class provides a domain-specific representation of a Github issue object.
|
6
|
+
# It encapsulates information about a repository issue, including the title, state, assignees,
|
7
|
+
# description, and the repository url.
|
8
|
+
#
|
9
|
+
class Issue
|
10
|
+
attr_reader :title, :state, :assignees, :description, :url
|
11
|
+
|
12
|
+
ATTRIBUTES = %w[title state assignees description url].freeze
|
13
|
+
|
14
|
+
def initialize(title, state, assignees, body, url)
|
15
|
+
@title = title
|
16
|
+
@state = state
|
17
|
+
@assignees = assignees
|
18
|
+
@description = body
|
19
|
+
@url = url
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/bas/domain/pto.rb
CHANGED
@@ -7,9 +7,9 @@ module Domain
|
|
7
7
|
# the start date, and the end date of the time off period.
|
8
8
|
#
|
9
9
|
class Pto
|
10
|
-
attr_reader :individual_name, :
|
10
|
+
attr_reader :individual_name, :start_date_from, :start_date_to, :end_date_from, :end_date_to
|
11
11
|
|
12
|
-
ATTRIBUTES = %w[individual_name
|
12
|
+
ATTRIBUTES = %w[individual_name start_date_from start_date_to end_date_from end_date_to].freeze
|
13
13
|
|
14
14
|
# Initializes a Domain::Pto instance with the specified individual name, start date, and end date.
|
15
15
|
#
|
@@ -21,8 +21,49 @@ module Domain
|
|
21
21
|
#
|
22
22
|
def initialize(individual_name, start_date, end_date)
|
23
23
|
@individual_name = individual_name
|
24
|
-
|
25
|
-
@
|
24
|
+
|
25
|
+
@start_date_from = start_date[:from]
|
26
|
+
@start_date_to = start_date[:to]
|
27
|
+
@end_date_from = end_date[:from]
|
28
|
+
@end_date_to = end_date[:to]
|
29
|
+
end
|
30
|
+
|
31
|
+
def same_day?
|
32
|
+
start_date = extract_date(start_date_from)
|
33
|
+
end_date = extract_date(end_date_from)
|
34
|
+
|
35
|
+
start_date == end_date
|
36
|
+
end
|
37
|
+
|
38
|
+
def format_timezone(timezone)
|
39
|
+
@start_date_from = set_timezone(start_date_from, timezone)
|
40
|
+
@start_date_to = set_timezone(start_date_to, timezone)
|
41
|
+
@end_date_from = set_timezone(end_date_from, timezone)
|
42
|
+
@end_date_to = set_timezone(end_date_to, timezone)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def extract_date(date)
|
48
|
+
return if date.nil?
|
49
|
+
|
50
|
+
date.strftime("%F")
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_date_time(date, timezone)
|
54
|
+
return if date.nil?
|
55
|
+
|
56
|
+
date_time = date.include?("T") ? date : "#{date}T00:00:00.000#{timezone}"
|
57
|
+
|
58
|
+
DateTime.parse(date_time).to_time
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_timezone(date, timezone)
|
62
|
+
return if date.nil?
|
63
|
+
|
64
|
+
date_time = build_date_time(date, timezone)
|
65
|
+
|
66
|
+
Time.at(date_time, in: timezone)
|
26
67
|
end
|
27
68
|
end
|
28
69
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "octokit"
|
4
|
+
require "openssl"
|
5
|
+
require "jwt"
|
6
|
+
|
7
|
+
require_relative "../base"
|
8
|
+
require_relative "./types/response"
|
9
|
+
|
10
|
+
module Fetcher
|
11
|
+
module Github
|
12
|
+
##
|
13
|
+
# This class is an implementation of the Fetcher::Base interface, specifically designed
|
14
|
+
# for fetching data from a GitHub repository.
|
15
|
+
#
|
16
|
+
class Base < Fetcher::Base
|
17
|
+
protected
|
18
|
+
|
19
|
+
# Implements the data fetching logic to get data from a Github repository.
|
20
|
+
# It connects to Github using the octokit gem, authenticates with a github app,
|
21
|
+
# request the data and returns a validated response.
|
22
|
+
#
|
23
|
+
def execute(method, *filter)
|
24
|
+
octokit_response = octokit.public_send(method, *filter)
|
25
|
+
|
26
|
+
Fetcher::Github::Types::Response.new(octokit_response)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def octokit
|
32
|
+
Octokit::Client.new(bearer_token: access_token)
|
33
|
+
end
|
34
|
+
|
35
|
+
def access_token
|
36
|
+
app = Octokit::Client.new(client_id: config[:app_id], bearer_token: jwt)
|
37
|
+
|
38
|
+
app.create_app_installation_access_token(config[:installation_id])[:token]
|
39
|
+
end
|
40
|
+
|
41
|
+
def jwt
|
42
|
+
private_pem = File.read(config[:secret_path])
|
43
|
+
private_key = OpenSSL::PKey::RSA.new(private_pem)
|
44
|
+
|
45
|
+
JWT.encode(jwt_payload, private_key, "RS256")
|
46
|
+
end
|
47
|
+
|
48
|
+
def jwt_payload
|
49
|
+
{
|
50
|
+
iat: Time.now.to_i - 60,
|
51
|
+
exp: Time.now.to_i + (10 * 60),
|
52
|
+
iss: config[:app_id]
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fetcher
|
4
|
+
module Github
|
5
|
+
module Types
|
6
|
+
##
|
7
|
+
# Represents a response received from the Octokit Github client. It encapsulates essential
|
8
|
+
# information about the response, providing a structured way to handle and analyze
|
9
|
+
# it's responses.
|
10
|
+
class Response
|
11
|
+
attr_reader :status_code, :message, :results
|
12
|
+
|
13
|
+
def initialize(response)
|
14
|
+
if response.empty?
|
15
|
+
@status_code = 404
|
16
|
+
@message = "no result were found"
|
17
|
+
@results = []
|
18
|
+
else
|
19
|
+
@status_code = 200
|
20
|
+
@message = "success"
|
21
|
+
@results = response
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../base"
|
4
|
+
|
5
|
+
module Fetcher
|
6
|
+
module Github
|
7
|
+
##
|
8
|
+
# This class is an implementation of the Fetcher::Github::Base interface, specifically designed
|
9
|
+
# for fetching issues from a Github repository.
|
10
|
+
#
|
11
|
+
class RepoIssues < Github::Base
|
12
|
+
def fetch
|
13
|
+
execute("list_issues", config[:repo])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/bas/formatter/pto.rb
CHANGED
@@ -38,6 +38,8 @@ module Formatter
|
|
38
38
|
def format(ptos_list)
|
39
39
|
raise Formatter::Exceptions::InvalidData unless ptos_list.all? { |pto| pto.is_a?(Domain::Pto) }
|
40
40
|
|
41
|
+
ptos_list.each { |pto| pto.format_timezone(@timezone) }
|
42
|
+
|
41
43
|
ptos_list.reduce("") do |payload, pto|
|
42
44
|
built_template = build_template(Domain::Pto::ATTRIBUTES, pto)
|
43
45
|
payload + format_message_by_case(built_template.gsub("\n", ""), pto)
|
@@ -47,42 +49,37 @@ module Formatter
|
|
47
49
|
private
|
48
50
|
|
49
51
|
def format_message_by_case(built_template, pto)
|
50
|
-
|
51
|
-
date_end = format_timezone(pto.end_date).strftime("%F")
|
52
|
-
|
53
|
-
if date_start == date_end
|
52
|
+
if pto.same_day?
|
54
53
|
interval = same_day_interval(pto)
|
55
|
-
day_message = today?(
|
54
|
+
day_message = today?(pto.start_date_from) ? "today" : "the day #{pto.start_date_from.strftime("%F")}"
|
56
55
|
|
57
56
|
"#{built_template} #{day_message} #{interval}\n"
|
58
57
|
else
|
59
|
-
|
58
|
+
start_date_interval = day_interval(pto.start_date_from, pto.start_date_to)
|
59
|
+
end_date_interval = day_interval(pto.end_date_from, pto.end_date_to)
|
60
|
+
|
61
|
+
"#{built_template} from #{start_date_interval} to #{end_date_interval}\n"
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
63
|
-
def
|
64
|
-
|
65
|
-
time_end = format_timezone(pto.end_date).strftime("%I:%M %P")
|
66
|
-
|
67
|
-
time_start == time_end ? "all day" : "from #{time_start} to #{time_end}"
|
68
|
-
end
|
65
|
+
def day_interval(start_date, end_date)
|
66
|
+
return start_date.strftime("%F") if end_date.nil?
|
69
67
|
|
70
|
-
|
71
|
-
|
68
|
+
time_start = start_date.strftime("%I:%M %P")
|
69
|
+
time_end = end_date.strftime("%I:%M %P")
|
72
70
|
|
73
|
-
|
71
|
+
"#{start_date.strftime("%F")} (#{time_start} - #{time_end})"
|
74
72
|
end
|
75
73
|
|
76
|
-
def
|
77
|
-
|
74
|
+
def same_day_interval(pto)
|
75
|
+
time_start = pto.start_date_from.strftime("%I:%M %P")
|
76
|
+
time_end = pto.end_date_from.strftime("%I:%M %P")
|
78
77
|
|
79
|
-
|
78
|
+
time_start == time_end ? "all day" : "from #{time_start} to #{time_end}"
|
80
79
|
end
|
81
80
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
DateTime.parse(date_time).to_time
|
81
|
+
def today?(date)
|
82
|
+
date == Time.now(in: @timezone).strftime("%F")
|
86
83
|
end
|
87
84
|
end
|
88
85
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../domain/issue"
|
4
|
+
require_relative "../base"
|
5
|
+
|
6
|
+
module Mapper
|
7
|
+
module Github
|
8
|
+
##
|
9
|
+
# This class implementats the methods of the Mapper::Base module, specifically designed for
|
10
|
+
# preparing or shaping Github issues data coming from a Fetcher::Base implementation.
|
11
|
+
class Issues
|
12
|
+
include Base
|
13
|
+
|
14
|
+
# Implements the logic for shaping the results from a fetcher response.
|
15
|
+
#
|
16
|
+
# <br>
|
17
|
+
# <b>Params:</b>
|
18
|
+
# * <tt>Fetcher::Github::Types::Response</tt> github_response: Array of github issues data.
|
19
|
+
#
|
20
|
+
# <br>
|
21
|
+
# <b>return</b> <tt>List<Domain::Issue></tt> mapped github issues to be used by a
|
22
|
+
# Formatter::Base implementation.
|
23
|
+
#
|
24
|
+
def map(github_response)
|
25
|
+
return [] if github_response.results.empty?
|
26
|
+
|
27
|
+
normalized_github_data = normalize_response(github_response.results)
|
28
|
+
|
29
|
+
normalized_github_data.map do |issue|
|
30
|
+
Domain::Issue.new(
|
31
|
+
issue["title"], issue["state"], issue["assignees"], issue["body"], issue["url"]
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def normalize_response(results)
|
39
|
+
return [] if results.nil?
|
40
|
+
|
41
|
+
results.map do |value|
|
42
|
+
{
|
43
|
+
"title" => value[:title],
|
44
|
+
"state" => value[:state],
|
45
|
+
"assignees" => extract_assignees(value[:assignees]),
|
46
|
+
"body" => value[:body],
|
47
|
+
"url" => value[:url]
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract_assignees(assignees)
|
53
|
+
assignees.map { |assignee| assignee[:login] }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -42,19 +42,11 @@ module Mapper
|
|
42
42
|
response.map do |value|
|
43
43
|
pto_fields = value["properties"].slice(*PTO_PARAMS)
|
44
44
|
|
45
|
-
|
46
|
-
pto_fields[
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def extract_pto_value(field, value)
|
54
|
-
case field
|
55
|
-
when "Person" then extract_person_field_value(value)
|
56
|
-
when "Desde?" then extract_date_field_value(value)
|
57
|
-
when "Hasta?" then extract_date_field_value(value)
|
45
|
+
{
|
46
|
+
"Person" => extract_person_field_value(pto_fields["Person"]),
|
47
|
+
"Desde?" => extract_date_field_value(pto_fields["Desde?"]),
|
48
|
+
"Hasta?" => extract_date_field_value(pto_fields["Hasta?"])
|
49
|
+
}
|
58
50
|
end
|
59
51
|
end
|
60
52
|
|
@@ -62,9 +54,20 @@ module Mapper
|
|
62
54
|
data["people"][0]["name"]
|
63
55
|
end
|
64
56
|
|
65
|
-
def extract_date_field_value(
|
57
|
+
def extract_date_field_value(date)
|
58
|
+
{
|
59
|
+
from: extract_start_date(date),
|
60
|
+
to: extract_end_date(date)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_start_date(data)
|
66
65
|
data["date"]["start"]
|
67
66
|
end
|
67
|
+
|
68
|
+
def extract_end_date(data)
|
69
|
+
data["date"]["end"]
|
70
|
+
end
|
68
71
|
end
|
69
72
|
end
|
70
73
|
end
|
@@ -27,8 +27,8 @@ module Mapper
|
|
27
27
|
|
28
28
|
ptos.map do |pto|
|
29
29
|
name = pto["name"]
|
30
|
-
start_date = pto["start_date"]
|
31
|
-
end_date = pto["end_date"]
|
30
|
+
start_date = { from: pto["start_date"], to: nil }
|
31
|
+
end_date = { from: pto["end_date"], to: nil }
|
32
32
|
|
33
33
|
Domain::Pto.new(name, start_date, end_date)
|
34
34
|
end
|
@@ -8,6 +8,7 @@ require_relative "../fetcher/notion/use_case/pto_next_week"
|
|
8
8
|
require_relative "../fetcher/notion/use_case/work_items_limit"
|
9
9
|
require_relative "../fetcher/postgres/use_case/pto_today"
|
10
10
|
require_relative "../fetcher/imap/use_case/support_emails"
|
11
|
+
require_relative "../fetcher/github/use_case/repo_issues"
|
11
12
|
|
12
13
|
# mapper
|
13
14
|
require_relative "../mapper/notion/birthday_today"
|
@@ -15,6 +16,7 @@ require_relative "../mapper/notion/pto_today"
|
|
15
16
|
require_relative "../mapper/notion/work_items_limit"
|
16
17
|
require_relative "../mapper/postgres/pto_today"
|
17
18
|
require_relative "../mapper/imap/support_emails"
|
19
|
+
require_relative "../mapper/github/issues"
|
18
20
|
|
19
21
|
# formatter
|
20
22
|
require_relative "../formatter/birthday"
|
data/lib/bas/version.rb
CHANGED
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: 0.
|
4
|
+
version: 0.2.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-
|
11
|
+
date: 2024-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A versatile business automation system offering key components for building
|
14
14
|
various use cases. It provides an easy-to-use tool for implementing automation
|
@@ -41,9 +41,13 @@ files:
|
|
41
41
|
- lib/bas/domain/birthday.rb
|
42
42
|
- lib/bas/domain/email.rb
|
43
43
|
- lib/bas/domain/exceptions/function_not_implemented.rb
|
44
|
+
- lib/bas/domain/issue.rb
|
44
45
|
- lib/bas/domain/pto.rb
|
45
46
|
- lib/bas/domain/work_items_limit.rb
|
46
47
|
- lib/bas/fetcher/base.rb
|
48
|
+
- lib/bas/fetcher/github/base.rb
|
49
|
+
- lib/bas/fetcher/github/types/response.rb
|
50
|
+
- lib/bas/fetcher/github/use_case/repo_issues.rb
|
47
51
|
- lib/bas/fetcher/imap/base.rb
|
48
52
|
- lib/bas/fetcher/imap/types/response.rb
|
49
53
|
- lib/bas/fetcher/imap/use_case/support_emails.rb
|
@@ -68,6 +72,7 @@ files:
|
|
68
72
|
- lib/bas/formatter/support_emails.rb
|
69
73
|
- lib/bas/formatter/work_items_limit.rb
|
70
74
|
- lib/bas/mapper/base.rb
|
75
|
+
- lib/bas/mapper/github/issues.rb
|
71
76
|
- lib/bas/mapper/imap/support_emails.rb
|
72
77
|
- lib/bas/mapper/notion/birthday_today.rb
|
73
78
|
- lib/bas/mapper/notion/pto_today.rb
|