aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorent Peyraud2017-06-20 13:39:53 +0200
committerFlorent Peyraud2017-06-20 13:39:53 +0200
commitb749813066da2353dfd5cf938f961234a68153ce (patch)
tree1b91a9f154f835b2b73c922e15d01139ddeb79fd
parent323934a122b589c8dcff9add8ab20a0f91a2da56 (diff)
parentbf1136bef6430b16732f42c8ce31ef046fe6e239 (diff)
downloadchouette-core-b749813066da2353dfd5cf938f961234a68153ce.tar.bz2
Merge branch 'master' of github.com:AF83/stif-boiv
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/stylesheets/components/_tables.sass2
-rw-r--r--app/controllers/calendars_controller.rb6
-rw-r--r--app/controllers/lines_controller.rb5
-rw-r--r--app/controllers/referentials_controller.rb28
-rw-r--r--app/controllers/routes_controller.rb6
-rw-r--r--app/controllers/routing_constraint_zones_controller.rb4
-rw-r--r--app/controllers/time_tables_controller.rb4
-rw-r--r--app/controllers/workbenches_controller.rb4
-rw-r--r--app/decorators/calendar_decorator.rb18
-rw-r--r--app/decorators/company_decorator.rb41
-rw-r--r--app/decorators/line_decorator.rb45
-rw-r--r--app/decorators/model_decorator.rb3
-rw-r--r--app/decorators/referential_decorator.rb56
-rw-r--r--app/decorators/route_decorator.rb64
-rw-r--r--app/decorators/routing_constraint_zone_decorator.rb42
-rw-r--r--app/decorators/time_table_decorator.rb53
-rw-r--r--app/helpers/links_helper.rb5
-rw-r--r--app/helpers/multiple_selection_toolbox_helper.rb40
-rw-r--r--app/helpers/table_builder_helper.rb234
-rw-r--r--app/helpers/table_builder_helper/column.rb36
-rw-r--r--app/helpers/table_builder_helper/custom_links.rb77
-rw-r--r--app/helpers/table_builder_helper/url.rb25
-rw-r--r--app/models/chouette/journey_pattern.rb2
-rw-r--r--app/views/calendars/show.html.slim10
-rw-r--r--app/views/lines/show.html.slim17
-rw-r--r--app/views/referentials/show.html.slim24
-rw-r--r--app/views/routes/show.html.slim17
-rw-r--r--app/views/routing_constraint_zones/show.html.slim13
-rw-r--r--app/views/time_tables/show.html.slim21
-rw-r--r--app/views/workbenches/_filters.html.slim4
-rw-r--r--app/views/workbenches/show.html.slim53
-rw-r--r--config/locales/journey_patterns.en.yml6
-rw-r--r--config/locales/journey_patterns.fr.yml6
-rw-r--r--lib/html_element.rb15
-rw-r--r--lib/link.rb10
-rw-r--r--spec/factories/chouette_2_factories.rb1
-rw-r--r--spec/features/referentials_permissions_spec.rb2
-rw-r--r--spec/helpers/table_builder_helper/column_spec.rb23
-rw-r--r--spec/helpers/table_builder_helper/custom_links_spec.rb27
-rw-r--r--spec/helpers/table_builder_helper_spec.rb372
-rw-r--r--spec/models/chouette/journey_pattern_spec.rb1
-rw-r--r--spec/support/pundit/policies.rb2
-rw-r--r--spec/views/lines/show.html.erb_spec.rb8
-rw-r--r--spec/views/time_tables/show.html.erb_spec.rb9
46 files changed, 1347 insertions, 97 deletions
diff --git a/Gemfile b/Gemfile
index 814a5ef14..203945992 100644
--- a/Gemfile
+++ b/Gemfile
@@ -161,6 +161,7 @@ group :test do
gem 'cucumber-rails', require: false
gem 'simplecov', :require => false
gem 'simplecov-rcov', :require => false
+ gem 'htmlbeautifier'
end
group :test, :development, :dev do
diff --git a/Gemfile.lock b/Gemfile.lock
index e87e6bb3b..eea6f4ba5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -261,6 +261,7 @@ GEM
hashdiff (0.3.2)
highline (1.7.8)
hike (1.2.3)
+ htmlbeautifier (1.3.1)
httparty (0.14.0)
multi_xml (>= 0.5.2)
i18n (0.8.1)
@@ -604,6 +605,7 @@ DEPENDENCIES
georuby-ext (= 0.0.5)
google-analytics-rails
has_array_of!
+ htmlbeautifier
i18n-tasks
inherited_resources
jbuilder (~> 2.0)
diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass
index e3a33e131..4e1e073da 100644
--- a/app/assets/stylesheets/components/_tables.sass
+++ b/app/assets/stylesheets/components/_tables.sass
@@ -148,7 +148,7 @@
background-color: #fff
box-shadow: 0 0 3px $darkgrey
position: fixed
- z-index: 2000
+ z-index: 999
right: 50px
bottom: 15px
diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb
index 86d567882..5662316b8 100644
--- a/app/controllers/calendars_controller.rb
+++ b/app/controllers/calendars_controller.rb
@@ -5,6 +5,12 @@ class CalendarsController < BreadcrumbController
respond_to :html
respond_to :js, only: :index
+ def show
+ show! do
+ @calendar = @calendar.decorate
+ end
+ end
+
private
def calendar_params
permitted_params = [:id, :name, :short_name, periods_attributes: [:id, :begin, :end, :_destroy], date_values_attributes: [:id, :value, :_destroy]]
diff --git a/app/controllers/lines_controller.rb b/app/controllers/lines_controller.rb
index 7eedaeb05..1e2056aad 100644
--- a/app/controllers/lines_controller.rb
+++ b/app/controllers/lines_controller.rb
@@ -25,6 +25,11 @@ class LinesController < BreadcrumbController
def show
@group_of_lines = resource.group_of_lines
show! do
+ @line = @line.decorate(context: {
+ line_referential: @line_referential,
+ current_organisation: current_organisation
+ })
+
build_breadcrumb :show
end
end
diff --git a/app/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb
index aa5b359da..1239d512f 100644
--- a/app/controllers/referentials_controller.rb
+++ b/app/controllers/referentials_controller.rb
@@ -24,19 +24,21 @@ class ReferentialsController < BreadcrumbController
end
def show
- resource.switch
- show! do |format|
- format.json {
- render :json => { :lines_count => resource.lines.count,
- :networks_count => resource.networks.count,
- :vehicle_journeys_count => resource.vehicle_journeys.count + resource.vehicle_journey_frequencies.count,
- :time_tables_count => resource.time_tables.count,
- :referential_id => resource.id}
- }
- format.html { build_breadcrumb :show}
- end
-
- @reflines = lines_collection.paginate(page: params[:page], per_page: 10)
+ resource.switch
+ show! do |format|
+ @referential = @referential.decorate
+
+ format.json {
+ render :json => { :lines_count => resource.lines.count,
+ :networks_count => resource.networks.count,
+ :vehicle_journeys_count => resource.vehicle_journeys.count + resource.vehicle_journey_frequencies.count,
+ :time_tables_count => resource.time_tables.count,
+ :referential_id => resource.id}
+ }
+ format.html { build_breadcrumb :show}
+ end
+
+ @reflines = lines_collection.paginate(page: params[:page], per_page: 10)
end
def edit
diff --git a/app/controllers/routes_controller.rb b/app/controllers/routes_controller.rb
index 73febc4b9..786bd57cc 100644
--- a/app/controllers/routes_controller.rb
+++ b/app/controllers/routes_controller.rb
@@ -42,6 +42,12 @@ class RoutesController < ChouetteController
end
show! do
+ @route = @route.decorate(context: {
+ referential: @referential,
+ line: @line,
+ route_sp: @route_sp
+ })
+
build_breadcrumb :show
end
end
diff --git a/app/controllers/routing_constraint_zones_controller.rb b/app/controllers/routing_constraint_zones_controller.rb
index 7707427b0..9d2fd712c 100644
--- a/app/controllers/routing_constraint_zones_controller.rb
+++ b/app/controllers/routing_constraint_zones_controller.rb
@@ -16,6 +16,10 @@ class RoutingConstraintZonesController < ChouetteController
def show
@routing_constraint_zone = collection.find(params[:id])
+ @routing_constraint_zone = @routing_constraint_zone.decorate(context: {
+ referential: @referential,
+ line: @line
+ })
end
protected
diff --git a/app/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb
index 5c4552afb..3704f2885 100644
--- a/app/controllers/time_tables_controller.rb
+++ b/app/controllers/time_tables_controller.rb
@@ -14,6 +14,10 @@ class TimeTablesController < ChouetteController
@year = params[:year] ? params[:year].to_i : Date.today.cwyear
@time_table_combination = TimeTableCombination.new
show! do
+ @time_table = @time_table.decorate(context: {
+ referential: @referential
+ })
+
build_breadcrumb :show
end
end
diff --git a/app/controllers/workbenches_controller.rb b/app/controllers/workbenches_controller.rb
index ccd55965b..1447c27de 100644
--- a/app/controllers/workbenches_controller.rb
+++ b/app/controllers/workbenches_controller.rb
@@ -14,6 +14,10 @@ class WorkbenchesController < BreadcrumbController
q_for_result =
scope.ransack(params[:q].merge(archived_at_not_null: nil, archived_at_null: nil))
@wbench_refs = sort_result(q_for_result.result).paginate(page: params[:page], per_page: 30)
+ @wbench_refs = ModelDecorator.decorate(
+ @wbench_refs,
+ with: ReferentialDecorator
+ )
@q = scope.ransack(params[:q])
show! do
diff --git a/app/decorators/calendar_decorator.rb b/app/decorators/calendar_decorator.rb
new file mode 100644
index 000000000..37e2cfe80
--- /dev/null
+++ b/app/decorators/calendar_decorator.rb
@@ -0,0 +1,18 @@
+class CalendarDecorator < Draper::Decorator
+ delegate_all
+
+ def action_links
+ links = []
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ content: h.destroy_link_content,
+ href: h.calendar_path(object),
+ method: :delete,
+ data: { confirm: h.t('calendars.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+end
diff --git a/app/decorators/company_decorator.rb b/app/decorators/company_decorator.rb
index 3a0cc16ce..51c1f3c61 100644
--- a/app/decorators/company_decorator.rb
+++ b/app/decorators/company_decorator.rb
@@ -1,4 +1,6 @@
class CompanyDecorator < Draper::Decorator
+ decorates Chouette::Company
+
delegate_all
def self.collection_decorator_class
@@ -9,4 +11,43 @@ class CompanyDecorator < Draper::Decorator
object.lines.count
end
+ # Requires:
+ # context: {
+ # line_referential:
+ # }
+ def action_links
+ links = []
+
+ if h.policy(Chouette::Company).create?
+ links << Link.new(
+ content: h.t('companies.actions.new'),
+ href: h.new_line_referential_company_path(context[:line_referential])
+ )
+ end
+
+ if h.policy(object).update?
+ links << Link.new(
+ content: h.t('companies.actions.edit'),
+ href: h.edit_line_referential_company_path(
+ context[:line_referential],
+ object
+ )
+ )
+ end
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ content: t('companies.actions.destroy'),
+ href: h.line_referential_company_path(
+ context[:line_referential],
+ object
+ ),
+ method: :delete,
+ data: { confirm: h.t('companies.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+
end
diff --git a/app/decorators/line_decorator.rb b/app/decorators/line_decorator.rb
new file mode 100644
index 000000000..f351103b2
--- /dev/null
+++ b/app/decorators/line_decorator.rb
@@ -0,0 +1,45 @@
+class LineDecorator < Draper::Decorator
+ decorates Chouette::Line
+
+ delegate_all
+
+ # Requires:
+ # context: {
+ # line_referential: ,
+ # current_organisation:
+ # }
+ def action_links
+ links = []
+
+ links << Link.new(
+ content: h.t('lines.actions.show_network'),
+ href: [context[:line_referential], object.network]
+ )
+
+ links << Link.new(
+ content: h.t('lines.actions.show_company'),
+ href: [context[:line_referential], object.company]
+ )
+
+ if h.policy(Chouette::Line).create? &&
+ context[:line_referential].organisations.include?(
+ context[:current_organisation]
+ )
+ links << Link.new(
+ content: h.t('lines.actions.new'),
+ href: h.new_line_referential_line_path(context[:line_referential])
+ )
+ end
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ content: h.destroy_link_content('lines.actions.destroy_confirm'),
+ href: h.line_referential_line_path(context[:line_referential], object),
+ method: :delete,
+ data: { confirm: h.t('lines.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+end
diff --git a/app/decorators/model_decorator.rb b/app/decorators/model_decorator.rb
new file mode 100644
index 000000000..dee014cc3
--- /dev/null
+++ b/app/decorators/model_decorator.rb
@@ -0,0 +1,3 @@
+class ModelDecorator < PaginatingDecorator
+ delegate :model
+end
diff --git a/app/decorators/referential_decorator.rb b/app/decorators/referential_decorator.rb
new file mode 100644
index 000000000..9107de0b3
--- /dev/null
+++ b/app/decorators/referential_decorator.rb
@@ -0,0 +1,56 @@
+class ReferentialDecorator < Draper::Decorator
+ delegate_all
+
+ def action_links
+ links = [
+ Link.new(
+ content: h.t('time_tables.index.title'),
+ href: h.referential_time_tables_path(object)
+ )
+ ]
+
+ if h.policy(object).clone?
+ links << Link.new(
+ content: h.t('actions.clone'),
+ href: h.new_referential_path(from: object.id)
+ )
+ end
+
+ if h.policy(object).edit?
+ links << HTMLElement.new(
+ :button,
+ 'Purger',
+ type: 'button',
+ data: {
+ toggle: 'modal',
+ target: '#purgeModal'
+ }
+ )
+
+ if object.archived?
+ links << Link.new(
+ content: h.t('actions.unarchive'),
+ href: h.unarchive_referential_path(object.id),
+ method: :put
+ )
+ else
+ links << Link.new(
+ content: h.t('actions.archive'),
+ href: h.archive_referential_path(object.id),
+ method: :put
+ )
+ end
+ end
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ content: h.destroy_link_content,
+ href: h.referential_path(object),
+ method: :delete,
+ data: { confirm: h.t('referentials.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+end
diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb
new file mode 100644
index 000000000..99b174dff
--- /dev/null
+++ b/app/decorators/route_decorator.rb
@@ -0,0 +1,64 @@
+class RouteDecorator < Draper::Decorator
+ decorates Chouette::Route
+
+ delegate_all
+
+ # Requires:
+ # context: {
+ # referential: ,
+ # line: ,
+ # route_sp
+ # }
+ def action_links
+ links = []
+
+ if context[:route_sp].any?
+ links << Link.new(
+ content: h.t('journey_patterns.index.title'),
+ href: [
+ context[:referential],
+ context[:line],
+ object,
+ :journey_patterns_collection
+ ]
+ )
+ end
+
+ if object.journey_patterns.present?
+ links << Link.new(
+ content: h.t('vehicle_journeys.actions.index'),
+ href: [
+ context[:referential],
+ context[:line],
+ object,
+ :vehicle_journeys
+ ]
+ )
+ end
+
+ links << Link.new(
+ content: h.t('vehicle_journey_exports.new.title'),
+ href: h.referential_line_route_vehicle_journey_exports_path(
+ context[:referential],
+ context[:line],
+ object,
+ format: :zip
+ )
+ )
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ content: h.destroy_link_content,
+ href: h.referential_line_route_path(
+ context[:referential],
+ context[:line],
+ object
+ ),
+ method: :delete,
+ data: { confirm: h.t('routes.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+end
diff --git a/app/decorators/routing_constraint_zone_decorator.rb b/app/decorators/routing_constraint_zone_decorator.rb
new file mode 100644
index 000000000..0b438a554
--- /dev/null
+++ b/app/decorators/routing_constraint_zone_decorator.rb
@@ -0,0 +1,42 @@
+class RoutingConstraintZoneDecorator < Draper::Decorator
+ decorates Chouette::RoutingConstraintZone
+
+ delegate_all
+
+ # Requires:
+ # context: {
+ # referential: ,
+ # line:
+ # }
+ def action_links
+ links = []
+
+ if h.policy(object).update?
+ links << Link.new(
+ content: h.t('actions.edit'),
+ href: h.edit_referential_line_routing_constraint_zone_path(
+ context[:referential],
+ context[:line],
+ object
+ )
+ )
+ end
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ content: h.destroy_link_content,
+ href: h.referential_line_routing_constraint_zone_path(
+ context[:referential],
+ context[:line],
+ object
+ ),
+ method: :delete,
+ data: {
+ confirm: h.t('routing_constraint_zones.actions.destroy_confirm')
+ }
+ )
+ end
+
+ links
+ end
+end
diff --git a/app/decorators/time_table_decorator.rb b/app/decorators/time_table_decorator.rb
new file mode 100644
index 000000000..526537310
--- /dev/null
+++ b/app/decorators/time_table_decorator.rb
@@ -0,0 +1,53 @@
+class TimeTableDecorator < Draper::Decorator
+ decorates Chouette::TimeTable
+
+ delegate_all
+
+ # Requires:
+ # context: {
+ # referential: ,
+ # }
+ def action_links
+ links = []
+
+ if object.calendar
+ links << Link.new(
+ content: h.t('actions.actualize'),
+ href: h.actualize_referential_time_table_path(
+ context[:referential],
+ object
+ ),
+ method: :post
+ )
+ end
+
+ links << Link.new(
+ content: h.t('actions.combine'),
+ href: h.new_referential_time_table_time_table_combination_path(
+ context[:referential],
+ object
+ )
+ )
+
+ if h.policy(object).duplicate?
+ links << Link.new(
+ content: h.t('actions.duplicate'),
+ href: h.duplicate_referential_time_table_path(
+ context[:referential],
+ object
+ )
+ )
+ end
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ content: h.destroy_link_content,
+ href: h.referential_time_table_path(context[:referential], object),
+ method: :delete,
+ data: { confirm: h.t('time_tables.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+end
diff --git a/app/helpers/links_helper.rb b/app/helpers/links_helper.rb
new file mode 100644
index 000000000..683b66a52
--- /dev/null
+++ b/app/helpers/links_helper.rb
@@ -0,0 +1,5 @@
+module LinksHelper
+ def destroy_link_content(translation_key = 'actions.destroy')
+ content_tag(:span, nil, class: 'fa fa-trash') + t(translation_key)
+ end
+end
diff --git a/app/helpers/multiple_selection_toolbox_helper.rb b/app/helpers/multiple_selection_toolbox_helper.rb
new file mode 100644
index 000000000..85294af6d
--- /dev/null
+++ b/app/helpers/multiple_selection_toolbox_helper.rb
@@ -0,0 +1,40 @@
+module MultipleSelectionToolboxHelper
+ # Box of links that floats at the bottom right of the page
+ def multiple_selection_toolbox(actions)
+ links = content_tag :ul do
+ delete_path = nil
+
+ if params[:controller] = 'workbenches'
+ delete_path = referentials_workbench_path
+ end
+
+ actions.map do |action|
+ if action == :delete
+ action_link = link_to(
+ '#',
+ method: :delete,
+ data: {
+ path: delete_path,
+ confirm: 'Etes-vous sûr(e) de vouloir effectuer cette action ?'
+ },
+ title: t("actions.#{action}")
+ ) do
+ content_tag :span, '', class: 'fa fa-trash'
+ end
+ end
+
+ content_tag :li, action_link, class: 'st_action'
+ end.join.html_safe
+ end
+
+ label = content_tag(
+ :span,
+ ("<span>0</span> élément(s) sélectionné(s)").html_safe,
+ class: 'info-msg'
+ )
+
+ content_tag :div, '', class: 'select_toolbox noselect' do
+ links + label
+ end
+ end
+end
diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb
new file mode 100644
index 000000000..b93e9b22b
--- /dev/null
+++ b/app/helpers/table_builder_helper.rb
@@ -0,0 +1,234 @@
+require 'table_builder_helper/column'
+require 'table_builder_helper/custom_links'
+require 'table_builder_helper/url'
+
+# table_builder_2
+# A Rails helper that constructs an HTML table from a collection of objects. It
+# receives the collection and an array of columns that get transformed into
+# `<td>`s. A column of checkboxes can be added to the left side of the table
+# for multiple selection. Columns are sortable by default, but sorting can be
+# disabled either at the table level or at the column level. An optional
+# `links` argument takes a set of symbols corresponding to controller actions
+# that should be inserted in a gear menu next to each row in the table. That
+# menu will also be populated with links defined in `collection#action_links`,
+# a list of `Link` objects defined in a decorator for the given object.
+#
+# Depends on `params` and `current_referential`.
+#
+# Example:
+# table_builder_2(
+# @companies,
+# [
+# TableBuilderHelper::Column.new(
+# name: 'ID Codif',
+# attribute: Proc.new { |n| n.try(:objectid).try(:local_id) },
+# sortable: false
+# ),
+# TableBuilderHelper::Column.new(
+# key: :name,
+# attribute: 'name'
+# ),
+# TableBuilderHelper::Column.new(
+# key: :phone,
+# attribute: 'phone'
+# ),
+# TableBuilderHelper::Column.new(
+# key: :email,
+# attribute: 'email'
+# ),
+# TableBuilderHelper::Column.new(
+# key: :url,
+# attribute: 'url'
+# ),
+# ],
+# links: [:show, :edit],
+# cls: 'table has-search'
+# )
+module TableBuilderHelper
+ # TODO: rename this after migration from `table_builder`
+ def table_builder_2(
+ # An `ActiveRecord::Relation`, wrapped in a decorator to provide a list of
+ # `Link` objects via an `#action_links` method
+ collection,
+
+ # An array of `TableBuilderHelper::Column`s
+ columns,
+
+ # When false, no columns will be sortable
+ sortable: true,
+
+ # When true, adds a column of checkboxes to the left side of the table
+ selectable: false,
+
+ # A set of controller actions that will be added as links to the top of the
+ # gear menu
+ links: [],
+
+ # A CSS class to apply to the <table>
+ cls: ''
+ )
+ content_tag :table,
+ thead(collection, columns, sortable, selectable, links.any?) +
+ tbody(collection, columns, selectable, links),
+ class: cls
+ end
+
+ private
+
+ def thead(collection, columns, sortable, selectable, has_links)
+ content_tag :thead do
+ content_tag :tr do
+ hcont = []
+
+ if selectable
+ hcont << content_tag(:th, checkbox(id_name: '0', value: 'all'))
+ end
+
+ columns.each do |column|
+ hcont << content_tag(:th, build_column_header(
+ column,
+ sortable,
+ collection.model,
+ params,
+ params[:sort],
+ params[:direction]
+ ))
+ end
+
+ # Inserts a blank column for the gear menu
+ hcont << content_tag(:th, '') if has_links
+
+ hcont.join.html_safe
+ end
+ end
+ end
+
+ def tbody(collection, columns, selectable, links)
+ content_tag :tbody do
+ collection.map do |item|
+
+ content_tag :tr do
+ bcont = []
+
+ if selectable
+ bcont << content_tag(
+ :td,
+ checkbox(id_name: item.try(:id), value: item.try(:id))
+ )
+ end
+
+ columns.each do |column|
+ value = column.value(item)
+
+ if column_is_linkable?(column)
+ # Build a link to the `item`
+ polymorph_url = URL.polymorphic_url_parts(item)
+ bcont << content_tag(:td, link_to(value, polymorph_url), title: 'Voir')
+ else
+ bcont << content_tag(:td, value)
+ end
+ end
+
+ if links.any?
+ bcont << content_tag(
+ :td,
+ build_links(item, links),
+ class: 'actions'
+ )
+ end
+
+ bcont.join.html_safe
+ end
+ end.join.html_safe
+ end
+ end
+
+ def build_links(item, links)
+ trigger = content_tag(
+ :div,
+ class: 'btn dropdown-toggle',
+ data: { toggle: 'dropdown' }
+ ) do
+ content_tag :span, '', class: 'fa fa-cog'
+ end
+
+ menu = content_tag :ul, class: 'dropdown-menu' do
+ (
+ CustomLinks.new(item, pundit_user, links).links +
+ item.action_links.select { |link| link.is_a?(Link) }
+ ).map do |link|
+ gear_menu_link(link)
+ end.join.html_safe
+ end
+
+ content_tag :div, trigger + menu, class: 'btn-group'
+ end
+
+ def build_column_header(
+ column,
+ table_is_sortable,
+ collection_model,
+ params,
+ sort_on,
+ sort_direction
+ )
+ if !table_is_sortable
+ return column.header_label(collection_model)
+ end
+
+ return column.name if !column.sortable
+
+ direction =
+ if column.key.to_s == sort_on && sort_direction == 'desc'
+ 'asc'
+ else
+ 'desc'
+ end
+
+ link_to(params.merge({direction: direction, sort: column.key})) do
+ arrow_up = content_tag(
+ :span,
+ '',
+ class: "fa fa-sort-asc #{direction == 'desc' ? 'active' : ''}"
+ )
+ arrow_down = content_tag(
+ :span,
+ '',
+ class: "fa fa-sort-desc #{direction == 'asc' ? 'active' : ''}"
+ )
+
+ arrow_icons = content_tag :span, arrow_up + arrow_down, class: 'orderers'
+
+ (
+ column.header_label(collection_model) +
+ arrow_icons
+ ).html_safe
+ end
+ end
+
+ def checkbox(id_name:, value:)
+ content_tag :div, '', class: 'checkbox' do
+ check_box_tag(id_name, value).concat(
+ content_tag(:label, '', for: id_name)
+ )
+ end
+ end
+
+ def column_is_linkable?(column)
+ column.attribute == 'name' || column.attribute == 'comment'
+ end
+
+ def gear_menu_link(link)
+ content_tag(
+ :li,
+ link_to(
+ link.href,
+ method: link.method,
+ data: link.data
+ ) do
+ link.content
+ end,
+ class: ('delete-action' if link.method == :delete)
+ )
+ end
+end
diff --git a/app/helpers/table_builder_helper/column.rb b/app/helpers/table_builder_helper/column.rb
new file mode 100644
index 000000000..800a8282e
--- /dev/null
+++ b/app/helpers/table_builder_helper/column.rb
@@ -0,0 +1,36 @@
+module TableBuilderHelper
+ class Column
+ attr_reader :key, :name, :attribute, :sortable
+
+ def initialize(key: nil, name: '', attribute:, sortable: true)
+ if key.nil? && name.empty?
+ raise ColumnMustHaveKeyOrNameError
+ end
+
+ @key = key
+ @name = name
+ @attribute = attribute
+ @sortable = sortable
+ end
+
+ def value(obj)
+ if @attribute.is_a?(Proc)
+ @attribute.call(obj)
+ else
+ obj.try(@attribute)
+ end
+ end
+
+ def header_label(model = nil)
+ return @name unless @name.empty?
+
+ # Transform `Chouette::Line` into "line"
+ model_key = model.to_s.demodulize.underscore
+
+ I18n.t("activerecord.attributes.#{model_key}.#{@key}")
+ end
+ end
+
+
+ class ColumnMustHaveKeyOrNameError < StandardError; end
+end
diff --git a/app/helpers/table_builder_helper/custom_links.rb b/app/helpers/table_builder_helper/custom_links.rb
new file mode 100644
index 000000000..abb907678
--- /dev/null
+++ b/app/helpers/table_builder_helper/custom_links.rb
@@ -0,0 +1,77 @@
+require 'table_builder_helper/url'
+
+module TableBuilderHelper
+ class CustomLinks
+ ACTIONS_TO_HTTP_METHODS = {
+ delete: :delete,
+ archive: :put,
+ unarchive: :put
+ }
+
+ def initialize(obj, user_context, actions)
+ @obj = obj
+ @user_context = user_context
+ @actions = actions
+ end
+
+ def links
+ actions_after_policy_check.map do |action|
+ Link.new(
+ content: I18n.t("actions.#{action}"),
+ href: polymorphic_url(action),
+ method: method_for_action(action)
+ )
+ end
+ end
+
+ def polymorphic_url(action)
+ polymorph_url = []
+
+ unless [:show, :delete].include?(action)
+ polymorph_url << action
+ end
+
+ polymorph_url += URL.polymorphic_url_parts(@obj)
+ end
+
+ def method_for_action(action)
+ ACTIONS_TO_HTTP_METHODS[action]
+ end
+
+ def actions_after_policy_check
+ @actions.select do |action|
+ # Has policy and can destroy
+ (action == :delete &&
+ Pundit.policy(@user_context, @obj).present? &&
+ Pundit.policy(@user_context, @obj).destroy?) ||
+
+ # Doesn't have policy
+ (action == :delete &&
+ !Pundit.policy(@user_context, @obj).present?) ||
+
+ # Has policy and can update
+ (action == :edit &&
+ Pundit.policy(@user_context, @obj).present? &&
+ Pundit.policy(@user_context, @obj).update?) ||
+
+ # Doesn't have policy
+ (action == :edit &&
+ !Pundit.policy(@user_context, @obj).present?) ||
+
+ # Object isn't archived
+ (action == :archive && !@obj.archived?) ||
+
+ # Object is archived
+ (action == :unarchive && @obj.archived?) ||
+
+ action_is_allowed_regardless_of_policy(action)
+ end
+ end
+
+ private
+
+ def action_is_allowed_regardless_of_policy(action)
+ ![:delete, :edit, :archive, :unarchive].include?(action)
+ end
+ end
+end
diff --git a/app/helpers/table_builder_helper/url.rb b/app/helpers/table_builder_helper/url.rb
new file mode 100644
index 000000000..f60864ac1
--- /dev/null
+++ b/app/helpers/table_builder_helper/url.rb
@@ -0,0 +1,25 @@
+module TableBuilderHelper
+ # Depends on `current_referential`, defined in object controllers
+ class URL
+ def self.polymorphic_url_parts(item)
+ polymorph_url = []
+
+ unless item.is_a?(Calendar) || item.is_a?(Referential)
+ if current_referential
+ polymorph_url << current_referential
+ polymorph_url << item.line if item.respond_to? :line
+ polymorph_url << item.route.line if item.is_a?(Chouette::RoutingConstraintZone)
+ polymorph_url << item if item.respond_to? :line_referential
+ polymorph_url << item.stop_area if item.respond_to? :stop_area
+ polymorph_url << item if item.respond_to? :stop_points || item.is_a?(Chouette::TimeTable)
+ elsif item.respond_to? :referential
+ polymorph_url << item.referential
+ end
+ else
+ polymorph_url << item
+ end
+
+ polymorph_url
+ end
+ end
+end
diff --git a/app/models/chouette/journey_pattern.rb b/app/models/chouette/journey_pattern.rb
index 3c902946d..a146dcff1 100644
--- a/app/models/chouette/journey_pattern.rb
+++ b/app/models/chouette/journey_pattern.rb
@@ -14,7 +14,7 @@ class Chouette::JourneyPattern < Chouette::TridentActiveRecord
validates_presence_of :route
validates_presence_of :name
- validates :stop_points, length: { minimum: 2 }, on: :update
+ validates :stop_points, length: { minimum: 2, too_short: :minimum }, on: :update
enum section_status: { todo: 0, completed: 1, control: 2 }
attr_accessor :control_checked
diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim
index 3886cefaa..26248cea8 100644
--- a/app/views/calendars/show.html.slim
+++ b/app/views/calendars/show.html.slim
@@ -8,10 +8,12 @@
/ Below is secondary actions & optional contents (filters, ...)
.row.mb-sm
.col-lg-12.text-right
- - if policy(@calendar).destroy?
- = link_to calendar_path(@calendar), method: :delete, data: { confirm: t('calendars.actions.destroy_confirm') }, class: 'btn btn-primary' do
- span.fa.fa-trash
- span = t('actions.destroy')
+ - @calendar.action_links.each do |link|
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ = link.content
/ PageContent
.page_content
diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim
index dbc019e72..6f75432e1 100644
--- a/app/views/lines/show.html.slim
+++ b/app/views/lines/show.html.slim
@@ -7,17 +7,12 @@
/ Below is secundary actions & optional contents
.row
.col-lg-12.text-right.mb-sm
- = link_to t('lines.actions.show_network'), [@line_referential, @line.network], class: 'btn btn-primary'
- = link_to t('lines.actions.show_company'), [@line_referential, @line.company], class: 'btn btn-primary'
-
- - if policy(Chouette::Line).create? && @line_referential.organisations.include?(current_organisation)
- = link_to t('lines.actions.new'), new_line_referential_line_path(@line_referential), class: 'btn btn-primary'
- - if false && policy(@line).update?
- = link_to t('lines.actions.edit'), edit_line_referential_line_path(@line_referential, @line), class: 'btn btn-primary'
- - if policy(@line).destroy?
- = link_to line_referential_line_path(@line_referential, @line), method: :delete, data: {confirm: t('lines.actions.destroy_confirm')}, class: 'btn btn-primary' do
- span.fa.fa-trash
- span = t('lines.actions.destroy')
+ - @line.action_links.each do |link|
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ = link.content
/ PageContent
.page_content
diff --git a/app/views/referentials/show.html.slim b/app/views/referentials/show.html.slim
index 3c1e36302..dfa264c0b 100644
--- a/app/views/referentials/show.html.slim
+++ b/app/views/referentials/show.html.slim
@@ -8,23 +8,15 @@
/ Below is secondary actions & optional contents (filters, ...)
.row.mb-sm
.col-lg-12.text-right
- = link_to t('time_tables.index.title'), referential_time_tables_path(@referential), class: 'btn btn-primary'
-
- - if policy(@referential).clone?
- = link_to t('actions.clone'), new_referential_path(from: @referential.id), class: 'btn btn-primary'
-
- - if policy(@referential).edit?
- button.btn.btn-primary type='button' data-toggle='modal' data-target='#purgeModal' Purger
-
- - if @referential.archived?
- = link_to t('actions.unarchive'), unarchive_referential_path(@referential.id), method: :put, class: 'btn btn-primary'
+ - @referential.action_links.each do |link|
+ - if link.is_a?(HTMLElement)
+ = link.to_html(class: 'btn btn-primary')
- else
- = link_to t('actions.archive'), archive_referential_path(@referential.id), method: :put, class: 'btn btn-primary'
-
- - if policy(@referential).destroy?
- = link_to referential_path(@referential), method: :delete, data: {confirm: t('referentials.actions.destroy_confirm')}, class: 'btn btn-primary' do
- span.fa.fa-trash
- span = t('actions.destroy')
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ = link.content
/ PageContent
.page_content
diff --git a/app/views/routes/show.html.slim b/app/views/routes/show.html.slim
index 6d2d4e90d..92a5080ae 100644
--- a/app/views/routes/show.html.slim
+++ b/app/views/routes/show.html.slim
@@ -8,17 +8,12 @@
/ Below is secundary actions & optional contents (filters, ...)
.row.mb-sm
.col-lg-12.text-right
- - if @route_sp.any?
- = link_to t('journey_patterns.index.title'), [@referential, @line, @route, :journey_patterns_collection], class: 'btn btn-primary'
- - if @route.journey_patterns.present?
- = link_to t('vehicle_journeys.actions.index'), [@referential, @line, @route, :vehicle_journeys], class: 'btn btn-primary'
-
- = link_to t('vehicle_journey_exports.new.title'), referential_line_route_vehicle_journey_exports_path(@referential, @line, @route, format: :zip), class: 'btn btn-primary'
-
- - if policy(@route).destroy?
- = link_to referential_line_route_path(@referential, @line, @route), method: :delete, data: {confirm: t('routes.actions.destroy_confirm')}, class: 'btn btn-primary' do
- span.fa.fa-trash
- span = t('actions.destroy')
+ - @route.action_links.each do |link|
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ = link.content
/ PageContent
.page_content
diff --git a/app/views/routing_constraint_zones/show.html.slim b/app/views/routing_constraint_zones/show.html.slim
index f0c244387..1dad4f561 100644
--- a/app/views/routing_constraint_zones/show.html.slim
+++ b/app/views/routing_constraint_zones/show.html.slim
@@ -7,13 +7,12 @@
/ Below is secundary actions & optional contents
.row
.col-lg-12.text-right.mb-sm
- - if policy(@routing_constraint_zone).update?
- = link_to t('actions.edit'), edit_referential_line_routing_constraint_zone_path(@referential, @line, @routing_constraint_zone), class: 'btn btn-primary'
-
- - if policy(@routing_constraint_zone).destroy?
- = link_to referential_line_routing_constraint_zone_path(@referential, @line, @routing_constraint_zone), method: :delete, data: {confirm: t('routing_constraint_zones.actions.destroy_confirm')}, class: 'btn btn-primary' do
- span.fa.fa-trash
- span = t('actions.destroy')
+ - @routing_constraint_zone.action_links.each do |link|
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ = link.content
/ PageContent
.page_content
diff --git a/app/views/time_tables/show.html.slim b/app/views/time_tables/show.html.slim
index 2e71ebb9e..f596fd480 100644
--- a/app/views/time_tables/show.html.slim
+++ b/app/views/time_tables/show.html.slim
@@ -10,21 +10,12 @@
/ Below is secundary actions & optional contents (filters, ...)
.row.mb-sm
.col-lg-12.text-right
- / - if policy(@time_table).create? && @referential.organisation == current_organisation
- / = link_to t('time_tables.actions.new'), new_referential_time_table_path(@referential), class: 'btn btn-primary'
- - if @time_table.calendar
- = link_to t('actions.actualize'), actualize_referential_time_table_path(@referential, @time_table), method: :post, class: 'btn btn-primary'
-
- /- if policy(@time_table).create? && @referential.organisation == current_organisation
- = link_to t('actions.combine'), new_referential_time_table_time_table_combination_path(@referential, @time_table), class: 'btn btn-primary'
-
- - if policy(@time_table).duplicate?
- = link_to t('actions.duplicate'), duplicate_referential_time_table_path(@referential, @time_table), class: 'btn btn-primary'
-
- - if policy(@time_table).destroy?
- = link_to referential_time_table_path(@referential, @time_table), method: :delete, data: {confirm: t('time_tables.actions.destroy_confirm')}, class: 'btn btn-primary' do
- span.fa.fa-trash
- span = t('actions.destroy')
+ - @time_table.action_links.each do |link|
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ = link.content
/ PageContent
.page_content
diff --git a/app/views/workbenches/_filters.html.slim b/app/views/workbenches/_filters.html.slim
index 7c5055963..0aedbdd62 100644
--- a/app/views/workbenches/_filters.html.slim
+++ b/app/views/workbenches/_filters.html.slim
@@ -12,7 +12,7 @@
= f.input :associated_lines_id_eq, as: :select, collection: @workbench.lines.includes(:company).order(:name), input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': 'Indiquez une ligne...' }, label: false, label_method: :display_name, wrapper_html: { class: 'select2ed'}
.form-group.togglable
- = f.label @wbench_refs.human_attribute_name(:status), required: false, class: 'control-label'
+ = f.label Referential.human_attribute_name(:status), required: false, class: 'control-label'
.form-group.checkbox_list
= f.input :archived_at_not_null, label: ("<span>Conservé<span class='fa fa-archive'></span></span>").html_safe, as: :boolean, wrapper_html: { class: 'checkbox-wrapper' }
= f.input :archived_at_null, label: ("<span>En préparation<span class='sb sb-lg sb-preparing'></span></span>").html_safe, as: :boolean, wrapper_html: { class: 'checkbox-wrapper' }
@@ -22,7 +22,7 @@
= f.input :organisation_name_eq_any, collection: Organisation.order('name').pluck(:name), as: :check_boxes, label: false, label_method: lambda{|w| ("<span>#{w}</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' }
.form-group.togglable
- = f.label @wbench_refs.human_attribute_name(:validity_period), required: false, class: 'control-label'
+ = f.label Referential.human_attribute_name(:validity_period), required: false, class: 'control-label'
.filter_menu
= f.simple_fields_for :validity_period do |p|
= p.input :begin_gteq, as: :date, label: t('simple_form.from'), wrapper_html: { class: 'date filter_menu-item' }, default: @begin_range, include_blank: @begin_range ? false : true
diff --git a/app/views/workbenches/show.html.slim b/app/views/workbenches/show.html.slim
index 77e670923..37c396b46 100644
--- a/app/views/workbenches/show.html.slim
+++ b/app/views/workbenches/show.html.slim
@@ -22,18 +22,47 @@
- if @wbench_refs.any?
.row
.col-lg-12
- = table_builder @wbench_refs,
- { :name => 'name',
- :status => Proc.new {|w| w.archived? ? ("<div class='td-block'><span class='fa fa-archive'></span><span>Conservé</span></div>").html_safe : ("<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>En préparation</span></div>").html_safe},
- :organisation => Proc.new {|w| w.organisation.name},
- :validity_period => Proc.new {|w| w.validity_period.nil? ? '-' : t('validity_range', debut: l(w.try(:validity_period).try(:begin), format: :short), end: l(w.try(:validity_period).try(:end), format: :short))},
- :lines => Proc.new {|w| w.lines.count},
- :created_at => Proc.new {|w| l(w.created_at, format: :short)},
- :updated_at => Proc.new {|w| l(w.updated_at, format: :short)},
- :published_at => ''},
- [:show, :edit, :archive, :unarchive, :delete],
- [:delete],
- 'table has-filter has-search'
+ .select_table
+ = table_builder_2 @wbench_refs,
+ [ \
+ TableBuilderHelper::Column.new( \
+ key: :name, \
+ attribute: 'name' \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :status, \
+ attribute: Proc.new {|w| w.archived? ? ("<div class='td-block'><span class='fa fa-archive'></span><span>Conservé</span></div>").html_safe : ("<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>En préparation</span></div>").html_safe} \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :organisation, \
+ attribute: Proc.new {|w| w.organisation.name} \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :validity_period, \
+ attribute: Proc.new {|w| w.validity_period.nil? ? '-' : t('validity_range', debut: l(w.try(:validity_period).try(:begin), format: :short), end: l(w.try(:validity_period).try(:end), format: :short))} \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :lines, \
+ attribute: Proc.new {|w| w.lines.count} \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :created_at, \
+ attribute: Proc.new {|w| l(w.created_at, format: :short)} \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :updated_at, \
+ attribute: Proc.new {|w| l(w.updated_at, format: :short)} \
+ ), \
+ TableBuilderHelper::Column.new( \
+ key: :published_at, \
+ attribute: '' \
+ ) \
+ ],
+ selectable: true,
+ links: [:show, :edit],
+ cls: 'table has-filter has-search'
+
+ = multiple_selection_toolbox([:delete])
= new_pagination @wbench_refs, 'pull-right'
diff --git a/config/locales/journey_patterns.en.yml b/config/locales/journey_patterns.en.yml
index d62d79e58..80adc2337 100644
--- a/config/locales/journey_patterns.en.yml
+++ b/config/locales/journey_patterns.en.yml
@@ -24,6 +24,12 @@ en:
form:
warning: "Be careful, selection is also applied to the %{count} vehicle journeys associated to this journey pattern"
activerecord:
+ errors:
+ models:
+ journey_pattern:
+ attributes:
+ stop_points:
+ minimum: 'Must at least have two stop points'
models:
journey_pattern:
zero: "journey pattern"
diff --git a/config/locales/journey_patterns.fr.yml b/config/locales/journey_patterns.fr.yml
index 39005e464..0dceb2f43 100644
--- a/config/locales/journey_patterns.fr.yml
+++ b/config/locales/journey_patterns.fr.yml
@@ -24,6 +24,12 @@ fr:
form:
warning: "Attention, la sélection s'applique aussi aux %{count} courses de la mission"
activerecord:
+ errors:
+ models:
+ journey_pattern:
+ attributes:
+ stop_points:
+ minimum: 'Une mission doit avoir au minimum deux arrêts'
models:
journey_pattern:
zero: "mission"
diff --git a/lib/html_element.rb b/lib/html_element.rb
new file mode 100644
index 000000000..469fd7565
--- /dev/null
+++ b/lib/html_element.rb
@@ -0,0 +1,15 @@
+class HTMLElement
+ def initialize(tag_name, content = nil, options = nil)
+ @tag_name = tag_name
+ @content = content
+ @options = options
+ end
+
+ def to_html(options = {})
+ ApplicationController.helpers.content_tag(
+ @tag_name,
+ @content,
+ @options.merge(options)
+ )
+ end
+end
diff --git a/lib/link.rb b/lib/link.rb
new file mode 100644
index 000000000..7683a808f
--- /dev/null
+++ b/lib/link.rb
@@ -0,0 +1,10 @@
+class Link
+ attr_reader :content, :href, :method, :data
+
+ def initialize(content: nil, href:, method: nil, data: nil)
+ @content = content
+ @href = href
+ @method = method
+ @data = data
+ end
+end
diff --git a/spec/factories/chouette_2_factories.rb b/spec/factories/chouette_2_factories.rb
index e8eba13e6..a8e80702d 100644
--- a/spec/factories/chouette_2_factories.rb
+++ b/spec/factories/chouette_2_factories.rb
@@ -1,3 +1,4 @@
+# TODO: Move these factories into their own files so all factory definitions are consistent
FactoryGirl.define do
factory :organisation do
diff --git a/spec/features/referentials_permissions_spec.rb b/spec/features/referentials_permissions_spec.rb
index 0216eeeb0..c37dff5b9 100644
--- a/spec/features/referentials_permissions_spec.rb
+++ b/spec/features/referentials_permissions_spec.rb
@@ -31,7 +31,7 @@ describe "Referentials", :type => :feature do
end
it 'shows the delete button' do
expected_href = referential_path(referential)
- expect( page ).to have_css(%{a[href=#{expected_href.inspect}] span}, text: destroy_link_text)
+ expect( page ).to have_css(%{a[href=#{expected_href.inspect}]}, text: destroy_link_text)
end
end
diff --git a/spec/helpers/table_builder_helper/column_spec.rb b/spec/helpers/table_builder_helper/column_spec.rb
new file mode 100644
index 000000000..0f27703b2
--- /dev/null
+++ b/spec/helpers/table_builder_helper/column_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe TableBuilderHelper::Column do
+ describe "#header_label" do
+ it "returns the column @name if present" do
+ expect(
+ TableBuilderHelper::Column.new(
+ name: 'ID Codif',
+ attribute: nil
+ ).header_label
+ ).to eq('ID Codif')
+ end
+
+ it "returns the I18n translation of @key if @name not present" do
+ expect(
+ TableBuilderHelper::Column.new(
+ key: :phone,
+ attribute: 'phone'
+ ).header_label(Chouette::Company)
+ ).to eq('Numéro de téléphone')
+ end
+ end
+end
diff --git a/spec/helpers/table_builder_helper/custom_links_spec.rb b/spec/helpers/table_builder_helper/custom_links_spec.rb
new file mode 100644
index 000000000..b64e97527
--- /dev/null
+++ b/spec/helpers/table_builder_helper/custom_links_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe TableBuilderHelper::CustomLinks do
+ describe "#actions_after_policy_check" do
+ it "includes :show" do
+ referential = build_stubbed(:referential)
+ user_context = UserContext.new(
+ build_stubbed(
+ :user,
+ organisation: referential.organisation,
+ permissions: [
+ 'boiv:read-offer'
+ ]
+ ),
+ referential: referential
+ )
+
+ expect(
+ TableBuilderHelper::CustomLinks.new(
+ referential,
+ user_context,
+ [:show]
+ ).actions_after_policy_check
+ ).to eq([:show])
+ end
+ end
+end
diff --git a/spec/helpers/table_builder_helper_spec.rb b/spec/helpers/table_builder_helper_spec.rb
new file mode 100644
index 000000000..32a6a3bfd
--- /dev/null
+++ b/spec/helpers/table_builder_helper_spec.rb
@@ -0,0 +1,372 @@
+require 'spec_helper'
+require 'htmlbeautifier'
+
+module TableBuilderHelper
+ include Pundit
+end
+
+describe TableBuilderHelper, type: :helper do
+ describe "#table_builder_2" do
+ it "builds a table" do
+ referential = build_stubbed(:referential)
+ workbench = referential.workbench
+
+ user_context = UserContext.new(
+ build_stubbed(
+ :user,
+ organisation: referential.organisation,
+ permissions: [
+ 'referentials.create',
+ 'referentials.edit',
+ 'referentials.destroy'
+ ]
+ ),
+ referential: referential
+ )
+ allow(helper).to receive(:current_user).and_return(user_context)
+
+ referentials = [referential]
+
+ allow(referentials).to receive(:model).and_return(Referential)
+
+ allow(helper).to receive(:params).and_return({
+ controller: 'workbenches',
+ action: 'show',
+ id: referentials[0].workbench.id
+ })
+
+ referentials = ModelDecorator.decorate(
+ referentials,
+ with: ReferentialDecorator
+ )
+
+ expected = <<-HTML
+<table class="table has-filter has-search">
+ <thead>
+ <tr>
+ <th>
+ <div class="checkbox"><input type="checkbox" name="0" id="0" value="all" /><label for="0"></label></div>
+ </th>
+ <th><a href="/workbenches/#{workbench.id}?direction=desc&amp;sort=name">Nom<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/workbenches/#{workbench.id}?direction=desc&amp;sort=status">Etat<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/workbenches/#{workbench.id}?direction=desc&amp;sort=organisation">Organisation<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/workbenches/#{workbench.id}?direction=desc&amp;sort=validity_period">Période de validité englobante<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/workbenches/#{workbench.id}?direction=desc&amp;sort=lines">Lignes<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/workbenches/#{workbench.id}?direction=desc&amp;sort=created_at">Créé le<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/workbenches/#{workbench.id}?direction=desc&amp;sort=updated_at">Edité le<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/workbenches/#{workbench.id}?direction=desc&amp;sort=published_at">Intégré le<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <div class="checkbox"><input type="checkbox" name="#{referential.id}" id="#{referential.id}" value="#{referential.id}" /><label for="#{referential.id}"></label></div>
+ </td>
+ <td title="Voir"><a href="/referentials/#{referential.id}">#{referential.name}</a></td>
+ <td>
+ <div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>En préparation</span></div>
+ </td>
+ <td>#{referential.organisation.name}</td>
+ <td>-</td>
+ <td>#{referential.lines.count}</td>
+ <td>#{I18n.localize(referential.created_at, format: :short)}</td>
+ <td>#{I18n.localize(referential.updated_at, format: :short)}</td>
+ <td></td>
+ <td class="actions">
+ <div class="btn-group">
+ <div class="btn dropdown-toggle" data-toggle="dropdown"><span class="fa fa-cog"></span></div>
+ <ul class="dropdown-menu">
+ <li><a href="/referentials/#{referential.id}">Consulter</a></li>
+ <li><a href="/referentials/#{referential.id}/edit">Editer</a></li>
+ <li><a href="/referentials/#{referential.id}/time_tables">Calendriers</a></li>
+ <li><a href="/referentials/new?from=#{referential.id}">Dupliquer</a></li>
+ <li><a rel="nofollow" data-method="put" href="/referentials/#{referential.id}/archive">Conserver</a></li>
+ <li class="delete-action"><a data-confirm="Etes vous sûr de vouloir supprimer ce jeu de données ?" rel="nofollow" data-method="delete" href="/referentials/#{referential.id}"><span class="fa fa-trash"></span>Supprimer</a></li>
+ </ul>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+ HTML
+
+ html_str = helper.table_builder_2(
+ referentials,
+ [
+ TableBuilderHelper::Column.new(
+ key: :name,
+ attribute: 'name'
+ ),
+ TableBuilderHelper::Column.new(
+ key: :status,
+ attribute: Proc.new do |w|
+ if w.archived?
+ ("<div class='td-block'><span class='fa fa-archive'></span><span>Conservé</span></div>").html_safe
+ else
+ ("<div class='td-block'><span class='sb sb-lg sb-preparing'></span><span>En préparation</span></div>").html_safe
+ end
+ end
+ ),
+ TableBuilderHelper::Column.new(
+ key: :organisation,
+ attribute: Proc.new {|w| w.organisation.name}
+ ),
+ TableBuilderHelper::Column.new(
+ key: :validity_period,
+ attribute: Proc.new do |w|
+ if w.validity_period.nil?
+ '-'
+ else
+ t(
+ 'validity_range',
+ debut: l(w.try(:validity_period).try(:begin), format: :short),
+ end: l(w.try(:validity_period).try(:end), format: :short)
+ )
+ end
+ end
+ ),
+ TableBuilderHelper::Column.new(
+ key: :lines,
+ attribute: Proc.new {|w| w.lines.count}
+ ),
+ TableBuilderHelper::Column.new(
+ key: :created_at,
+ attribute: Proc.new {|w| l(w.created_at, format: :short)}
+ ),
+ TableBuilderHelper::Column.new(
+ key: :updated_at,
+ attribute: Proc.new {|w| l(w.updated_at, format: :short)}
+ ),
+ TableBuilderHelper::Column.new(
+ key: :published_at,
+ attribute: ''
+ )
+ ],
+ selectable: true,
+ links: [:show, :edit],
+ cls: 'table has-filter has-search'
+ )
+
+ beautified_html = HtmlBeautifier.beautify(html_str, indent: ' ')
+
+ expect(beautified_html).to eq(expected.chomp)
+ end
+
+ it "can set a column as non-sortable" do
+ company = build_stubbed(:company)
+ line_referential = build_stubbed(
+ :line_referential,
+ companies: [company]
+ )
+ referential = build_stubbed(
+ :referential,
+ line_referential: line_referential
+ )
+
+ user_context = UserContext.new(
+ build_stubbed(
+ :user,
+ organisation: referential.organisation,
+ permissions: [
+ 'referentials.create',
+ 'referentials.edit',
+ 'referentials.destroy'
+ ]
+ ),
+ referential: referential
+ )
+ allow(helper).to receive(:current_user).and_return(user_context)
+ allow(TableBuilderHelper::URL).to receive(:current_referential)
+ .and_return(referential)
+
+ companies = [company]
+
+ allow(companies).to receive(:model).and_return(Chouette::Company)
+
+ allow(helper).to receive(:params).and_return({
+ controller: 'referential_companies',
+ action: 'index',
+ referential_id: referential.id
+ })
+
+ companies = ModelDecorator.decorate(
+ companies,
+ with: CompanyDecorator
+ )
+
+ expected = <<-HTML
+<table class="table has-search">
+ <thead>
+ <tr>
+ <th>ID Codif</th>
+ <th><a href="/referentials/#{referential.id}/companies?direction=desc&amp;sort=name">Nom<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/referentials/#{referential.id}/companies?direction=desc&amp;sort=phone">Numéro de téléphone<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/referentials/#{referential.id}/companies?direction=desc&amp;sort=email">Email<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th><a href="/referentials/#{referential.id}/companies?direction=desc&amp;sort=url">Page web associée<span class="orderers"><span class="fa fa-sort-asc active"></span><span class="fa fa-sort-desc "></span></span></a></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>#{company.objectid.local_id}</td>
+ <td title="Voir"><a href="/referentials/#{referential.id}/companies/#{company.id}">#{company.name}</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="actions">
+ <div class="btn-group">
+ <div class="btn dropdown-toggle" data-toggle="dropdown"><span class="fa fa-cog"></span></div>
+ <ul class="dropdown-menu">
+ <li><a href="/referentials/#{referential.id}/companies/#{company.id}">Consulter</a></li>
+ </ul>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+ HTML
+
+ html_str = helper.table_builder_2(
+ companies,
+ [
+ TableBuilderHelper::Column.new(
+ name: 'ID Codif',
+ attribute: Proc.new { |n| n.try(:objectid).try(:local_id) },
+ sortable: false
+ ),
+ TableBuilderHelper::Column.new(
+ key: :name,
+ attribute: 'name'
+ ),
+ TableBuilderHelper::Column.new(
+ key: :phone,
+ attribute: 'phone'
+ ),
+ TableBuilderHelper::Column.new(
+ key: :email,
+ attribute: 'email'
+ ),
+ TableBuilderHelper::Column.new(
+ key: :url,
+ attribute: 'url'
+ ),
+ ],
+ links: [:show, :edit, :delete],
+ cls: 'table has-search'
+ )
+
+ beautified_html = HtmlBeautifier.beautify(html_str, indent: ' ')
+
+ expect(beautified_html).to eq(expected.chomp)
+ end
+
+ it "can set all columns as non-sortable" do
+ company = build_stubbed(:company)
+ line_referential = build_stubbed(
+ :line_referential,
+ companies: [company]
+ )
+ referential = build_stubbed(
+ :referential,
+ line_referential: line_referential
+ )
+
+ user_context = UserContext.new(
+ build_stubbed(
+ :user,
+ organisation: referential.organisation,
+ permissions: [
+ 'referentials.create',
+ 'referentials.edit',
+ 'referentials.destroy'
+ ]
+ ),
+ referential: referential
+ )
+ allow(helper).to receive(:current_user).and_return(user_context)
+ allow(TableBuilderHelper::URL).to receive(:current_referential)
+ .and_return(referential)
+
+ companies = [company]
+
+ allow(companies).to receive(:model).and_return(Chouette::Company)
+
+ allow(helper).to receive(:params).and_return({
+ controller: 'referential_companies',
+ action: 'index',
+ referential_id: referential.id
+ })
+
+ companies = ModelDecorator.decorate(
+ companies,
+ with: CompanyDecorator
+ )
+
+ expected = <<-HTML
+<table class="table has-search">
+ <thead>
+ <tr>
+ <th>ID Codif</th>
+ <th>Nom</th>
+ <th>Numéro de téléphone</th>
+ <th>Email</th>
+ <th>Page web associée</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>#{company.objectid.local_id}</td>
+ <td title="Voir"><a href="/referentials/#{referential.id}/companies/#{company.id}">#{company.name}</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td class="actions">
+ <div class="btn-group">
+ <div class="btn dropdown-toggle" data-toggle="dropdown"><span class="fa fa-cog"></span></div>
+ <ul class="dropdown-menu">
+ <li><a href="/referentials/#{referential.id}/companies/#{company.id}">Consulter</a></li>
+ </ul>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+ HTML
+
+ html_str = helper.table_builder_2(
+ companies,
+ [
+ TableBuilderHelper::Column.new(
+ name: 'ID Codif',
+ attribute: Proc.new { |n| n.try(:objectid).try(:local_id) }
+ ),
+ TableBuilderHelper::Column.new(
+ key: :name,
+ attribute: 'name'
+ ),
+ TableBuilderHelper::Column.new(
+ key: :phone,
+ attribute: 'phone'
+ ),
+ TableBuilderHelper::Column.new(
+ key: :email,
+ attribute: 'email'
+ ),
+ TableBuilderHelper::Column.new(
+ key: :url,
+ attribute: 'url'
+ ),
+ ],
+ sortable: false,
+ links: [:show, :edit, :delete],
+ cls: 'table has-search'
+ )
+
+ beautified_html = HtmlBeautifier.beautify(html_str, indent: ' ')
+
+ expect(beautified_html).to eq(expected.chomp)
+ end
+ end
+end
diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb
index f7006efc7..aaf9a694f 100644
--- a/spec/models/chouette/journey_pattern_spec.rb
+++ b/spec/models/chouette/journey_pattern_spec.rb
@@ -18,6 +18,7 @@ describe Chouette::JourneyPattern, :type => :model do
journey_pattern.stop_points.delete(sp)
end
expect(journey_pattern).to_not be_valid
+ expect(journey_pattern.errors).to have_key(:stop_points)
end
end
diff --git a/spec/support/pundit/policies.rb b/spec/support/pundit/policies.rb
index e18309226..56433b2ee 100644
--- a/spec/support/pundit/policies.rb
+++ b/spec/support/pundit/policies.rb
@@ -9,7 +9,7 @@ module Support
end
def create_user_context(user:, referential:)
- OpenStruct.new(user: user, context: {referential: referential})
+ UserContext.new(user, referential: referential)
end
def add_permissions(*permissions, for_user:)
diff --git a/spec/views/lines/show.html.erb_spec.rb b/spec/views/lines/show.html.erb_spec.rb
index 3a9efa0ce..7bc120f1a 100644
--- a/spec/views/lines/show.html.erb_spec.rb
+++ b/spec/views/lines/show.html.erb_spec.rb
@@ -3,7 +3,13 @@ require 'spec_helper'
describe "/lines/show", :type => :view do
assign_referential
- let!(:line) { assign :line, create(:line) }
+ let!(:line) do
+ line = create(:line)
+ assign :line, line.decorate(context: {
+ line_referential: line.line_referential,
+ current_organisation: referential.organisation
+ })
+ end
let!(:line_referential) { assign :line_referential, line.line_referential }
let!(:routes) { assign :routes, Array.new(2) { create(:route, :line => line) }.paginate }
let!(:map) { assign(:map, double(:to_html => '<div id="map"/>'.html_safe)) }
diff --git a/spec/views/time_tables/show.html.erb_spec.rb b/spec/views/time_tables/show.html.erb_spec.rb
index f429f9dec..edfb3f3cc 100644
--- a/spec/views/time_tables/show.html.erb_spec.rb
+++ b/spec/views/time_tables/show.html.erb_spec.rb
@@ -3,7 +3,14 @@ require 'spec_helper'
describe "/time_tables/show", :type => :view do
assign_referential
- let!(:time_table) { assign(:time_table, create(:time_table)) }
+ let!(:time_table) do
+ assign(
+ :time_table,
+ create(:time_table).decorate(context: {
+ referential: referential
+ })
+ )
+ end
let!(:year) { assign(:year, Date.today.cwyear) }
let!(:time_table_combination) {assign(:time_table_combination, TimeTableCombination.new)}