binxtils 0.2.0 → 0.3.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 +4 -4
- data/README.md +55 -2
- data/lib/binxtils/set_period.rb +99 -0
- data/lib/binxtils/sortable_helper.rb +76 -0
- data/lib/binxtils/sortable_table.rb +34 -0
- data/lib/binxtils/version.rb +1 -1
- data/lib/binxtils.rb +3 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e7eb389fe8adfebf07d5c4b775a457a89ffe7cedee03b4c635865ee844b5c237
|
|
4
|
+
data.tar.gz: b05037ae696c23642e45e2d04003e2ba4b76eb3b974ad6157388355aab41bf7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b3874b3af0782086081d84a32ec0c303abbab56775621ceb70f5366ffdfd5436cccfacd559e8bfc5f96894f2ed84a3e381c7f81a9f7991cea7914388039e995
|
|
7
|
+
data.tar.gz: fd12bc0f7d66071963d0bd52a763e27a6cccbddfab58174254031c588e60a8e2003907c2ee343a182a3e91d28acf91590c4dda05bbad55ea4bec8770bdf9f419
|
data/README.md
CHANGED
|
@@ -12,12 +12,65 @@ gem "binxtils"
|
|
|
12
12
|
- **Binxtils::TimeParser** - Parse fuzzy time/date strings into `Time` objects
|
|
13
13
|
- **Binxtils::TimeZoneParser** - Parse and resolve time zone strings
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
### Functionable modules
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
These modules use [Functionable](https://github.com/sethherr/functionable) and are called as class methods:
|
|
18
18
|
|
|
19
19
|
```ruby
|
|
20
20
|
Binxtils::TimeParser.parse("next thursday")
|
|
21
21
|
Binxtils::InputNormalizer.string(" Some Input ")
|
|
22
22
|
Binxtils::TimeZoneParser.parse("Eastern Time")
|
|
23
23
|
```
|
|
24
|
+
|
|
25
|
+
### Rails concerns
|
|
26
|
+
|
|
27
|
+
- **Binxtils::SetPeriod** - Controller concern for time period filtering (hour, day, week, month, year, all, custom). Parses period params, manages timezones, and sets `@time_range`.
|
|
28
|
+
- **Binxtils::SortableTable** - Controller concern providing `sort_column` and `sort_direction` helpers with configurable defaults.
|
|
29
|
+
- **Binxtils::SortableHelper** - View helper for rendering sortable column header links with active-state indicators.
|
|
30
|
+
|
|
31
|
+
Include them in your controllers and helpers:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
class ApplicationController < ActionController::Base
|
|
35
|
+
include Binxtils::SetPeriod
|
|
36
|
+
include Binxtils::SortableTable
|
|
37
|
+
|
|
38
|
+
# Optionally configure the earliest date for the "all" period
|
|
39
|
+
self.default_earliest_time = Time.at(1714460400).freeze
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module ApplicationHelper
|
|
43
|
+
include Binxtils::SortableHelper
|
|
44
|
+
|
|
45
|
+
# Optionally extend the permitted search params
|
|
46
|
+
def default_search_keys
|
|
47
|
+
super + [:organization_id, query_items: []]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`SortableTable` requires a `sortable_columns` method in your controller:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class BikesController < ApplicationController
|
|
56
|
+
def sortable_columns
|
|
57
|
+
%w[created_at updated_at manufacturer_id]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## npm package
|
|
63
|
+
|
|
64
|
+
This repo also publishes `@bikeindex/time-localizer`, an npm package for localizing time elements in the browser. Luxon is bundled into the published package, so consumers don't need to install it separately.
|
|
65
|
+
|
|
66
|
+
To publish a new version: update the version in `package.json`, then run `npm publish` from the repo root (requires npm login with access to the `@bikeindex` scope). The `prepublishOnly` script automatically builds `dist/index.js` before publishing.
|
|
67
|
+
|
|
68
|
+
## Releasing
|
|
69
|
+
|
|
70
|
+
From the `main` branch, run `bin/release` with a version number:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
bin/release 0.3.0
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This bumps the version, commits, tags, pushes, builds and publishes the gem to RubyGems, and creates a GitHub release.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Binxtils
|
|
4
|
+
module SetPeriod
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
PERIOD_TYPES = %w[hour day month year week all next_week next_month].freeze
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
class_attribute :default_earliest_time, default: Time.at(0).freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# For setting periods, particularly for graphing
|
|
14
|
+
def set_period
|
|
15
|
+
set_timezone
|
|
16
|
+
# Set time period
|
|
17
|
+
@period ||= params[:period]
|
|
18
|
+
if @period == "custom"
|
|
19
|
+
if params[:start_time].present?
|
|
20
|
+
@start_time = Binxtils::TimeParser.parse(params[:start_time], @timezone)
|
|
21
|
+
@end_time = Binxtils::TimeParser.parse(params[:end_time], @timezone) || latest_period_date
|
|
22
|
+
@start_time, @end_time = @end_time, @start_time if @start_time > @end_time
|
|
23
|
+
else
|
|
24
|
+
set_time_range_from_period
|
|
25
|
+
end
|
|
26
|
+
elsif params[:search_at].present?
|
|
27
|
+
@period = "custom"
|
|
28
|
+
@search_at = Binxtils::TimeParser.parse(params[:search_at], @timezone)
|
|
29
|
+
offset = params[:period].present? ? params[:period].to_i : 10.minutes.to_i
|
|
30
|
+
@start_time = @search_at - offset
|
|
31
|
+
@end_time = @search_at + offset
|
|
32
|
+
else
|
|
33
|
+
set_time_range_from_period
|
|
34
|
+
end
|
|
35
|
+
# Add this render_chart in here so we don't have to define it in all the controllers
|
|
36
|
+
@render_chart = Binxtils::InputNormalizer.boolean(params[:render_chart])
|
|
37
|
+
@time_range = @start_time..@end_time
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_time_range_from_period
|
|
43
|
+
@period = default_period unless PERIOD_TYPES.include?(@period)
|
|
44
|
+
|
|
45
|
+
case @period
|
|
46
|
+
when "hour"
|
|
47
|
+
@start_time = Time.current - 1.hour
|
|
48
|
+
when "day"
|
|
49
|
+
@start_time = Time.current.beginning_of_day - 1.day
|
|
50
|
+
when "month"
|
|
51
|
+
@start_time = Time.current.beginning_of_day - 30.days
|
|
52
|
+
when "year"
|
|
53
|
+
@start_time = Time.current.beginning_of_day - 1.year
|
|
54
|
+
when "week"
|
|
55
|
+
@start_time = Time.current.beginning_of_day - 1.week
|
|
56
|
+
when "next_month"
|
|
57
|
+
@start_time ||= Time.current
|
|
58
|
+
@end_time = Time.current.beginning_of_day + 30.days
|
|
59
|
+
when "next_week"
|
|
60
|
+
@start_time = Time.current
|
|
61
|
+
@end_time = Time.current.beginning_of_day + 1.week
|
|
62
|
+
when "all"
|
|
63
|
+
@start_time = earliest_period_date
|
|
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 overridden, specifically in invoices
|
|
74
|
+
def latest_period_date
|
|
75
|
+
Time.current
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Separate method so it can be overridden on per controller basis
|
|
79
|
+
def earliest_period_date
|
|
80
|
+
default_earliest_time
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def set_timezone
|
|
84
|
+
return true if @timezone.present?
|
|
85
|
+
|
|
86
|
+
if params[:timezone].present?
|
|
87
|
+
@timezone = Binxtils::TimeZoneParser.parse(params[:timezone])
|
|
88
|
+
session[:timezone] = @timezone&.name
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if session[:timezone].present?
|
|
92
|
+
@timezone ||= Binxtils::TimeZoneParser.parse(session[:timezone])
|
|
93
|
+
Time.zone = @timezone
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
@timezone ||= Binxtils::TimeParser.default_time_zone
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Binxtils
|
|
4
|
+
module SortableHelper
|
|
5
|
+
BASE_SEARCH_KEYS = [
|
|
6
|
+
:direction, :sort, # sorting params
|
|
7
|
+
:period, :start_time, :end_time, :render_chart, # Time period params
|
|
8
|
+
:user_id, :query, :per_page # General search params
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
# Set defaults, required for testing
|
|
12
|
+
def sort_column = "id"
|
|
13
|
+
def sort_direction = "desc"
|
|
14
|
+
|
|
15
|
+
def default_search_keys
|
|
16
|
+
BASE_SEARCH_KEYS
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def sortable(column, title = nil, html_options = {}, &block)
|
|
20
|
+
if title.is_a?(Hash) # If title is a hash, it wasn't passed
|
|
21
|
+
html_options = title
|
|
22
|
+
title = nil
|
|
23
|
+
end
|
|
24
|
+
title ||= column.gsub(/_(id|at)\z/, "").titleize
|
|
25
|
+
|
|
26
|
+
# Check for render_sortable - otherwise default to rendering
|
|
27
|
+
render_sortable = html_options.key?(:render_sortable) ? html_options[:render_sortable] : !html_options[:skip_sortable]
|
|
28
|
+
return title unless render_sortable
|
|
29
|
+
|
|
30
|
+
html_options[:class] = "#{html_options[:class]} sortable-link"
|
|
31
|
+
direction = (column == sort_column && sort_direction == "desc") ? "asc" : "desc"
|
|
32
|
+
|
|
33
|
+
if column == sort_column
|
|
34
|
+
html_options[:class] += " active"
|
|
35
|
+
span_content = (direction == "asc") ? "\u2193" : "\u2191"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
link_to(sortable_url(column, direction), html_options) do
|
|
39
|
+
concat(block_given? ? capture(&block) : title.html_safe)
|
|
40
|
+
concat(content_tag(:span, span_content, class: "sortable-direction"))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sortable_search_params?(except: [])
|
|
45
|
+
except_keys = %i[direction sort period per_page] + except
|
|
46
|
+
s_params = sortable_search_params.except(*except_keys).values.reject(&:blank?).any?
|
|
47
|
+
|
|
48
|
+
return true if s_params
|
|
49
|
+
return false if except.map(&:to_s).include?("period")
|
|
50
|
+
|
|
51
|
+
params[:period].present? && params[:period] != "all"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def sortable_params
|
|
55
|
+
@sortable_params ||= sortable_search_params.as_json.filter_map do |k, v|
|
|
56
|
+
next if v.blank? || k == "sort" && v == default_column ||
|
|
57
|
+
k == "direction" && v == default_direction
|
|
58
|
+
[k, v]
|
|
59
|
+
end.to_h.with_indifferent_access
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def sortable_search_params
|
|
63
|
+
return @sortable_search_params if defined?(@sortable_search_params)
|
|
64
|
+
|
|
65
|
+
search_param_keys = params.keys.select { |k| k.to_s.start_with?("search_") } # match params starting with search_
|
|
66
|
+
@sortable_search_params = params.permit(*(default_search_keys | search_param_keys))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# This is a separate method purely for testing purposes, so it can be stubbed
|
|
72
|
+
def sortable_url(sort, direction)
|
|
73
|
+
url_for(sortable_search_params.merge(sort:, direction:))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Binxtils
|
|
4
|
+
module SortableTable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
before_action :set_period, only: %i[index]
|
|
9
|
+
|
|
10
|
+
helper_method :sort_column, :sort_direction, :default_column, :default_direction
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def sort_column
|
|
14
|
+
@sort_column ||= sortable_columns.include?(params[:sort]) ? params[:sort] : default_column
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def sort_direction
|
|
18
|
+
%w[asc desc].include?(params[:direction]) ? params[:direction] : default_direction
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def permitted_time_range_columns
|
|
22
|
+
%w[created_at updated_at].freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def current_time_range_column
|
|
26
|
+
permitted_time_range_columns.include?(sort_column) ? sort_column : permitted_time_range_columns.first
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# So they can be overridden
|
|
30
|
+
def default_direction = "desc"
|
|
31
|
+
|
|
32
|
+
def default_column = sortable_columns.first
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/binxtils/version.rb
CHANGED
data/lib/binxtils.rb
CHANGED
|
@@ -10,4 +10,7 @@ require "rails-html-sanitizer"
|
|
|
10
10
|
require_relative "binxtils/input_normalizer"
|
|
11
11
|
require_relative "binxtils/time_zone_parser"
|
|
12
12
|
require_relative "binxtils/time_parser"
|
|
13
|
+
require_relative "binxtils/set_period"
|
|
14
|
+
require_relative "binxtils/sortable_table"
|
|
15
|
+
require_relative "binxtils/sortable_helper"
|
|
13
16
|
require_relative "binxtils/railtie" if defined?(Rails::Railtie)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: binxtils
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bike Index
|
|
@@ -90,6 +90,9 @@ files:
|
|
|
90
90
|
- lib/binxtils.rb
|
|
91
91
|
- lib/binxtils/input_normalizer.rb
|
|
92
92
|
- lib/binxtils/railtie.rb
|
|
93
|
+
- lib/binxtils/set_period.rb
|
|
94
|
+
- lib/binxtils/sortable_helper.rb
|
|
95
|
+
- lib/binxtils/sortable_table.rb
|
|
93
96
|
- lib/binxtils/time_parser.rb
|
|
94
97
|
- lib/binxtils/time_zone_parser.rb
|
|
95
98
|
- lib/binxtils/version.rb
|