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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d8ff973fc126ff9d0af5adf9200417f302a9a86cc50be3d3ef090a8979a22b1
4
- data.tar.gz: 846dfe3cd6d719aa0a7cc17ce2219a0dee54a5d89043a475ec93b4786fd688d2
3
+ metadata.gz: 97cc25416cff1a50cad38e85bbc1e7389693ccf2f4004a1c5fb8d5049162b407
4
+ data.tar.gz: efef410e0d776c8808655dbd20cb7500c81bcf53c16f072a7103d5827def9e58
5
5
  SHA512:
6
- metadata.gz: 14ea01f630b4a296fa94a352d14bc00111833d2f5b1867861280c5aa32abca445bdc7ec888c83a4a673109aba2c5e3239fdccc48cc7315477a3b579d200b649c
7
- data.tar.gz: 3b73cf8b1dc7d931c3cd428141f2a3806c1b90c23bb1c0ac2dc755bf16278b679f42405f4d504b5cfbd6d1f09787d065f34fe3a06cee15ed9d684a93d2249971
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?branch=project-opensource-config&style=for-the-badge)
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/8383?label=openssf%20best%20practices&style=for-the-badge)](https://bestpractices.coreinfrastructure.org/projects/8383)
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
@@ -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, :start_date, :end_date
10
+ attr_reader :individual_name, :start_date_from, :start_date_to, :end_date_from, :end_date_to
11
11
 
12
- ATTRIBUTES = %w[individual_name start_date end_date].freeze
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
- @start_date = start_date
25
- @end_date = end_date
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
@@ -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
- date_start = format_timezone(pto.start_date).strftime("%F")
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?(date_start) ? "today" : "the day #{date_start}"
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
- "#{built_template} from #{date_start} to #{date_end}\n"
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 same_day_interval(pto)
64
- time_start = format_timezone(pto.start_date).strftime("%I:%M %P")
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
- def format_timezone(date)
71
- date_time = build_date(date)
68
+ time_start = start_date.strftime("%I:%M %P")
69
+ time_end = end_date.strftime("%I:%M %P")
72
70
 
73
- Time.at(date_time, in: @timezone)
71
+ "#{start_date.strftime("%F")} (#{time_start} - #{time_end})"
74
72
  end
75
73
 
76
- def today?(date)
77
- time_now = Time.now.strftime("%F")
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
- date == format_timezone(time_now).strftime("%F")
78
+ time_start == time_end ? "all day" : "from #{time_start} to #{time_end}"
80
79
  end
81
80
 
82
- def build_date(date)
83
- date_time = date.include?("T") ? date : "#{date}T00:00:00.000#{@timezone}"
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
- pto_fields.each do |field, pto_value|
46
- pto_fields[field] = extract_pto_value(field, pto_value)
47
- end
48
-
49
- pto_fields
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(data)
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bas
4
4
  # Gem version
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.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: 0.1.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-03-26 00:00:00.000000000 Z
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