aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock2
-rw-r--r--app/controllers/referentials_controller.rb28
-rw-r--r--app/controllers/workbenches_controller.rb4
-rw-r--r--app/decorators/company_decorator.rb31
-rw-r--r--app/decorators/model_decorator.rb3
-rw-r--r--app/decorators/referential_decorator.rb48
-rw-r--r--app/helpers/newapplication_helper.rb12
-rw-r--r--app/helpers/table_builder_helper.rb236
-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.rb24
-rw-r--r--app/views/referential_lines/show.html.slim1
-rw-r--r--app/views/referentials/show.html.slim47
-rw-r--r--app/views/workbenches/_filters.html.slim4
-rw-r--r--app/views/workbenches/show.html.slim51
-rw-r--r--lib/link.rb10
-rw-r--r--spec/factories/chouette_2_factories.rb1
-rw-r--r--spec/factories/workbenches.rb19
-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.rb394
22 files changed, 1036 insertions, 43 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/controllers/referentials_controller.rb b/app/controllers/referentials_controller.rb
index 437444f29..e95670241 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/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/company_decorator.rb b/app/decorators/company_decorator.rb
index 3a0cc16ce..5a2fdd1c0 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,33 @@ class CompanyDecorator < Draper::Decorator
object.lines.count
end
+ def action_links
+ links = []
+
+ if h.policy(Chouette::Company).create?
+ links << Link.new(
+ name: h.t('companies.actions.new'),
+ href: h.new_line_referential_company_path(@line_referential)
+ )
+ end
+
+ if h.policy(object).update?
+ links << Link.new(
+ name: h.t('companies.actions.edit'),
+ href: h.edit_line_referential_company_path(@line_referential, object)
+ )
+ end
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ name: t('companies.actions.destroy'),
+ href: h.line_referential_company_path(@line_referential, object),
+ method: :delete,
+ data: { confirm: t('companies.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..5112f9dd4
--- /dev/null
+++ b/app/decorators/referential_decorator.rb
@@ -0,0 +1,48 @@
+class ReferentialDecorator < Draper::Decorator
+ delegate_all
+
+ def action_links
+ links = [
+ Link.new(
+ name: h.t('time_tables.index.title'),
+ href: h.referential_time_tables_path(object)
+ )
+ ]
+
+ if h.policy(object).clone?
+ links << Link.new(
+ name: h.t('actions.clone'),
+ href: h.new_referential_path(from: object.id)
+ )
+ end
+
+ if h.policy(object).edit?
+ # TODO: Handle buttons in the header and don't show them in the gear menu
+ # button.btn.btn-primary type='button' data-toggle='modal' data-target='#purgeModal' Purger
+
+ if object.archived?
+ links << Link.new(
+ name: h.t('actions.unarchive'),
+ href: h.unarchive_referential_path(object.id),
+ method: :put
+ )
+ else
+ links << Link.new(
+ name: h.t('actions.archive'),
+ href: h.archive_referential_path(object.id),
+ method: :put
+ )
+ end
+ end
+
+ if h.policy(object).destroy?
+ links << Link.new(
+ href: h.referential_path(object),
+ method: :delete,
+ data: { confirm: h.t('referentials.actions.destroy_confirm') }
+ )
+ end
+
+ links
+ end
+end
diff --git a/app/helpers/newapplication_helper.rb b/app/helpers/newapplication_helper.rb
index 3d43e9fc7..27b9cb0a4 100644
--- a/app/helpers/newapplication_helper.rb
+++ b/app/helpers/newapplication_helper.rb
@@ -1,6 +1,7 @@
module NewapplicationHelper
# Table Builder
+ # selectable means actions-for-selection
def table_builder collection, columns, actions, selectable = [], cls = nil
return unless collection.present?
@@ -8,6 +9,7 @@ module NewapplicationHelper
content_tag :tr do
hcont = []
+ # Adds checkbox to table header
unless selectable.empty?
cbx = content_tag :div, '', class: 'checkbox' do
check_box_tag('0', 'all').concat(content_tag(:label, '', for: '0'))
@@ -16,12 +18,14 @@ module NewapplicationHelper
end
columns.map do |k, v|
+ # These columns are hard-coded to not be sortable
if ["ID Codif", "Oid", "OiD", "ID Reflex", "Arrêt de départ", "Arrêt d'arrivée", "Période de validité englobante", "Période englobante", "Nombre de courses associées", "Journées d'application", "Arrêts de l'itinéraire", "Arrêts inclus dans l'ITL"].include? k
hcont << content_tag(:th, k)
else
hcont << content_tag(:th, sortable_columns(collection, k))
end
end
+ # Inserts a blank column for the gear menu
hcont << content_tag(:th, '') if actions.any?
hcont.join.html_safe
@@ -34,6 +38,8 @@ module NewapplicationHelper
content_tag :tr do
bcont = []
+ # Adds item checkboxes whose value = the row object's id
+ # Apparently the object id is also used in the HTML id attribute without any prefix
unless selectable.empty?
cbx = content_tag :div, '', class: 'checkbox' do
check_box_tag(item.try(:id), item.try(:id)).concat(content_tag(:label, '', for: item.try(:id)))
@@ -48,9 +54,14 @@ module NewapplicationHelper
else
item.try(attribute)
end
+ # if so this column's contents get transformed into a link to the object
if attribute == 'name' or attribute == 'comment'
lnk = []
+ # #is_a? ? ; or ?
+ # TODO: Ask Jean-Paul: on which pages do we create multiple links?
+ # Do we actually create multiple links with this code?
+ # Answer: this is a polymorphic URL
unless item.class == Calendar or item.class == Referential
if current_referential
lnk << current_referential
@@ -172,6 +183,7 @@ module NewapplicationHelper
pic2 = content_tag :span, '', class: "fa fa-sort-desc #{(direction == 'asc') ? 'active' : ''}"
pics = content_tag :span, pic1 + pic2, class: 'orderers'
+ # This snake cases and downcases the class name. Should use the ActiveSupport method to do this
obj = collection.model.to_s.gsub('Chouette::', '').scan(/[A-Z][a-z]+/).join('_').downcase
(I18n.t("activerecord.attributes.#{obj}.#{key}") + pics).html_safe
diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb
new file mode 100644
index 000000000..4537a0795
--- /dev/null
+++ b/app/helpers/table_builder_helper.rb
@@ -0,0 +1,236 @@
+require 'table_builder_helper/column'
+require 'table_builder_helper/custom_links'
+require 'table_builder_helper/url'
+
+# TODO: Add doc comment about neeeding to make a decorator for your collections
+# TODO: Document global variables this uses
+module TableBuilderHelper
+ # TODO: rename this after migration from `table_builder`
+ def table_builder_2(
+ collection,
+ columns,
+ current_referential: nil,
+
+ # When false, no columns will be sortable
+ sortable: true,
+
+ selectable: false,
+ # selection_actions: [] ## this has been gotten rid of. The element based on this should be created elsewhere
+ links: [], # links: or actions: ? I think 'links' is better since 'actions' evokes Rails controller actions and we want to put `link_to`s here
+ # TODO: get rid of this argument. Going with params instead
+ sort_by: {}, # { column: 'name', direction: 'desc' }
+ cls: '' # can we rename this to "class"?
+ # ^^ rename to html_options = {} at the end of the non-keyword arguments? Hrm, not a fan of combining hash args and keyword args
+# sort column
+# sort direction
+
+# TODO: add `linked_column` or some such attribute that defines which column should be linked and what method to call to get it
+ )
+
+ 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
+ ).map do |link|
+ # TODO: ensure the Delete link is formatted correctly with the spacer,
+ # icon, and label
+ content_tag :li, link_to(
+ link.name,
+ link.href,
+ method: link.method,
+ data: link.data
+ )
+ end.join.html_safe
+
+ # actions.map do |action|
+ # polymorph_url = []
+ #
+ # unless [:show, :delete].include? action
+ # polymorph_url << action
+ # end
+ #
+ # polymorph_url += polymorphic_url_parts(item)
+ #
+ # if action == :delete
+ # if policy(item).present?
+ # if policy(item).destroy?
+ # # TODO: This tag is exactly the same as the one below it
+ # content_tag :li, '', class: 'delete-action' do
+ # link_to(polymorph_url, method: :delete, data: { confirm: 'Etes-vous sûr(e) de vouloir effectuer cette action ?' }) do
+ # txt = t("actions.#{action}")
+ # pic = content_tag :span, '', class: 'fa fa-trash'
+ # pic + txt
+ # end
+ # end
+ # end
+ # else
+ # content_tag :li, '', class: 'delete-action' do
+ # link_to(polymorph_url, method: :delete, data: { confirm: 'Etes-vous sûr(e) de vouloir effectuer cette action ?' }) do
+ # txt = t("actions.#{action}")
+ # pic = content_tag :span, '', class: 'fa fa-trash'
+ # pic + txt
+ # end
+ # end
+ # end
+ #
+ # elsif action == :edit
+ # if policy(item).present?
+ # if policy(item).update?
+ # content_tag :li, link_to(t("actions.#{action}"), polymorph_url)
+ # end
+ # else
+ # content_tag :li, link_to(t("actions.#{action}"), polymorph_url)
+ # end
+ # elsif action == :archive
+ # unless item.archived?
+ # content_tag :li, link_to(t("actions.#{action}"), polymorph_url, method: :put)
+ # end
+ # elsif action == :unarchive
+ # if item.archived?
+ # content_tag :li, link_to(t("actions.#{action}"), polymorph_url, method: :put)
+ # end
+ # else
+ # content_tag :li, link_to(t("actions.#{action}"), polymorph_url)
+ # end
+ # 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
+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..b2fdf2b73
--- /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..f03acac1e
--- /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(
+ name: 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..59099ee99
--- /dev/null
+++ b/app/helpers/table_builder_helper/url.rb
@@ -0,0 +1,24 @@
+module TableBuilderHelper
+ 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/views/referential_lines/show.html.slim b/app/views/referential_lines/show.html.slim
index db99381d3..5fb1bb125 100644
--- a/app/views/referential_lines/show.html.slim
+++ b/app/views/referential_lines/show.html.slim
@@ -7,6 +7,7 @@
/ Below is secundary actions & optional contents
.row
.col-lg-12.text-right.mb-sm
+ / TODO: Make a list of Link objects that can be rendered with link_tos here and in the gear menu
= link_to @line.human_attribute_name(:footnotes), referential_line_footnotes_path(@referential, @line), class: 'btn btn-primary'
= link_to t('routing_constraint_zones.index.title'), referential_line_routing_constraint_zones_path(@referential, @line), class: 'btn btn-primary'
diff --git a/app/views/referentials/show.html.slim b/app/views/referentials/show.html.slim
index 3c1e36302..1bbf350b4 100644
--- a/app/views/referentials/show.html.slim
+++ b/app/views/referentials/show.html.slim
@@ -8,23 +8,38 @@
/ 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'
+ /= 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'
+ / - 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')
+ /= leslinks.map { |l| link_to l.href, l.label }
+ - @referential.action_links.each do |link|
+ - if link.method == :delete
+ = link_to link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary' do
+ span.fa.fa-trash
+ span = t('actions.destroy')
- 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.name,
+ link.href,
+ method: link.method,
+ data: link.data,
+ class: 'btn btn-primary'
/ 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..6dec68f7a 100644
--- a/app/views/workbenches/show.html.slim
+++ b/app/views/workbenches/show.html.slim
@@ -22,18 +22,45 @@
- 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'
+ = 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'
+ / [:delete],
= new_pagination @wbench_refs, 'pull-right'
diff --git a/lib/link.rb b/lib/link.rb
new file mode 100644
index 000000000..c875b85ac
--- /dev/null
+++ b/lib/link.rb
@@ -0,0 +1,10 @@
+class Link
+ attr_reader :name, :href, :method, :data
+
+ def initialize(name: nil, href:, method: nil, data: nil)
+ @name = name
+ @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/factories/workbenches.rb b/spec/factories/workbenches.rb
index f51e7d94c..154482213 100644
--- a/spec/factories/workbenches.rb
+++ b/spec/factories/workbenches.rb
@@ -5,5 +5,24 @@ FactoryGirl.define do
association :organisation, :factory => :organisation
association :line_referential
association :stop_area_referential
+
+ trait :with_referential do
+ # TODO: change all => to :
+ # association :referential,
+ # organisation: { organisation }
+
+ # after(:stub) do |workbench, evaluator|
+ #
+ # end
+
+ referentials do |workbench|
+ [association(
+ :referential,
+ organisation: workbench.organisation,
+ line_referential: workbench.line_referential,
+ stop_area_referential: workbench.stop_area_referential
+ )]
+ end
+ end
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..c006e33c0
--- /dev/null
+++ b/spec/helpers/table_builder_helper_spec.rb
@@ -0,0 +1,394 @@
+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 = create_user_context(
+ # user: build_stubbed(:user),
+ # referential: referential
+ # )
+ user_context = OpenStruct.new(
+ user: build_stubbed(
+ :user,
+ organisation: referential.organisation,
+ permissions: [
+ 'referentials.create',
+ 'referentials.edit',
+ 'referentials.destroy'
+ ]
+ ),
+ context: { 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/1008/time_tables">Calendriers</a></li>
+ <li><a href="/referentials/new?from=1008">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(e) de vouloir effectuer cette action ?" 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
+# TODO: Create a module for the selection box
+# <div class="select_toolbox noselect">
+# <ul>
+# <li class="st_action"><a data-path="/workbenches/1/referentials" data-confirm="Etes-vous sûr(e) de vouloir effectuer cette action ?" title="Supprimer" rel="nofollow" data-method="delete" href="#"><span class="fa fa-trash"></span></a></li>
+# </ul><span class="info-msg"><span>0</span> élément(s) sélectionné(s)</span>
+# </div>
+
+ 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 = OpenStruct.new(
+ user: build_stubbed(
+ :user,
+ organisation: referential.organisation,
+ permissions: [
+ 'referentials.create',
+ 'referentials.edit',
+ 'referentials.destroy'
+ ]
+ ),
+ context: { 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 = OpenStruct.new(
+ user: build_stubbed(
+ :user,
+ organisation: referential.organisation,
+ permissions: [
+ 'referentials.create',
+ 'referentials.edit',
+ 'referentials.destroy'
+ ]
+ ),
+ context: { 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
+
+
+# Replace table builder on workspaces#show page
+# Make the builder work without a `current_referential` so we can actually test it
+# Make a way to define a column as non-sortable. By default, columns will be sortable. Unless sortable==false and no columns should be sortable.
+#
+#
+# TODO:
+# - Finish writing workbench test
+# - Port some code over to the new table builder
+# - Ask Jean-Paul if there's anything he wishes could be changed or improved about the existing table builder
+# - Thing that Jean-Paul didn't like was the link generation