Skip to content

Ptime mapper script #737

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

Draft
wants to merge 29 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
31cc487
Squash branch
ManuelMoeri Mar 3, 2025
923b345
Fix tests
ManuelMoeri Mar 3, 2025
2420260
Remove duplicate in gemfile.lock and fix test
ManuelMoeri Mar 5, 2025
ff10d9a
Remove gem that was present twice in the gemfile
ManuelMoeri Mar 5, 2025
a697b3a
Fix preloading and showing the correct person in the dropdown
ManuelMoeri Mar 6, 2025
4fc97e7
Try to fix tests by removing local from routes
ManuelMoeri Mar 6, 2025
c1462e7
Fix tests for now, but make rubocop mad
ManuelMoeri Mar 6, 2025
13bf453
Fix the broken dropdown again
ManuelMoeri Mar 6, 2025
445609b
Fix currently selected person for ptime data
ManuelMoeri Mar 7, 2025
7b8d485
Make fetch method less redundant and make rubocop happy
ManuelMoeri Mar 7, 2025
a57e024
Rename env variable to more fitting of its purpose
ManuelMoeri Mar 7, 2025
47f4d6e
Fix helper tests to check for the correct path
ManuelMoeri Mar 10, 2025
726dcf2
Comment in method
ManuelMoeri Mar 10, 2025
637de97
Fix fetching of some people data and only show active employees in dr…
ManuelMoeri Mar 13, 2025
6802f9f
Add .envrc to gitignore and change logic to correctly handle roles
ManuelMoeri Mar 19, 2025
ae606bc
Fix logic of roles and prepare for the newly added attribues
ManuelMoeri Mar 19, 2025
c88db4e
Comment out method that is not used right now and fix tests
ManuelMoeri Mar 20, 2025
54ad28b
Fix naming of tests, make method in helper shorter and replace method…
ManuelMoeri Mar 20, 2025
ff60cd5
Change fixtures to better match something and add extended tests for …
ManuelMoeri Mar 20, 2025
05dd95b
Fix and enhance tests aswell as json fixtures
ManuelMoeri Mar 20, 2025
1c8f129
Make constant non-private to make rubocop happy once more
ManuelMoeri Mar 20, 2025
98e8c0e
Fix failing tests by including the newly added person in the fixtures
ManuelMoeri Mar 21, 2025
5bf42e2
Delete unnecessary file and the corresponding spec and rake task
ManuelMoeri Mar 21, 2025
8f517e5
Extend mapping tests to include roles
ManuelMoeri Mar 21, 2025
4b0c714
Fix people_spec by setting the default nationality correct
ManuelMoeri Mar 21, 2025
d77e006
Change logic to only send requests to ptime when it has not been fetc…
ManuelMoeri Mar 24, 2025
bc08eb2
Feature/733 make attr readonly for ptime attrs (#846)
Miguel7373 Apr 7, 2025
6d0dfeb
Update nodemon and esbuild versions, remove unnecessary changes from …
RandomTannenbaum Apr 11, 2025
7f3cba4
Readd schema.rb
ManuelMoeri Apr 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,6 @@ config/docker/development/home/rails/.bash_history

/app/assets/builds/*
!/app/assets/builds/.keep

.sec
.envrc
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ gem 'config'
gem 'countries'
gem 'cssbundling-rails'
gem 'csv'
gem 'daemons'
gem 'database_cleaner'
gem 'devise'
gem 'drb'
Expand Down Expand Up @@ -84,6 +85,7 @@ group :test do
gem 'capybara'
gem 'selenium-webdriver', '>= 4.28.0'
gem 'simplecov'
gem 'webmock'
# Use fixed version of webdrivers to avoid compatibility issues with chrome and chromedriver
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
Expand Down
11 changes: 11 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,14 @@ GEM
connection_pool (2.5.0)
countries (7.1.1)
unaccent (~> 0.3)
crack (1.0.0)
bigdecimal
rexml
crass (1.0.6)
cssbundling-rails (1.4.1)
railties (>= 6.0.0)
csv (3.3.2)
daemons (1.4.1)
database_cleaner (2.1.0)
database_cleaner-active_record (>= 2, < 3)
database_cleaner-active_record (2.2.0)
Expand Down Expand Up @@ -181,6 +185,7 @@ GEM
rainbow
rubocop (>= 1.0)
sysexits (~> 1.1)
hashdiff (1.1.0)
hashie (5.0.0)
highline (3.1.2)
reline
Expand Down Expand Up @@ -504,6 +509,10 @@ GEM
version_gem (1.1.4)
warden (1.2.9)
rack (>= 2.0.9)
webmock (3.23.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
websocket (1.2.11)
websocket-driver (0.7.7)
base64
Expand Down Expand Up @@ -533,6 +542,7 @@ DEPENDENCIES
countries
cssbundling-rails
csv
daemons
database_cleaner
devise
dotenv
Expand Down Expand Up @@ -585,6 +595,7 @@ DEPENDENCIES
stimulus-rails
turbo-rails
tzinfo-data
webmock

BUNDLED WITH
2.4.10
13 changes: 13 additions & 0 deletions app/controllers/concerns/ptime/people_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Ptime
module PeopleController
def show
super
Ptime::PeopleEmployees.new.update_person_data(@person)
end

def new
@person = Ptime::PeopleEmployees.new.find_or_create(params[:ptime_employee_id])
redirect_to(@person)
end
end
end
32 changes: 20 additions & 12 deletions app/controllers/people_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@ class PeopleController < CrudController

helper_method :default_branch_adress

self.permitted_attrs = [:birthdate, :location, :marital_status, :updated_by, :name, :nationality,
:nationality2, :title, :competence_notes, :company_id, :email,
:department_id, :shortname, :picture, :picture_cache,
:display_competence_notes_in_cv,
{ person_roles_attributes:
[[:role_id, :person_role_level_id, :percent, :id, :_destroy]],
language_skills_attributes:
[[:language, :level, :certificate, :id, :_destroy]] }]

layout 'person', only: [:show]

def index
Expand All @@ -34,9 +27,6 @@ def show
def new
super
@person.nationality = 'CH'
%w[DE EN FR].each do |language|
@person.language_skills.push(LanguageSkill.new({ language: language }))
end
end

def create
Expand All @@ -62,7 +52,6 @@ def export
disposition: content_disposition('attachment', filename)
end


private

def fetch_entries
Expand All @@ -76,4 +65,23 @@ def person
def default_branch_adress
BranchAdress.find_by(default_branch_adress: true) || BranchAdress.first
end

# rubocop:disable Metrics/MethodLength
def permitted_attrs
if Skills.use_ptime_sync?
[:updated_by, :picture, :picture_cache, :display_competence_notes_in_cv,
{ language_skills_attributes:
[[:language, :level, :certificate, :id, :_destroy]] }]
else
[:birthdate, :location, :marital_status, :updated_by, :name, :nationality,
:nationality2, :title, :competence_notes, :company_id, :email,
:department_id, :shortname, :picture, :picture_cache, :display_competence_notes_in_cv,
{ person_roles_attributes:
[[:role_id, :person_role_level_id, :percent, :id, :_destroy]],
language_skills_attributes:
[[:language, :level, :certificate, :id,
:_destroy]] }]
end
end
# rubocop:enable Metrics/MethodLength
end
60 changes: 60 additions & 0 deletions app/domain/ptime/assign_employee_ids.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# This script will assign each person the corresponding employee ID from PuzzleTime
module Ptime
class AssignEmployeeIds
MAX_NUMBER_OF_FETCHED_EMPLOYEES = 1000

# rubocop:disable Rails/Output
def run(should_map: false)
puts 'Notice this is a dry run and mapping will not happen!' unless should_map
fetch_data

puts "Currently there are:
- #{@ptime_employees.length} employees in PuzzleTime
- #{@skills_people.count} people in PuzzleSkills
This is a difference of #{(@skills_people.count - @ptime_employees.length).abs} entries"
map_employees(should_map)
end

private

# rubocop:disable Metrics
def map_employees(should_map)
puts 'Assigning employee IDs now...' if should_map

unmatched_entries = []
mapped_people_count = 0

@ptime_employees.each do |ptime_employee|
ptime_employee_name = ptime_employee[:attributes][:full_name]
ptime_employee_email = ptime_employee[:attributes][:email]
matched_person = Person.where(ptime_employee_id: nil).find_by(email: ptime_employee_email)

if matched_person.nil?
unmatched_entries << { name: ptime_employee_name, id: ptime_employee[:id] }
else
if should_map
matched_person.ptime_employee_id = ptime_employee[:id]
matched_person.save!
end
mapped_people_count += 1
end
end

puts '--------------------------'
puts "#{mapped_people_count} people were matched successfully"
puts '--------------------------'
puts "#{unmatched_entries.size} people didn't match"
unmatched_entries.each { |entry| puts "- #{entry[:name]} with id #{entry[:id]}" }
end
# rubocop:enable Metrics

def fetch_data
puts 'Fetching required data...'
@ptime_employees = Ptime::Client.new.request(:get, 'employees',
{ per_page: MAX_NUMBER_OF_FETCHED_EMPLOYEES })
@skills_people = Person.all
puts 'Successfully fetched data'
end
# rubocop:enable Rails/Output
end
end
49 changes: 49 additions & 0 deletions app/domain/ptime/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require 'rest_client'
require 'base64'

module Ptime
class Client
def initialize
@base_url = "#{ENV.fetch('PTIME_BASE_URL')}/api/v1/"
end

def request(method, endpoint, params = {})
url = @base_url + endpoint

if last_error_stale?
execute_request(method, url, params)
else
raise CustomExceptions::PTimeTemporarilyUnavailableError, 'PTime is temporarily unavailable'
end
end

private

def last_error_stale?
last_request_time = ENV.fetch('LAST_PTIME_ERROR', nil)
return true if last_request_time.nil?

last_request_time.to_datetime <= 5.minutes.ago
end

def build_request(method, url)
RestClient::Request.new(
:method => method,
:url => url,
:user => ENV.fetch('PTIME_API_USERNAME'),
:password => ENV.fetch('PTIME_API_PASSWORD'),
:headers => { :accept => :json, :content_type => :json }
)
end

def execute_request(method, url, params)
url += "?#{params.to_query}" if method == :get && params.present?
response = build_request(method, url).execute
JSON.parse(response.body, symbolize_names: true)[:data]
rescue RestClient::ExceptionWithResponse
raise CustomExceptions::PTimeClientError, 'Error'
end
end
end
86 changes: 86 additions & 0 deletions app/domain/ptime/people_employees.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
module Ptime
class PeopleEmployees
ATTRIBUTES_MAPPING = {
firstname: :name,
shortname: :shortname,
email: :email,
marital_status: :marital_status,
graduation: :title,
city: :location,
birthday: :birthdate
}.freeze

def find_or_create(ptime_employee_id)
raise 'No ptime_employee_id provided' unless ptime_employee_id

person = Person.find_by(ptime_employee_id: ptime_employee_id)
return person if person

new_person = Person.new(ptime_employee_id: ptime_employee_id)
update_person_data(new_person)
end

# rubocop:disable Metrics
def update_person_data(person)
raise 'Person has no ptime_employee_id' unless person.ptime_employee_id

begin
ptime_employee = Ptime::Client.new.request(:get, "employees/#{person.ptime_employee_id}")
rescue CustomExceptions::PTimeTemporarilyUnavailableError
return
end

ptime_employee[:attributes].each do |key, value|
if ATTRIBUTES_MAPPING.key?(key.to_sym)
person[ATTRIBUTES_MAPPING[key.to_sym]] =
value.presence || '-'
end
end

set_additional_attributes(person, ptime_employee)

person.save!
set_person_roles(person, ptime_employee)
person
end
# rubocop:enable Metrics

private

def set_additional_attributes(person, ptime_employee)
is_employed = ptime_employee[:attributes][:is_employed]
person.company = Company.find_by(name: is_employed ? 'Firma' : 'Ex-Mitarbeiter')

nationalities = ptime_employee[:attributes][:nationalities] || []
person.nationality = nationalities[0]
person.nationality2 = nationalities[1]
person.name = append_ptime_employee_name(ptime_employee)
end

def append_ptime_employee_name(ptime_employee)
"#{ptime_employee[:attributes][:firstname]} #{ptime_employee[:attributes][:lastname]}"
end

def set_person_roles(person, ptime_employee)
PersonRole.where(person_id: person.id).destroy_all

ptime_employee[:attributes][:employment_roles].each do |role|
role_id = Role.find_or_create_by(name: sanitized_role_name(role[:name])).id
# role_level_id = map_role_level(role)
PersonRole.create!(person_id: person.id,
role_id: role_id,
percent: role[:percent],
person_role_level_id: PersonRoleLevel.first.id)
end
end

# Remove prefix of role such as 'T1' or 'M2' which is not needed
def sanitized_role_name(role_name)
role_name.gsub(/\A[A-Z]\d+\s/, '')
end

# def map_role_level(role)
# PersonRoleLevel.find_by(level: role[role_level]).id
# end
end
end
6 changes: 6 additions & 0 deletions app/exceptions/custom_exceptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module CustomExceptions

class PTimeClientError < StandardError; end
class PTimeTemporarilyUnavailableError < StandardError; end

end
4 changes: 4 additions & 0 deletions app/helpers/auth_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def devise?
AuthConfig.keycloak? || Rails.env.test?
end

def use_ptime_sync?
ActiveModel::Type::Boolean.new.cast(ENV.fetch('PTIME_API_ACCESSIBLE'))
end

def language_selector
languages = I18n.available_locales.map { |e| e.to_s }.map do |lang_code|
[language(lang_code).capitalize, url_for(locale: lang_code)]
Expand Down
11 changes: 11 additions & 0 deletions app/helpers/form_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,15 @@ def crud_form(*attrs, &)
standard_form(path_args(entry), *attrs, &)
end

def disabled_with_ptime_sync
if ptime_sync_active?
{
'data-bs-toggle': 'tooltip',
'data-bs-title': I18n.t('people.form.ptime_data'),
'data-bs-placement': 'top',
'data-controller': 'tooltip',
disabled: true
}
end
end
end
Loading
Loading