Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
27 changes: 27 additions & 0 deletions app/views/settings/gtt/_general.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,31 @@
min: 0, max: 15, step: 1) %>
<em class="info"><%= l(:text_geojson_precision_info) %></em>
</p>

<p>
<%= content_tag(:label, l(:label_distance_unit)) %>
<%= select_tag 'settings[distance_unit]',
options_for_select(
RedmineGtt::DistanceUnit::METERS_PER_UNIT.keys.map { |u|
[l(:"gtt_distance_unit_#{u}"), u]
},
RedmineGtt::DistanceUnit.current
) %>
<em class="info"><%= l(:text_distance_unit_info) %></em>
</p>

<p>
<%= content_tag(:label, l(:label_api_distance_unit)) %>
<%# reserved: the API contract is meters-only for now; the setting
exists so a later version can offer more units without a
settings migration %>
<%= select_tag 'settings[api_distance_unit]',
options_for_select(
RedmineGtt::DistanceUnit::API_UNITS.map { |u|
[l(:"gtt_distance_unit_#{u}"), u]
},
RedmineGtt::DistanceUnit.api_unit
) %>
<em class="info"><%= l(:text_api_distance_unit_info) %></em>
</p>
</fieldset>
9 changes: 9 additions & 0 deletions config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ de:
gtt_hide_map_for_invalid_geom_info: Bitte bearbeiten Sie die Anfrage und stellen
Sie die Geometrie ein, um die Karte zu sehen.
label_default_measure_enabled: Zeige Steuerung zum Messen von Länge und Fläche
label_distance_unit: "Anzeigeeinheit für Entfernungen"
text_distance_unit_info: "Einheit für angezeigte und eingegebene Entfernungen (Distanzfilter, Distanzspalte, Beobachtungsradius)."
Comment thread
dkastl marked this conversation as resolved.
label_api_distance_unit: "API-Entfernungseinheit"
text_api_distance_unit_info: "Die REST-API verwendet in dieser Version immer Meter."
gtt_distance_unit_m: "Meter (m)"
gtt_distance_unit_km: "Kilometer (km)"
gtt_distance_unit_ft: "Fuß (ft)"
gtt_distance_unit_mi: "Meilen (mi)"
gtt_distance_unit_nm: "Seemeilen (nm)"
gtt_label_watch_nearby_legend: "Automatisch beobachten: Tickets in der Nähe"
gtt_label_watch_nearby: "Neue Tickets in der Nähe meines Standorts beobachten"
gtt_label_watch_nearby_radius: "Entfernung"
Expand Down
9 changes: 9 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ en:
text_geojson_precision_info: "Decimal places for coordinates in GeoJSON output (EPSG:4326). 6 is about 0.11 m; lower values shrink the payload."
gtt_hide_map_for_invalid_geom_info: "Please edit the issue and set the geometry
to see the map."
label_distance_unit: "Distance display unit"
text_distance_unit_info: "Unit for distances shown and entered in the UI (distance filter, distance column, watch radius)."
Comment thread
dkastl marked this conversation as resolved.
label_api_distance_unit: "API distance unit"
text_api_distance_unit_info: "The REST API always uses meters in this version."
gtt_distance_unit_m: "Meters (m)"
gtt_distance_unit_km: "Kilometers (km)"
gtt_distance_unit_ft: "Feet (ft)"
gtt_distance_unit_mi: "Miles (mi)"
gtt_distance_unit_nm: "Nautical miles (nm)"

gtt_label_watch_nearby_legend: "Auto watch: nearby issues"
gtt_label_watch_nearby: "Watch new issues near my location"
Expand Down
9 changes: 9 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ ja:
label_geojson_precision: "GeoJSON座標の小数点桁数"
text_geojson_precision_info: "GeoJSON出力(EPSG:4326)の座標の小数点以下の桁数。6桁で約0.11m。小さくするとデータ量を削減できます。"
gtt_hide_map_for_invalid_geom_info: "地図を見るにはチケットを編集して位置情報を登録してください。"
label_distance_unit: "距離の表示単位"
text_distance_unit_info: "画面で表示・入力する距離の単位(距離フィルタ、距離カラム、ウォッチ半径)。"
Comment thread
dkastl marked this conversation as resolved.
label_api_distance_unit: "APIの距離単位"
text_api_distance_unit_info: "このバージョンではREST APIは常にメートルを使用します。"
gtt_distance_unit_m: "メートル (m)"
gtt_distance_unit_km: "キロメートル (km)"
gtt_distance_unit_ft: "フィート (ft)"
gtt_distance_unit_mi: "マイル (mi)"
gtt_distance_unit_nm: "海里 (nm)"

gtt_label_watch_nearby_legend: "自動ウォッチ: 近くのチケット"
gtt_label_watch_nearby: "登録した所在地の近くの新しいチケットをウォッチする"
Expand Down
4 changes: 3 additions & 1 deletion init.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
'hide_map_for_invalid_geom' => false,
# Keep in sync with RedmineGtt::DEFAULT_GEOJSON_PRECISION (literal here to
# avoid autoloading the module during plugin registration).
'geojson_precision' => 6
'geojson_precision' => 6,
'distance_unit' => 'm',
'api_distance_unit' => 'm'
},
partial: 'settings/gtt/settings'
)
Expand Down
1 change: 1 addition & 0 deletions lib/redmine_gtt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def self.setup_controller_patches
RedmineGtt::Patches::ProjectsControllerPatch.apply
RedmineGtt::Patches::UsersControllerPatch.apply
RedmineGtt::Patches::IssuesHelperPatch.apply
RedmineGtt::Patches::QueriesHelperPatch.apply

[
IssuesController,
Expand Down
48 changes: 48 additions & 0 deletions lib/redmine_gtt/distance_unit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

module RedmineGtt
# Converts between meters and the display unit configured in the plugin
# settings (#10). Meters are the internal unit everywhere: storage, SQL,
# the filter wire format and the REST API all stay metric; only what the
# user sees and types is converted.
module DistanceUnit

METERS_PER_UNIT = {
'm' => 1.0,
'km' => 1000.0,
'ft' => 0.3048,
'mi' => 1609.344,
'nm' => 1852.0
}.freeze

DEFAULT = 'm'

# Units the REST API may use. Reserved for future flexibility: the API
# contract is meters and only meters for now, but the setting exists so
# a later version can widen this list without a settings migration.
API_UNITS = %w(m).freeze

# The configured display unit, falling back to meters for anything
# unknown (empty setting, typo from a manually edited setting hash).
def self.current
unit = Setting.plugin_redmine_gtt['distance_unit'].to_s
METERS_PER_UNIT.key?(unit) ? unit : DEFAULT
end

# The unit used by the REST API. Clamped to meters regardless of the
# stored value while API_UNITS has a single entry.
def self.api_unit
unit = Setting.plugin_redmine_gtt['api_distance_unit'].to_s
API_UNITS.include?(unit) ? unit : DEFAULT
end

def self.from_meters(meters, unit = current)
meters.to_f / METERS_PER_UNIT.fetch(unit)
end

def self.to_meters(value, unit = current)
value.to_f * METERS_PER_UNIT.fetch(unit)
end
Comment thread
dkastl marked this conversation as resolved.

end
end
3 changes: 2 additions & 1 deletion lib/redmine_gtt/patches/issue_query_patch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def available_columns
caption: :field_geom
)
columns << QueryColumn.new(:distance,
caption: :label_gtt_distance,
# header shows the configured display unit, e.g. "Distance (km)"
caption: -> { "#{l(:label_gtt_distance)} (#{DistanceUnit.current})" },
sortable: lambda{
lng, lat = find_center_point
distance_query lng, lat
Expand Down
38 changes: 38 additions & 0 deletions lib/redmine_gtt/patches/queries_helper_patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module RedmineGtt
module Patches

# Renders the distance column in the configured display unit (#10).
# The underlying value stays meters (it is what the query selects and
# what the REST API returns); only the rendered list/CSV cell converts.
module QueriesHelperPatch

def self.apply
QueriesHelper.prepend self unless QueriesHelper < self
end

def column_value(column, item, value)
if column.name == :distance && value.present?
gtt_format_distance(value)
else
super
end
end

def csv_value(column, object, value)
if column.name == :distance && value.present?
# mirror core's CSV float formatting incl. the decimal separator
gtt_format_distance(value).gsub('.', l(:general_csv_decimal_separator))
else
super
end
end

private

def gtt_format_distance(meters)
sprintf('%.2f', RedmineGtt::DistanceUnit.from_meters(meters))
end

end
end
end
67 changes: 67 additions & 0 deletions test/unit/distance_unit_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require_relative '../test_helper'

class DistanceUnitTest < GttTest

teardown do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'distance_unit' => 'm', 'api_distance_unit' => 'm'
)
end

test 'defaults to meters' do
assert_equal 'm', RedmineGtt::DistanceUnit.current
assert_equal 'm', RedmineGtt::DistanceUnit.api_unit
end

test 'falls back to meters for unknown units' do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'distance_unit' => 'parsec'
)
assert_equal 'm', RedmineGtt::DistanceUnit.current
end

