tranzito_utils 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c4045a5fec9bb07e94ee83a17aafb7f7e58b1c228de4a5562116a9982a362d12
4
+ data.tar.gz: dc1d4c81f7f71079c0b66e5b7c6b8e23b88d3fbde4372eef0c50a3ff50569ff6
5
+ SHA512:
6
+ metadata.gz: 501bde401f008a5d0ae611539dec533d448d4d75e40ad23b262a1030818a9785201ff75429435cb4fc78c3b4f59e67b5d41d037d10a8a3a21588bb37d13c4282
7
+ data.tar.gz: 9ec818c62b80d08cba34103ef490bb6339f431ed835e1d9584428ada2270f4a80bb54e34216577e4cf3ec86e9822b93fb0aae01b5cf7d9783a0e9349ed46c61a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # TranzitoUtils [![CircleCI](https://dl.circleci.com/status-badge/img/gh/Tranzito/tranzito_utils/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/Tranzito/tranzito_utils/tree/main)
2
+
3
+ `tranzito_utils` is a ruby gem containing these Rails helpers, concerns and services.
4
+
5
+ | Name | Type | Description |
6
+ | ---- | ---- | ----------- |
7
+ | `SetPeriod` | Controller Concern | Time period selection and browsing |
8
+ | `SortableTable` | Controller Concern | Sort column and direction |
9
+ | `TimeParser` | Service | Parse time strings into activerecord time objects |
10
+ | `ParamsNormalizer` | Service | Normalize truthy and falsey strings |
11
+ | `GraphingHelper` | Helper | Graphing helper for [chartkick](https://chartkick.com/) charts |
12
+ | `SortableHelper` | Helper | Sort table headers |
13
+
14
+ # Installation
15
+
16
+ ## tranzito_utils (gem)
17
+ Install the gem by adding this line to your application's Gemfile:
18
+
19
+ ```
20
+ gem "tranzito_utils"
21
+ ```
22
+
23
+ Then run `bundle install`
24
+
25
+ To include the config file inside your host app run this command.
26
+
27
+ ```
28
+ rails g tranzito_utils:install
29
+ ```
30
+
31
+ This will add the `tranzito_utils.rb` file in the initializer where you can alter the default behaviour of several variables being used throughout the gem.
32
+
33
+
34
+ # Usage
35
+
36
+ #### SortableTable
37
+
38
+ To use the SortableTable module you can include it in your required controllers and then be able to use its methods.
39
+
40
+ ```
41
+ include TranzitoUtils::SortableTable
42
+ ```
43
+
44
+ You can use the following methods of ` TranzitoUtils::SortableTable` in your controllers. These are also the helper methods.
45
+
46
+ ```
47
+ sort_column, sort_direction
48
+ ```
49
+
50
+ #### SetPeriod
51
+
52
+ Include the `SetPeriod` module into your `ApplicationController` by adding this line
53
+
54
+ ```
55
+ include TranzitoUtils::SetPeriod
56
+ ```
57
+
58
+ You just need to add this line in the controller where you want to use the `SetPeriod` module.
59
+
60
+ ```
61
+ before_action :set_period, only: [:index]
62
+ ```
63
+
64
+ Also, the `SetPeriod` has a partial file which you can include in your views using this line.
65
+
66
+ ```
67
+ render partial: "/tranzito_utils/period_select"
68
+ ```
69
+
70
+ This will include the period_select view for filtering.
71
+
72
+ ### SortableHelper
73
+
74
+ For SortableHelper, you need to add that line into your `application_helper.rb` file.
75
+
76
+ ```
77
+ include TranzitoUtils::SortableHelper
78
+ ```
79
+
80
+ ### GraphingHelper
81
+
82
+ Same for GraphingHelper, if you want to use its methods then you need to add that line into your `application_helper.rb` file.
83
+
84
+ ```
85
+ include TranzitoUtils::GraphingHelper
86
+ ```
87
+
88
+ #### ParamsNormalizer
89
+
90
+ Params normalizer uses activerecord to parse strings into booleans.
91
+
92
+ ## tranzito_utils_js (npm)
93
+ You also need to add this NPM package in order to use the gem without any issue. You can install it using `yarn` or `npm`.
94
+
95
+ For yarn
96
+ ```
97
+ yarn add tranzito_utils_js
98
+ ```
99
+ For npm
100
+ ```
101
+ npm install tranzito_utils_js
102
+ ```
103
+
104
+ Then import it into your application by adding this line in `application.js` or any `.js` file where you need it.
105
+
106
+ ```
107
+ import { PeriodSelector, TimeParser } from "tranzito_utils_js"
108
+ ```
109
+ Here `TimeParser` and `PeriodSelector` are classes we can use in our app.
110
+
111
+ For `TimeParser` You can use it by initializing like this
112
+
113
+ ```
114
+ if (!window.timeParser) { window.timeParser = new TimeParser() }
115
+ window.timeParser.localize()
116
+ ```
117
+
118
+ As for `PeriodSelector`, you can use it by initializing like this
119
+ ```
120
+ if ($('#timeSelectionBtnGroup').length) {
121
+ const periodSelector = new PeriodSelector()
122
+ periodSelector.init()
123
+ }
124
+ ```
125
+ `#timeSelectionBtnGroup` is being used in the partial in `tranzito_utils` gem.
126
+ ## License
127
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
128
+
129
+
130
+ ## Testing
131
+
132
+ To setup testing, you have to create the database:
133
+
134
+ ```shell
135
+ cd spec/dummy
136
+ RAILS_ENV=test bundle exec rails db:create db:schema:load db:migrate
137
+ ```
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "bundler/gem_tasks"
@@ -0,0 +1,45 @@
1
+ - skip_submission ||= false
2
+ - include_future ||= false
3
+ - prepend_text ||= nil
4
+
5
+ #timeSelectionBtnGroup.text-right{ role: "group", class: @period == "custom" ? "custom-period-selected" : "", "data-nosubmit" => "#{skip_submission}" }
6
+ - if prepend_text.present?
7
+ %span.mr-2.less-strong.d-block.d-lg-inline-block
8
+ = prepend_text
9
+
10
+ - if include_future
11
+ %a.btn.btn-outline-secondary.btn-sm.period-select-standard{ href: url_for(sortable_search_params.merge(period: "next_week")), class: ("active" if @period == "next_week"), data: { period: "next_week" } }
12
+ %span.d-none.d-md-inline-block next
13
+ seven days
14
+ %a.btn.btn-outline-secondary.btn-sm.period-select-standard{ href: url_for(sortable_search_params.merge(period: "next_month")), class: ("active" if @period == "next_month"), data: { period: "next_month" } }
15
+ %span.d-none.d-md-inline-block next
16
+ thirty days
17
+ %a.btn.btn-outline-secondary.btn-sm.period-select-standard{ href: url_for(sortable_search_params.merge(period: "hour")), class: ("active" if @period == "hour"), data: { period: "hour" } }
18
+ %span.d-none.d-md-inline-block past
19
+ hour
20
+ %a.btn.btn-outline-secondary.btn-sm.period-select-standard{ href: url_for(sortable_search_params.merge(period: "day")), class: ("active" if @period == "day"), data: { period: "day" } }
21
+ %span.d-none.d-md-inline-block past
22
+ day
23
+ %a.btn.btn-outline-secondary.btn-sm.period-select-standard{ href: url_for(sortable_search_params.merge(period: "week")), class: ("active" if @period == "week"), data: { period: "week" } }
24
+ %span.d-none.d-md-inline-block past
25
+ seven days
26
+ %a.btn.btn-outline-secondary.btn-sm.period-select-standard{ href: url_for(sortable_search_params.merge(period: "month")), class: ("active" if @period == "month"), data: { period: "month" } }
27
+ %span.d-none.d-md-inline-block past
28
+ thirty days
29
+ %a.btn.btn-outline-secondary.btn-sm.period-select-standard{ href: url_for(sortable_search_params.merge(period: "year")), class: ("active" if @period == "year"), data: { period: "year" } }
30
+ %span.d-none.d-md-inline-block past
31
+ year
32
+ %a.btn.btn-outline-secondary.btn-sm.period-select-standard{ href: url_for(sortable_search_params.merge(period: "all")), class: ("active" if @period == "all"), data: { period: "all" } }
33
+ all
34
+ %button#periodSelectCustom.btn.btn-outline-secondary.btn-sm.ml-2{ class: ("active" if @period == "custom"), data: { period: "custom" } }
35
+ custom
36
+
37
+ %form#timeSelectionCustom.custom-time-selection.mt-2.collapse{ class: @period == "custom" ? "in show" : "" }
38
+ .form-group
39
+ = label_tag :start_time_selector, "from", class: "control-label mr-2"
40
+ = datetime_local_field_tag :start_time_selector, @start_time.strftime("%Y-%m-%dT%H:%M"), step: 60, class: "form-control"
41
+ .form-group.end-time-control
42
+ = label_tag :end_time_selector, "to", class: "control-label mr-2 ml-2"
43
+ = datetime_local_field_tag :end_time_selector, @end_time.strftime("%Y-%m-%dT%H:%M"), step: 60, class: "form-control"
44
+ %button#updatePeriodSelectCustom.btn.btn-success.btn-sm.ml-2
45
+ update
@@ -0,0 +1,5 @@
1
+ en:
2
+ time:
3
+ formats:
4
+ convert_time: "%FT%T%z"
5
+ dotted: "%Y.%-m.%-d"
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module TranzitoUtils
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("../templates/", __FILE__)
8
+
9
+ # it will copy the tranzito_utils.rb file into the hosts application
10
+ def copy_initializer
11
+ template "tranzito_utils.rb", "config/initializers/tranzito_utils.rb"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Tranzito_utils initializer file
4
+ # Customize only when you want to override the default values of tranzito_utils
5
+
6
+ # For TranzitoUtils::SetPeriod
7
+ # You can update the time for earliest period for SetPeriod concern
8
+ # TranzitoUtils::DEFAULT[:earliest_period_time] = Time.at(1637388000)
9
+
10
+ # For TranzitoUtils::SortableHelper
11
+ # You can add the more sortable search params for the sortable_search_params method in sortable_helper
12
+ # TranzitoUtils::DEFAULT[:additional_search_keys] = []
13
+
14
+ # For TranzitoUtils::TimeParser service
15
+ # You can update the earliest year and latest year for TimeParser service
16
+ # TranzitoUtils::DEFAULT[:earliest_year] = 1900
17
+ # TranzitoUtils::DEFAULT[:latest_year] = Time.current.year + 100
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TranzitoUtils
4
+ module SetPeriod
5
+ extend ActiveSupport::Concern
6
+ PERIOD_TYPES = %w[hour day month year week all next_week next_month].freeze
7
+
8
+ # For setting periods, particularly for graphing
9
+ def set_period
10
+ set_timezone
11
+ # Set time period
12
+ @period ||= params[:period]
13
+ if @period == "custom"
14
+ if params[:start_time].present?
15
+ @start_time = TimeParser.parse(params[:start_time], @timezone)
16
+ @end_time = TimeParser.parse(params[:end_time], @timezone) || Time.current
17
+
18
+ @start_time, @end_time = @end_time, @start_time if @start_time > @end_time
19
+ else
20
+ set_time_range_from_period
21
+ end
22
+ elsif params[:search_at].present?
23
+ @period = "custom"
24
+ @search_at = TimeParser.parse(params[:search_at], @timezone)
25
+ offset = params[:period].present? ? params[:period].to_i : 10.minutes.to_i
26
+ @start_time = @search_at - offset
27
+ @end_time = @search_at + offset
28
+ else
29
+ set_time_range_from_period
30
+ end
31
+
32
+ # Add this render_chart in here so we don't have to define it in all the controllers
33
+ @render_chart = ActiveRecord::Type::Boolean.new.cast(params[:render_chart].to_s.strip)
34
+ @time_range = @start_time..@end_time
35
+ end
36
+
37
+ private
38
+
39
+ def set_time_range_from_period
40
+ @period = default_period unless PERIOD_TYPES.include?(@period)
41
+
42
+ case @period
43
+ when "hour"
44
+ @start_time = Time.current - 1.hour
45
+ when "day"
46
+ @start_time = Time.current.beginning_of_day - 1.day
47
+ when "month"
48
+ @start_time = Time.current.beginning_of_day - 30.days
49
+ when "year"
50
+ @start_time = Time.current.beginning_of_day - 1.year
51
+ when "week"
52
+ @start_time = Time.current.beginning_of_day - 1.week
53
+ when "next_month"
54
+ @start_time ||= Time.current
55
+ @end_time = Time.current.beginning_of_day + 30.days
56
+ when "next_week"
57
+ @start_time = Time.current
58
+ @end_time = Time.current.beginning_of_day + 1.week
59
+ when "all"
60
+ @start_time = TranzitoUtils::DEFAULT[:earliest_period_time]
61
+ else
62
+ @period = default_period
63
+ @start_time = TranzitoUtils::DEFAULT[:earliest_period_time]
64
+ end
65
+ @end_time ||= latest_period_date
66
+ end
67
+
68
+ # Separate method so it can be overridden on per controller basis
69
+ def default_period
70
+ "all"
71
+ end
72
+
73
+ # Separate method so it can be overriden
74
+ def latest_period_date
75
+ Time.current
76
+ end
77
+
78
+ def set_timezone
79
+ return true if @timezone.present?
80
+
81
+ # Parse the timezone params if they are passed (tested in admin#activity_groups#index)
82
+ if params[:timezone].present?
83
+ @timezone = TimeParser.parse_timezone(params[:timezone])
84
+ # If it's a valid timezone, save to session
85
+ session[:timezone] = @timezone&.name
86
+ end
87
+
88
+ # Set the timezone on a per request basis if we have a timezone saved
89
+ if session[:timezone].present?
90
+ @timezone ||= TimeParser.parse_timezone(session[:timezone])
91
+ Time.zone = @timezone
92
+ end
93
+
94
+ @timezone ||= TranzitoUtils::DEFAULT[:time_zone]
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TranzitoUtils
4
+ module SortableTable
5
+ extend ActiveSupport::Concern
6
+ SORT_DIRECTIONS = %w[asc desc].freeze
7
+
8
+ included do
9
+ helper_method :sort_column, :sort_direction
10
+ end
11
+
12
+ def sort_column
13
+ @sort_column ||= sortable_columns.include?(params[:sort]) ? params[:sort] : default_column
14
+ end
15
+
16
+ def sort_direction
17
+ @sort_direction ||= SORT_DIRECTIONS.include?(params[:direction]) ? params[:direction] : default_direction
18
+ end
19
+
20
+ # So it can be overridden
21
+ def default_direction
22
+ "desc"
23
+ end
24
+
25
+ def default_column
26
+ sortable_columns.first
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ module TranzitoUtils
2
+ class Gem < Rails::Engine
3
+ initializer "tranzito_utils.config", before: :load_config_initializers do |app|
4
+ # Setting the default timezone for timeparser service from host application configuration
5
+ TranzitoUtils::DEFAULT[:time_zone] = ActiveSupport::TimeZone[Rails.application.config.time_zone]
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TranzitoUtils
4
+ module GraphingHelper
5
+ def time_range_counts(collection:, column: "created_at", time_range: nil)
6
+ time_range ||= @time_range
7
+ # Note: by specifying the range parameter, we force it to display empty days
8
+ collection.send(group_by_method(time_range), column, range: time_range, format: group_by_format(time_range))
9
+ .count
10
+ end
11
+
12
+ def time_range_amounts(collection:, column: "created_at", amount_column: "amount_cents", time_range: nil)
13
+ time_range ||= @time_range
14
+ # Note: by specifying the range parameter, we force it to display empty days
15
+ collection.send(group_by_method(time_range), column, range: time_range, format: group_by_format(time_range))
16
+ .sum(amount_column)
17
+ .map { |k, v| [k, (v.to_f / 100.00).round(2)] } # Convert cents to dollars
18
+ .to_h
19
+ end
20
+
21
+ def time_range_duration(collection:, column: "created_at", duration_column: "duration_seconds", time_range: nil)
22
+ time_range ||= @time_range
23
+ # Note: by specifying the range parameter, we force it to display empty days
24
+ collection.send(group_by_method(time_range), column, range: time_range, format: group_by_format(time_range))
25
+ .sum(duration_column)
26
+ .map { |k, v| [k, (v.to_f / 3600.00).round(2)] } # Convert seconds to decimal hours
27
+ .to_h
28
+ end
29
+
30
+ def group_by_method(time_range)
31
+ if time_range.last - time_range.first < 3601 # 1.hour + 1 second
32
+ :group_by_minute
33
+ elsif time_range.last - time_range.first < 500_000 # around 6 days
34
+ :group_by_hour
35
+ elsif time_range.last - time_range.first < 5_000_000 # around 60 days
36
+ :group_by_day
37
+ elsif time_range.last - time_range.first < 31449600 # 364 days (52 weeks)
38
+ :group_by_week
39
+ else
40
+ :group_by_month
41
+ end
42
+ end
43
+
44
+ def group_by_format(time_range, group_period = nil)
45
+ group_period ||= group_by_method(time_range)
46
+ if group_period == :group_by_minute
47
+ "%l:%M %p"
48
+ elsif group_period == :group_by_hour
49
+ "%a%l %p"
50
+ elsif group_period == :group_by_week
51
+ "%Y-%-m-%-d"
52
+ elsif %i[group_by_day group_by_week].include?(group_period) || time_range.present? && time_range.last - time_range.first < 2.weeks.to_i
53
+ "%a %Y-%-m-%-d"
54
+ elsif group_period == :group_by_month
55
+ "%Y-%-m"
56
+ end
57
+ end
58
+
59
+ def humanized_time_range_column(time_range_column, return_value_for_all: false)
60
+ return_value_for_all = true if @render_chart
61
+ return nil unless return_value_for_all || !(@period == "all")
62
+ humanized_text = time_range_column.to_s.gsub("_at", "").humanize.downcase
63
+ return humanized_text.gsub("start", "starts") if time_range_column&.match?("start_at")
64
+ return humanized_text.gsub("end", "ends") if time_range_column&.match?("end_at")
65
+ return humanized_text.gsub("needs", "need") if time_range_column&.match?("needs_renewal_at")
66
+ humanized_text
67
+ end
68
+
69
+ def humanized_time_range(time_range)
70
+ return nil if @period == "all"
71
+
72
+ unless @period == "custom"
73
+ period_display = @period.match?("next_") ? @period.tr("_", " ") : "past #{@period}"
74
+ return "in the #{period_display}"
75
+ end
76
+
77
+ group_by = group_by_method(time_range)
78
+ precision_class = if group_by == :group_by_minute
79
+ "preciseTimeSeconds"
80
+ elsif group_by == :group_by_hour
81
+ "preciseTime"
82
+ else
83
+ ""
84
+ end
85
+
86
+ content_tag(:span) do
87
+ concat "from "
88
+ concat content_tag(:em, l(time_range.first, format: :convert_time), class: "convertTime #{precision_class}")
89
+ concat " to "
90
+ if time_range.last > Time.current - 5.minutes
91
+ concat content_tag(:em, "now")
92
+ else
93
+ concat content_tag(:em, l(time_range.last, format: :convert_time), class: "convertTime #{precision_class}")
94
+ end
95
+ end
96
+ end
97
+
98
+ # Initially just used by scheduled jobs display, but now used by other things!
99
+ def period_in_words(seconds)
100
+ return "" if seconds.blank?
101
+
102
+ seconds = seconds.to_i.abs
103
+ if seconds < 1.minute
104
+ pluralize(seconds, "second")
105
+ elsif seconds >= 1.minute && seconds < 1.hour
106
+ pluralize((seconds / 60.0).round(1), "minute")
107
+ elsif seconds >= 1.hour && seconds < 24.hours
108
+ pluralize((seconds / 3600.0).round(1), "hour")
109
+ elsif seconds >= 24.hours
110
+ pluralize((seconds / 86400.0).round(1), "day")
111
+ end.gsub(".0 ", " ") # strip out the empty zero
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TranzitoUtils
4
+ module SortableHelper
5
+ DEFAULT_SEARCH_KEYS = [:direction, :sort, :user_id, :period, :start_time, :end_time, :per_page].freeze
6
+
7
+ def sortable(column, title = nil, html_options = {})
8
+ if title.is_a?(Hash) # If title is a hash, it wasn't passed
9
+ html_options = title
10
+ title = nil
11
+ end
12
+
13
+ title ||= column.gsub(/_(id|at)\z/, "").titleize
14
+ # Check for render_sortable - otherwise default to rendering
15
+ render_sortable = html_options.key?(:render_sortable) ? html_options[:render_sortable] : !html_options[:skip_sortable]
16
+ return title unless render_sortable
17
+
18
+ html_options[:class] = "#{html_options[:class]} sortable-link"
19
+ direction = column == sort_column && sort_direction == "desc" ? "asc" : "desc"
20
+ if column == sort_column
21
+ html_options[:class] += " active"
22
+ span_content = direction == "asc" ? "\u2193" : "\u2191"
23
+ end
24
+
25
+ link_to(sortable_search_params.merge(sort: column, direction: direction), html_options) do
26
+ concat(title.html_safe)
27
+ concat(content_tag(:span, span_content, class: "sortable-direction"))
28
+ end
29
+ end
30
+
31
+ def sortable_search_params
32
+ search_param_keys = params.keys.select { |k| k.to_s.start_with?(/search_/) }
33
+ params.permit(*(DEFAULT_SEARCH_KEYS | TranzitoUtils::DEFAULT[:additional_search_keys] | search_param_keys))
34
+ end
35
+
36
+ def sortable_search_params_without_sort
37
+ sortable_search_params.except(:direction, :sort)
38
+ end
39
+
40
+ def sortable_search_params?(except: [])
41
+ except_keys = %i[direction sort period per_page] + except
42
+ s_params = sortable_search_params.except(*except_keys).values.reject(&:blank?).any?
43
+
44
+ return true if s_params
45
+ return false if except.map(&:to_s).include?("period")
46
+ params[:period].present? && params[:period] != "all"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TranzitoUtils
4
+ module ParamsNormalizer
5
+ def self.boolean(param = nil)
6
+ return false if param.blank?
7
+ ActiveRecord::Type::Boolean.new.cast(param.to_s.strip)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TranzitoUtils
4
+ module TimeParser
5
+ def self.parse(time_str = nil, timezone_str = nil)
6
+ return nil unless time_str.present?
7
+ return time_str if time_str.is_a?(Time)
8
+
9
+ if time_str.is_a?(Integer) || time_str.is_a?(Float) || time_str.to_s.strip.match(/^\d+\z/) # it's only numbers
10
+ return parse("#{time_str}-01-01") if time_str.to_s.length == 4 # Looks a year, valid 8601 format
11
+ # otherwise it's a timestamp
12
+ Time.at(time_str.to_i)
13
+ else
14
+ Time.zone = parse_timezone(timezone_str)
15
+ time = Time.zone.parse(time_str.to_s)
16
+ Time.zone = TranzitoUtils::DEFAULT[:time_zone]
17
+ time
18
+ end
19
+ rescue ArgumentError => e
20
+ # Try to parse some other, unexpected formats -
21
+ paychex_formatted = %r{(?<month>\d+)/(?<day>\d+)/(?<year>\d+) (?<hour>\d\d):(?<minute>\d\d) (?<ampm>\w\w)}.match(time_str)
22
+ ie11_formatted = %r{(?<month>\d+)/(?<day>\d+)/(?<year>\d+)}.match(time_str)
23
+ just_date = %r{(?<year>\d{4})[^\d|\w](?<month>\d\d?)}.match(time_str)
24
+ just_date_backward = %r{(?<month>\d\d?)[^\d|\w](?<year>\d{4})}.match(time_str)
25
+
26
+ # Get the successful matching regex group, and then reformat it in an expected way
27
+ regex_match = [paychex_formatted, ie11_formatted, just_date, just_date_backward].compact.first
28
+ raise e unless regex_match.present?
29
+
30
+ new_str = %w[year month day]
31
+ .map { |component| regex_match[component] if regex_match.names.include?(component) }
32
+ .compact
33
+ .join("-")
34
+
35
+ # If we end up with an unreasonable year, throw an error
36
+ raise e unless new_str.split("-").first.to_i.between?(TranzitoUtils::DEFAULT[:earliest_year], TranzitoUtils::DEFAULT[:latest_year])
37
+ # Add the day, if there isn't one
38
+ new_str += "-01" unless regex_match.names.include?("day")
39
+ # If it's paychex_formatted there is an hour and minute
40
+ if paychex_formatted.present?
41
+ new_str += " #{regex_match["hour"]}:#{regex_match["minute"]}#{regex_match["ampm"]}"
42
+ end
43
+ # Run it through TimeParser again
44
+ parse(new_str, timezone_str)
45
+ end
46
+
47
+ def self.parse_timezone(timezone_str)
48
+ return TranzitoUtils::DEFAULT[:time_zone] unless timezone_str.present?
49
+ return timezone_str if timezone_str.is_a?(ActiveSupport::TimeZone) # in case we were given a timezone obj
50
+
51
+ # tzinfo requires non-whitespaced strings, so try that if the normal lookup fails
52
+ ActiveSupport::TimeZone[timezone_str] || ActiveSupport::TimeZone[timezone_str.strip.tr("\s", "_")]
53
+ end
54
+
55
+ # Accepts a time object, rounds to minutes
56
+ def self.round(time, unit = "minute")
57
+ unit == "second" ? time.change(usec: 0, sec: 0) : time.change(min: 0, usec: 0, sec: 0)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TranzitoUtils
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tranzito_utils/version"
4
+
5
+ module TranzitoUtils
6
+ DEFAULT = {
7
+ earliest_period_time: Time.at(1637388000),
8
+ earliest_year: 1900,
9
+ latest_year: (Time.current.year + 100),
10
+ additional_search_keys: [],
11
+ time_zone: ""
12
+ }
13
+ end
14
+
15
+ require "tranzito_utils/concerns/set_period"
16
+ require "tranzito_utils/concerns/sortable_table"
17
+ require "tranzito_utils/helpers/graphing_helper"
18
+ require "tranzito_utils/helpers/sortable_helper"
19
+ require "tranzito_utils/services/time_parser"
20
+ require "tranzito_utils/services/params_normalizer"
21
+ require "tranzito_utils/gem"
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tranzito_utils
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - willbarrett
8
+ - sethherr
9
+ - hafiz-ahmed
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2022-09-26 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '6.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '6.0'
29
+ description: Ruby gem contain several modules mainly containing the helpers, concerns
30
+ and services for personal use by Tranzito
31
+ email:
32
+ - info@tranzito.org
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - MIT-LICENSE
38
+ - README.md
39
+ - Rakefile
40
+ - app/views/tranzito_utils/_period_select.html.haml
41
+ - config/locales/en.yml
42
+ - lib/generators/tranzito_utils/install_generator.rb
43
+ - lib/generators/tranzito_utils/templates/tranzito_utils.rb
44
+ - lib/tranzito_utils.rb
45
+ - lib/tranzito_utils/concerns/set_period.rb
46
+ - lib/tranzito_utils/concerns/sortable_table.rb
47
+ - lib/tranzito_utils/gem.rb
48
+ - lib/tranzito_utils/helpers/graphing_helper.rb
49
+ - lib/tranzito_utils/helpers/sortable_helper.rb
50
+ - lib/tranzito_utils/services/params_normalizer.rb
51
+ - lib/tranzito_utils/services/time_parser.rb
52
+ - lib/tranzito_utils/version.rb
53
+ homepage: https://github.com/Tranzito/tranzito_utils
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ homepage_uri: https://github.com/Tranzito/tranzito_utils
58
+ source_code_uri: https://github.com/Tranzito/tranzito_utils
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.1.2
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Ruby gem contain several modules mainly containing the helpers, concerns
78
+ and services for personal use by Tranzito
79
+ test_files: []