Patch by Marius BALTEANU. git-svn-id: http://svn.redmine.org/redmine/trunk@18765 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/4.1.0
helper :issues | helper :issues | ||||
helper :queries | helper :queries | ||||
include QueriesHelper | include QueriesHelper | ||||
helper :projects_queries | |||||
include ProjectsQueriesHelper | |||||
helper :repositories | helper :repositories | ||||
helper :members | helper :members | ||||
helper :trackers | helper :trackers | ||||
respond_to do |format| | respond_to do |format| | ||||
format.html { | format.html { | ||||
@projects = scope.to_a | |||||
@entry_count = scope.count | |||||
@entry_pages = Paginator.new @entry_count, per_page_option, params['page'] | |||||
@entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a | |||||
} | } | ||||
format.api { | format.api { | ||||
@offset, @limit = api_offset_and_limit | @offset, @limit = api_offset_and_limit | ||||
projects = scope.reorder(:created_on => :desc).limit(Setting.feeds_limit.to_i).to_a | projects = scope.reorder(:created_on => :desc).limit(Setting.feeds_limit.to_i).to_a | ||||
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}") | render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}") | ||||
} | } | ||||
format.csv { | |||||
# Export all entries | |||||
@entries = scope.to_a | |||||
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'projects.csv') | |||||
} | |||||
end | end | ||||
end | end | ||||
url = bookmark_project_url(project) | url = bookmark_project_url(project) | ||||
link_to text, url, remote: true, method: method, class: css | link_to text, url, remote: true, method: method, class: css | ||||
end | end | ||||
def grouped_project_list(projects, query, &block) | |||||
ancestors = [] | |||||
grouped_query_results(projects, query) do |project, group_name, group_count, group_totals| | |||||
ancestors.pop while ancestors.any? && !project.is_descendant_of?(ancestors.last) | |||||
yield project, ancestors.size, group_name, group_count, group_totals | |||||
ancestors << project unless project.leaf? | |||||
end | |||||
end | |||||
end | end |
# frozen_string_literal: true | |||||
# Redmine - project management software | |||||
# Copyright (C) 2006-2017 Jean-Philippe Lang | |||||
# | |||||
# This program is free software; you can redistribute it and/or | |||||
# modify it under the terms of the GNU General Public License | |||||
# as published by the Free Software Foundation; either version 2 | |||||
# of the License, or (at your option) any later version. | |||||
# | |||||
# This program is distributed in the hope that it will be useful, | |||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
# GNU General Public License for more details. | |||||
# | |||||
# You should have received a copy of the GNU General Public License | |||||
# along with this program; if not, write to the Free Software | |||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
module ProjectsQueriesHelper | |||||
include ApplicationHelper | |||||
def column_value(column, item, value) | |||||
if item.is_a?(Project) | |||||
case column.name | |||||
when :name | |||||
link_to_project(item) + (content_tag('span', '', :class => 'icon icon-user my-project', :title => l(:label_my_projects)) if User.current.member_of?(item)) | |||||
when :short_description | |||||
item.description? ? content_tag('div', textilizable(item, :short_description), :class => "wiki") : '' | |||||
when :homepage | |||||
item.homepage? ? content_tag('div', textilizable(item, :homepage), :class => "wiki") : '' | |||||
when :status | |||||
get_project_status_label[column.value_object(item)] | |||||
when :parent_id | |||||
link_to_project(item.parent) unless item.parent.nil? | |||||
else | |||||
super | |||||
end | |||||
end | |||||
end | |||||
def csv_content(column, item) | |||||
if item.is_a?(Project) | |||||
case column.name | |||||
when :status | |||||
get_project_status_label[column.value_object(item)] | |||||
when :parent_id | |||||
return item.parent.name unless item.parent.nil? | |||||
end | |||||
end | |||||
super | |||||
end | |||||
private | |||||
def get_project_status_label | |||||
{ | |||||
Project::STATUS_ACTIVE => l(:project_status_active), | |||||
Project::STATUS_CLOSED => l(:project_status_closed) | |||||
} | |||||
end | |||||
end |
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} | render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} | ||||
end | end | ||||
def available_display_types_tags(query) | |||||
available_display_types = [] | |||||
query.available_display_types.each do |t| | |||||
available_display_types << [l(:"label_display_type_#{t}"), t] | |||||
end | |||||
select_tag('display_type', options_for_select(available_display_types, @query.display_type), :id => 'display_type') | |||||
end | |||||
def grouped_query_results(items, query, &block) | def grouped_query_results(items, query, &block) | ||||
result_count_by_group = query.result_count_by_group | result_count_by_group = query.result_count_by_group | ||||
previous_group, first = false, true | previous_group, first = false, true |
self.queried_class = Project | self.queried_class = Project | ||||
self.view_permission = :search_project | self.view_permission = :search_project | ||||
self.available_columns = [] | |||||
self.available_columns = [ | |||||
QueryColumn.new(:name, :sortable => "#{Project.table_name}.name"), | |||||
QueryColumn.new(:status, :sortable => "#{Project.table_name}.status"), | |||||
QueryColumn.new(:short_description, :sortable => "#{Project.table_name}.description", :caption => :field_description), | |||||
QueryColumn.new(:homepage, :sortable => "#{Project.table_name}.homepage"), | |||||
QueryColumn.new(:identifier, :sortable => "#{Project.table_name}.identifier"), | |||||
QueryColumn.new(:parent_id, :sortable => "#{Project.table_name}.lft ASC", :default_order => 'desc', :caption => :field_parent), | |||||
QueryColumn.new(:is_public, :sortable => "#{Project.table_name}.is_public", :groupable => true), | |||||
QueryColumn.new(:created_on, :sortable => "#{Project.table_name}.created_on", :default_order => 'desc') | |||||
] | |||||
def initialize(attributes=nil, *args) | def initialize(attributes=nil, *args) | ||||
super attributes | super attributes | ||||
end | end | ||||
def available_columns | def available_columns | ||||
[] | |||||
return @available_columns if @available_columns | |||||
@available_columns = self.class.available_columns.dup | |||||
@available_columns += ProjectCustomField.visible. | |||||
map {|cf| QueryAssociationCustomFieldColumn.new(:project, cf) } | |||||
@available_columns | |||||
end | |||||
def available_display_types | |||||
['board', 'list'] | |||||
end | |||||
def default_columns_names | |||||
@default_columns_names ||= [:name, :identifier, :short_description] | |||||
end | |||||
def default_sort_criteria | |||||
[[]] | |||||
end | end | ||||
def base_scope | def base_scope |
self.column_names = params[:c] || query_params[:column_names] || self.column_names | self.column_names = params[:c] || query_params[:column_names] || self.column_names | ||||
self.totalable_names = params[:t] || query_params[:totalable_names] || self.totalable_names | self.totalable_names = params[:t] || query_params[:totalable_names] || self.totalable_names | ||||
self.sort_criteria = params[:sort] || query_params[:sort_criteria] || self.sort_criteria | self.sort_criteria = params[:sort] || query_params[:sort_criteria] || self.sort_criteria | ||||
self.display_type = params[:display_type] || query_params[:display_type] || self.display_type | |||||
self | self | ||||
end | end | ||||
end | end | ||||
end | end | ||||
def display_type | |||||
options[:display_type] || self.available_display_types.first | |||||
end | |||||
def display_type=(type) | |||||
unless type || self.available_display_types.include?(type) | |||||
type = self.available_display_types.first | |||||
end | |||||
options[:display_type] = type | |||||
end | |||||
def available_display_types | |||||
['list'] | |||||
end | |||||
private | private | ||||
def grouped_query(&block) | def grouped_query(&block) |
<div id="projects-index"> | |||||
<%= render_project_hierarchy(@entries) %> | |||||
</div> |
<div class="autoscroll"> | |||||
<table class="list projects odd-even <%= @query.css_classes %>"> | |||||
<thead> | |||||
<tr> | |||||
<% @query.inline_columns.each do |column| %> | |||||
<%= column_header(@query, column) %> | |||||
<% end %> | |||||
</tr> | |||||
</thead> | |||||
<tbody> | |||||
<% grouped_project_list(entries, @query) do |entry, level, group_name, group_count, group_totals| -%> | |||||
<% if group_name %> | |||||
<% reset_cycle %> | |||||
<tr class="group open"> | |||||
<td colspan="<%= @query.inline_columns.size %>"> | |||||
<span class="expander" onclick="toggleRowGroup(this);"> </span> | |||||
<span class="name"><%= group_name %></span> | |||||
<% if group_count %> | |||||
<span class="count"><%= group_count %></span> | |||||
<% end %> | |||||
<span class="totals"><%= group_totals %></span> | |||||
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", | |||||
"toggleAllRowGroups(this)", :class => 'toggle-all') %> | |||||
</td> | |||||
</tr> | |||||
<% end %> | |||||
<tr id="project-<%= entry.id %>" class="<%= cycle('odd', 'even') %> <%= entry.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> | |||||
<% @query.inline_columns.each do |column| %> | |||||
<%= content_tag('td', column_content(column, entry), :class => column.css_classes) %> | |||||
<% end %> | |||||
</tr> | |||||
<% end -%> | |||||
</tbody> | |||||
</table> | |||||
</div> |
<% end %> | <% end %> | ||||
<% if @query.valid? %> | <% if @query.valid? %> | ||||
<% if @projects.empty? %> | |||||
<% if @entries.empty? %> | |||||
<p class="nodata"><%= l(:label_no_data) %></p> | <p class="nodata"><%= l(:label_no_data) %></p> | ||||
<% else %> | <% else %> | ||||
<div id="projects-index"> | |||||
<%= render_project_hierarchy(@projects) %> | |||||
</div> | |||||
<%= render :partial => @query.display_type, :locals => { :entries => @entries }%> | |||||
<span class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></span> | |||||
<% end %> | <% end %> | ||||
<% end %> | <% end %> | ||||
<% end %> | <% end %> | ||||
<fieldset id="options"><legend><%= l(:label_options) %></legend> | <fieldset id="options"><legend><%= l(:label_options) %></legend> | ||||
<p><label for="query_default_columns"><%=l(:label_default_columns)%></label> | |||||
<% if @query.available_display_types.size > 1 %> | |||||
<p><label for='display_type'><%= l(:label_display_type) %></label> | |||||
<%= available_display_types_tags(@query) %> | |||||
</p> | |||||
<% end %> | |||||
<p id ="default_columns"><label for="query_default_columns"><%=l(:label_default_columns)%></label> | |||||
<%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', | <%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', | ||||
:data => {:disables => "#columns, .block_columns input"} %></p> | :data => {:disables => "#columns, .block_columns input"} %></p> | ||||
<% unless params[:gantt] %> | <% unless params[:gantt] %> | ||||
<p><label for="query_group_by"><%= l(:field_group_by) %></label> | |||||
<p id="group_by"><label id="group_by" for="query_group_by"><%= l(:field_group_by) %></label> | |||||
<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> | <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> | ||||
<% unless @query.available_block_columns.empty? %> | <% unless @query.available_block_columns.empty? %> | ||||
$("input.disable-unless-private").attr('disabled', !private_checked); | $("input.disable-unless-private").attr('disabled', !private_checked); | ||||
}).trigger('change'); | }).trigger('change'); | ||||
}); | }); | ||||
$(function ($) { | |||||
$('#display_type').change(function (e) { | |||||
var option = $(e.target).val() | |||||
if (option == 'board') { | |||||
$('fieldset#columns, fieldset#sort, p#default_columns, p#group_by').hide(); | |||||
} else { | |||||
$('fieldset#columns, fieldset#sort, p#default_columns, p#group_by').show(); | |||||
} | |||||
}).change() | |||||
}); | |||||
<% end %> | <% end %> |
<% if @query.available_columns.any? %> | <% if @query.available_columns.any? %> | ||||
<fieldset id="options" class="collapsible collapsed"> | <fieldset id="options" class="collapsible collapsed"> | ||||
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend> | <legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend> | ||||
<div style="display: none;"> | |||||
<table> | |||||
<div class="hidden"> | |||||
<% if @query.available_display_types.size > 1 %> | |||||
<div> | |||||
<span class="field"><label for='display_type'><%= l(:label_display_type) %></label></span> | |||||
<%= available_display_types_tags(@query) %> | |||||
</div> | |||||
<% end %> | |||||
<table id="list" class="<%= 'hidden' if (@query.display_type == 'board') %>"> | |||||
<% if @query.available_columns.any? %> | <% if @query.available_columns.any? %> | ||||
<tr> | <tr> | ||||
<td class="field"><%= l(:field_column_names) %></td> | <td class="field"><%= l(:field_column_names) %></td> | ||||
</div> | </div> | ||||
<%= error_messages_for @query %> | <%= error_messages_for @query %> | ||||
<%= javascript_tag do %> | |||||
$(function ($) { | |||||
$('#display_type').change(function (e) { | |||||
var option = $(e.target).val() | |||||
if (option == 'board') { | |||||
$('table#list').hide(); | |||||
} else { | |||||
$('table#list').show(); | |||||
} | |||||
}) | |||||
}); | |||||
<% end %> |
label_password_char_class_lowercase: lowercase letters | label_password_char_class_lowercase: lowercase letters | ||||
label_password_char_class_digits: digits | label_password_char_class_digits: digits | ||||
label_password_char_class_special_chars: special characters | label_password_char_class_special_chars: special characters | ||||
label_display_type: Display results as | |||||
label_display_type_list: List | |||||
label_display_type_board: Board | |||||
button_login: Login | button_login: Login | ||||
button_submit: Submit | button_submit: Submit |
.clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; } | .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; } | ||||
.mobile-show {display: none;} | .mobile-show {display: none;} | ||||
.hidden {display: none;} | |||||
/***** Links *****/ | /***** Links *****/ | ||||
a, a:link, a:visited{ color: #169; text-decoration: none; } | a, a:link, a:visited{ color: #169; text-decoration: none; } | ||||
table.list th, .table-list-header { background-color:#EEEEEE; padding: 4px; white-space:nowrap; font-weight:bold; } | table.list th, .table-list-header { background-color:#EEEEEE; padding: 4px; white-space:nowrap; font-weight:bold; } | ||||
table.list td {text-align:center; vertical-align:middle; padding-right:10px;} | table.list td {text-align:center; vertical-align:middle; padding-right:10px;} | ||||
table.list td.id { width: 2%; text-align: center;} | table.list td.id { width: 2%; text-align: center;} | ||||
table.list td.name, table.list td.description, table.list td.subject, table.list td.comments, table.list td.roles, table.list td.attachments, table.list td.text {text-align: left;} | |||||
table.list td.name, table.list td.description, table.list td.subject, table.list td.comments, table.list td.roles, table.list td.attachments, table.list td.text, table.list td.short_description {text-align: left;} | |||||
table.list td.attachments a {display:block;} | table.list td.attachments a {display:block;} | ||||
table.list td.tick {width:15%} | table.list td.tick {width:15%} | ||||
table.list td.checkbox { width: 15px; padding: 2px 0 0 0; } | table.list td.checkbox { width: 15px; padding: 2px 0 0 0; } | ||||
tr.project.closed, tr.project.archived { color: #aaa; } | tr.project.closed, tr.project.archived { color: #aaa; } | ||||
tr.project.closed a, tr.project.archived a { color: #aaa; } | tr.project.closed a, tr.project.archived a { color: #aaa; } | ||||
tr.project.idnt td.name span {background: url(../images/arrow_right.png) no-repeat 2px 50%; padding-left: 16px;} | |||||
tr.project.idnt-1 td.name {padding-left: 0.5em;} | |||||
tr.project.idnt-2 td.name {padding-left: 2em;} | |||||
tr.project.idnt-3 td.name {padding-left: 3.5em;} | |||||
tr.project.idnt-4 td.name {padding-left: 5em;} | |||||
tr.project.idnt-5 td.name {padding-left: 6.5em;} | |||||
tr.project.idnt-6 td.name {padding-left: 8em;} | |||||
tr.project.idnt-7 td.name {padding-left: 9.5em;} | |||||
tr.project.idnt-8 td.name {padding-left: 11em;} | |||||
tr.project.idnt-9 td.name {padding-left: 12.5em;} | |||||
tr.issue { text-align: center; white-space: nowrap; } | tr.issue { text-align: center; white-space: nowrap; } | ||||
tr.issue td.subject, tr.issue td.category, td.assigned_to, td.last_updated_by, tr.issue td.string, tr.issue td.text, tr.issue td.list, tr.issue td.relations, tr.issue td.parent { white-space: normal; } | tr.issue td.subject, tr.issue td.category, td.assigned_to, td.last_updated_by, tr.issue td.string, tr.issue td.text, tr.issue td.list, tr.issue td.relations, tr.issue td.parent { white-space: normal; } | ||||
tr.issue td.relations { text-align: left; } | tr.issue td.relations { text-align: left; } | ||||
table.issues td.block_column span {font-weight: bold; display: block; margin-bottom: 4px;} | table.issues td.block_column span {font-weight: bold; display: block; margin-bottom: 4px;} | ||||
table.issues td.block_column pre {white-space:normal;} | table.issues td.block_column pre {white-space:normal;} | ||||
tr.issue.idnt td.subject {background: url(../images/arrow_right.png) no-repeat 2px 50%;} | |||||
tr.issue.idnt-1 td.subject {padding-left: 24px; background-position: 8px 50%;} | |||||
tr.issue.idnt-2 td.subject {padding-left: 40px; background-position: 24px 50%;} | |||||
tr.issue.idnt-3 td.subject {padding-left: 56px; background-position: 40px 50%;} | |||||
tr.issue.idnt-4 td.subject {padding-left: 72px; background-position: 56px 50%;} | |||||
tr.issue.idnt-5 td.subject {padding-left: 88px; background-position: 72px 50%;} | |||||
tr.issue.idnt-6 td.subject {padding-left: 104px; background-position: 88px 50%;} | |||||
tr.issue.idnt-7 td.subject {padding-left: 120px; background-position: 104px 50%;} | |||||
tr.issue.idnt-8 td.subject {padding-left: 136px; background-position: 120px 50%;} | |||||
tr.issue.idnt-9 td.subject {padding-left: 152px; background-position: 136px 50%;} | |||||
tr.issue.idnt td.subject, tr.project.idnt td.name {background: url(../images/arrow_right.png) no-repeat 2px 50%;} | |||||
tr.issue.idnt-1 td.subject, tr.project.idnt-1 td.name {padding-left: 24px; background-position: 8px 50%;} | |||||
tr.issue.idnt-2 td.subject, tr.project.idnt-2 td.name {padding-left: 40px; background-position: 24px 50%;} | |||||
tr.issue.idnt-3 td.subject, tr.project.idnt-3 td.name {padding-left: 56px; background-position: 40px 50%;} | |||||
tr.issue.idnt-4 td.subject, tr.project.idnt-4 td.name {padding-left: 72px; background-position: 56px 50%;} | |||||
tr.issue.idnt-5 td.subject, tr.project.idnt-5 td.name {padding-left: 88px; background-position: 72px 50%;} | |||||
tr.issue.idnt-6 td.subject, tr.project.idnt-6 td.name {padding-left: 104px; background-position: 88px 50%;} | |||||
tr.issue.idnt-7 td.subject, tr.project.idnt-7 td.name {padding-left: 120px; background-position: 104px 50%;} | |||||
tr.issue.idnt-8 td.subject, tr.project.idnt-8 td.name {padding-left: 136px; background-position: 120px 50%;} | |||||
tr.issue.idnt-9 td.subject, tr.project.idnt-9 td.name {padding-left: 152px; background-position: 136px 50%;} | |||||
table.issue-report {table-layout:fixed;} | table.issue-report {table-layout:fixed;} | ||||
.issue-report-graph {width: 75%; margin: 2em 0;} | .issue-report-graph {width: 75%; margin: 2em 0;} |
end | end | ||||
end | end | ||||
def test_index_as_list_should_format_column_value | |||||
get :index, :params => { | |||||
:c => ['name', 'status', 'short_description', 'homepage', 'parent_id', 'identifier', 'is_public', 'created_on', 'project.cf_3'], | |||||
:display_type => 'list' | |||||
} | |||||
assert_response :success | |||||
assert_select 'table.projects' do | |||||
assert_select 'tr[id=?]', 'project-1' do | |||||
assert_select 'td.name a[href=?]', '/projects/ecookbook', :text => 'eCookbook' | |||||
assert_select 'td.status', :text => 'active' | |||||
assert_select 'td.short_description', :text => 'Recipes management application' | |||||
assert_select 'td.homepage a.external', :text => 'http://ecookbook.somenet.foo/' | |||||
assert_select 'td.identifier', :text => 'ecookbook' | |||||
assert_select 'td.is_public', :text => 'Yes' | |||||
assert_select 'td.created_on', :text => '07/19/2006 05:13 PM' | |||||
assert_select 'td.project_cf_3.list', :text => 'Stable' | |||||
end | |||||
assert_select 'tr[id=?]', 'project-4' do | |||||
assert_select 'td.parent_id a[href=?]', '/projects/ecookbook', :text => 'eCookbook' | |||||
end | |||||
end | |||||
end | |||||
def test_index_as_list_should_show_my_favourite_projects | |||||
@request.session[:user_id] = 1 | |||||
get :index, :params => { | |||||
:display_type => 'list' | |||||
} | |||||
assert_response :success | |||||
assert_select 'tr[id=?] td.name span[class=?]', 'project-5', 'icon icon-user my-project' | |||||
end | |||||
def test_index_as_list_should_indent_projects | |||||
@request.session[:user_id] = 1 | |||||
get :index, :params => { | |||||
:c => ['name', 'short_description'], | |||||
:sort => 'parent_id:desc,lft:desc', | |||||
:display_type => 'list' | |||||
} | |||||
assert_response :success | |||||
child_level1 = css_select('tr#project-5').map {|e| e.attr('class')}.first.split(' ') | |||||
child_level2 = css_select('tr#project-6').map {|e| e.attr('class')}.first.split(' ') | |||||
assert_include 'idnt', child_level1 | |||||
assert_include 'idnt-1', child_level1 | |||||
assert_include 'idnt', child_level2 | |||||
assert_include 'idnt-2', child_level2 | |||||
end | |||||
def test_autocomplete_js | def test_autocomplete_js | ||||
get :autocomplete, :params => { | get :autocomplete, :params => { | ||||
:format => 'js', | :format => 'js', |
values = query.available_filters['status'][:values] | values = query.available_filters['status'][:values] | ||||
assert_equal ['active', 'closed'], values.map(&:first) | assert_equal ['active', 'closed'], values.map(&:first) | ||||
assert_equal ['1', '5'], values.map(&:second) | assert_equal ['1', '5'], values.map(&:second) | ||||
end | |||||
def test_default_columns | |||||
q = ProjectQuery.new | |||||
assert q.columns.any? | |||||
assert q.inline_columns.any? | |||||
assert q.block_columns.empty? | |||||
end | |||||
def test_available_columns_should_include_project_custom_fields | |||||
query = ProjectQuery.new | |||||
assert_include :"project.cf_3", query.available_columns.map(&:name) | |||||
end | end | ||||
end | end |