test 'reads the configured display unit' do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'distance_unit' => 'km'
)
assert_equal 'km', RedmineGtt::DistanceUnit.current
end

test 'api unit is clamped to meters regardless of the stored value' do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'api_distance_unit' => 'km'
)
assert_equal 'm', RedmineGtt::DistanceUnit.api_unit
end

test 'converts from meters into the given unit' do
assert_equal 1.5, RedmineGtt::DistanceUnit.from_meters(1500, 'km')
assert_in_delta 0.932, RedmineGtt::DistanceUnit.from_meters(1500, 'mi'), 0.001
assert_in_delta 4921.26, RedmineGtt::DistanceUnit.from_meters(1500, 'ft'), 0.01
assert_in_delta 0.81, RedmineGtt::DistanceUnit.from_meters(1500, 'nm'), 0.001
assert_equal 1500.0, RedmineGtt::DistanceUnit.from_meters(1500, 'm')
end

test 'converts a unit value into meters' do
assert_equal 1500.0, RedmineGtt::DistanceUnit.to_meters(1.5, 'km')
assert_in_delta 1609.344, RedmineGtt::DistanceUnit.to_meters(1, 'mi'), 0.001
assert_equal 25.0, RedmineGtt::DistanceUnit.to_meters(25, 'm')
end

test 'conversions honor the configured unit by default' do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'distance_unit' => 'km'
)
assert_equal 2.0, RedmineGtt::DistanceUnit.from_meters(2000)
assert_equal 2000.0, RedmineGtt::DistanceUnit.to_meters(2)
end

test 'distance column caption carries the unit' do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'distance_unit' => 'mi'
)
column = IssueQuery.new.available_columns.detect { |c| c.name == :distance }
assert column, 'distance column should be available on global queries'
assert_match(/\(mi\)\z/, column.caption)
end
end
39 changes: 39 additions & 0 deletions test/unit/queries_helper_patch_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require_relative '../test_helper'

class QueriesHelperPatchTest < Redmine::HelperTest
include QueriesHelper
include ApplicationHelper
include ERB::Util

teardown do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'distance_unit' => 'm'
)
end

def distance_column
IssueQuery.new.available_columns.detect { |c| c.name == :distance }
end

test 'renders the distance column in the configured unit' do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'distance_unit' => 'km'
)
assert_equal '1.50', column_value(distance_column, nil, 1500.0)
end

test 'renders meters by default' do
assert_equal '1500.00', column_value(distance_column, nil, 1500.0)
end

test 'renders the csv value with the locale decimal separator' do
Setting.plugin_redmine_gtt = Setting.plugin_redmine_gtt.merge(
'distance_unit' => 'km'
)
assert_equal '1.50', csv_value(distance_column, nil, 1500.0)
end

test 'leaves blank distance values to core' do
assert_equal '', column_value(distance_column, nil, nil).to_s
end
end