tranzito_utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []