Skip to content

Commit

Permalink
Merge pull request #14843 from opf/feature/51670-allow-columns-to-be-…
Browse files Browse the repository at this point in the history
…changed-and-persisted

Feature/51670 allow columns to be changed and persisted
  • Loading branch information
ulferts authored Mar 5, 2024
2 parents 1a2144f + 92eb6f9 commit cc980fe
Show file tree
Hide file tree
Showing 105 changed files with 1,949 additions and 648 deletions.
39 changes: 39 additions & 0 deletions app/components/projects/configure_view_modal_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<%= render(Primer::Alpha::Dialog.new(title: t(:'queries.configure_view.heading'),
size: :large,
id: MODAL_ID,
# Hack to give the draggable autcompleter (ng-select) bound to the dialog
# enough height to display all options.
# This is necessary as long as ng-select does not support popovers.
style: "min-height: 430px")) do |d| %>
<% d.with_header(variant: :large, mb: 3) %>

<%= render(Primer::Alpha::Dialog::Body.new) do %>
<%= primer_form_with(
url: projects_path,
id: COLUMN_FORM_ID,
method: :get,
data: {
controller: "params-from-query",
'application-target': "dynamic",
'params-from-query-allowed-value': '["filters", "query_id", "page", "per_page", "sortBy"]'
}) do %>
<%= helpers.angular_component_tag 'opce-draggable-autocompleter',
inputs: {
options: helpers.projects_columns_options,
selected: selected_columns,
protected: helpers.protected_projects_columns_options,
name: COLUMN_HTML_NAME,
id: 'columns-select',
inputLabel: I18n.t(:'queries.configure_view.columns.input_label'),
inputPlaceholder: I18n.t(:'queries.configure_view.columns.input_placeholder'),
dragAreaLabel: I18n.t(:'queries.configure_view.columns.drag_area_label'),
appendToComponent: true
}%>
<% end %>
<% end %>

<%= render(Primer::Alpha::Dialog::Footer.new) do %>
<%= render(Primer::ButtonComponent.new(data: { "close-dialog-id": MODAL_ID })) { I18n.t(:button_cancel) } %>
<%= render(Primer::ButtonComponent.new(scheme: :primary, type: :submit, form: COLUMN_FORM_ID)) { I18n.t(:button_apply) } %>
<% end %>
<% end %>
43 changes: 43 additions & 0 deletions app/components/projects/configure_view_modal_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++

class Projects::ConfigureViewModalComponent < ApplicationComponent
MODAL_ID = 'op-project-list-configure-dialog'
COLUMN_FORM_ID = 'op-project-list-configure-columns-form'
COLUMN_HTML_NAME = 'columns'

options :query

def selected_columns
@selected_columns ||= query
.selects
.map { |c| { id: c.attribute, name: c.caption } }
end
end
14 changes: 12 additions & 2 deletions app/components/projects/index_page_header_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
data: {
controller: "params-from-query",
'application-target': "dynamic",
'params-from-query-allowed-value': '["filters"]'
'params-from-query-allowed-value': '["filters", "columns"]'
}
}
) do |item|
Expand All @@ -49,6 +49,13 @@
item.with_leading_visual_icon(icon: 'sign-out')
end

menu.with_item(
label: t(:'queries.configure_view.heading'),
content_arguments: { 'data-show-dialog-id': Projects::ConfigureViewModalComponent::MODAL_ID }
) do |item|
item.with_leading_visual_icon(icon: :gear)
end

if query.persisted?
menu.with_item(
label: t(:button_delete),
Expand Down Expand Up @@ -77,7 +84,7 @@
data: {
controller: "params-from-query",
'application-target': "dynamic",
'params-from-query-allowed-value': '["filters"]'
'params-from-query-allowed-value': '["filters", "columns"]'
},
id: 'project-save-form') do |f|
render(Queries::Projects::Create.new(f))
Expand All @@ -87,11 +94,14 @@
<% end %>

<% if show_state? %>
<%# TODO: move into a component %>
<%= render(Primer::Alpha::Dialog.new(title: t('js.label_export'),
id: EXPORT_MODAL_ID)) do |d|
d.with_header(variant: :large)
d.with_body do
render partial: '/projects/project_export_modal'
end
end %>

<%= render(Projects::ConfigureViewModalComponent.new(query:)) %>
<% end %>
5 changes: 2 additions & 3 deletions app/components/projects/row_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,12 @@ See COPYRIGHT and LICENSE files for more details.
</td>
<% end %>
<td class="buttons">
<% items = helpers.project_more_menu_items(project) %>
<% if items.any? %>
<% if more_menu_items.any? %>
<ul class="project-actions">
<li aria-haspopup="true" title="<%= I18n.t(:label_open_menu) %>" class="drop-down">
<a class="icon icon-show-more-horizontal context-menu--icon" title="<%= t(:label_open_menu) %>" href></a>
<ul style="display:none;" class="menu-drop-down-container">
<% items.each do |item| %>
<% more_menu_items.each do |item| %>
<li>
<%= link_to(*item) %>
</li>
Expand Down
104 changes: 91 additions & 13 deletions app/components/projects/row_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@ def hierarchy
end

def column_value(column)
if column.to_s.start_with? 'cf_'
if custom_field_column?(column)
custom_field_column(column)
else
super
send(column.attribute)
end
end

def custom_field_column(column)
return nil unless user_can_view_project?

cf = custom_field(column)
cf = column.custom_field
custom_value = project.formatted_custom_value_for(cf)

if cf.field_format == 'text' && custom_value.present?
Expand Down Expand Up @@ -159,28 +159,106 @@ def project_css_classes
end

def column_css_class(column)
"#{super} #{additional_css_class(column)}"
end

def custom_field(name)
table.project_custom_fields.fetch(name)
"#{column.attribute} #{additional_css_class(column)}"
end

def additional_css_class(column)
case column
when :name
if column.attribute == :name
"project--hierarchy #{project.archived? ? 'archived' : ''}"
when :status_explanation, :description
elsif [:status_explanation, :description].include?(column.attribute)
"project-long-text-container"
when /\Acf_/
cf = custom_field(column)
elsif custom_field_column?(column)
cf = column.custom_field
formattable = cf.field_format == 'text' ? ' project-long-text-container' : ''
"format-#{cf.field_format}#{formattable}"
end
end

def more_menu_items
@more_menu_items ||= [more_menu_subproject_item,
more_menu_settings_item,
more_menu_activity_item,
more_menu_archive_item,
more_menu_unarchive_item,
more_menu_copy_item,
more_menu_delete_item].compact
end

def more_menu_subproject_item
if User.current.allowed_in_project?(:add_subprojects, project)
[t(:label_subproject_new),
new_project_path(parent_id: project.id),
{ class: 'icon-context icon-add',
title: t(:label_subproject_new) }]
end
end

def more_menu_settings_item
if User.current.allowed_in_project?({ controller: '/projects/settings/general', action: 'show', project_id: project.id },
project)
[t(:label_project_settings),
project_settings_general_path(project),
{ class: 'icon-context icon-settings',
title: t(:label_project_settings) }]
end
end

def more_menu_activity_item
if User.current.allowed_in_project?(:view_project_activity, project)
[
t(:label_project_activity),
project_activity_index_path(project, event_types: ['project_attributes']),
{ class: 'icon-context icon-checkmark',
title: t(:label_project_activity) }
]
end
end

def more_menu_archive_item
if User.current.allowed_in_project?(:archive_project, project) && project.active?
[t(:button_archive),
project_archive_path(project, status: params[:status]),
{ data: { confirm: t('project.archive.are_you_sure', name: project.name) },
method: :post,
class: 'icon-context icon-locked',
title: t(:button_archive) }]
end
end

def more_menu_unarchive_item
if User.current.admin? && project.archived? && (project.parent.nil? || project.parent.active?)
[t(:button_unarchive),
project_archive_path(project, status: params[:status]),
{ method: :delete,
class: 'icon-context icon-unlocked',
title: t(:button_unarchive) }]
end
end

def more_menu_copy_item
if User.current.allowed_in_project?(:copy_projects, project) && !project.archived?
[t(:button_copy),
copy_project_path(project),
{ class: 'icon-context icon-copy',
title: t(:button_copy) }]
end
end

def more_menu_delete_item
if User.current.admin
[t(:button_delete),
confirm_destroy_project_path(project),
{ class: 'icon-context icon-delete',
title: t(:button_delete) }]
end
end

def user_can_view_project?
User.current.allowed_in_project?(:view_project, project)
end

def custom_field_column?(column)
column.is_a?(Queries::Projects::Selects::CustomField)
end
end
end
35 changes: 16 additions & 19 deletions app/components/projects/table_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,22 @@ See COPYRIGHT and LICENSE files for more details.
<div class="generic-table--results-container">
<table class="generic-table" <%= table_id ? "id=\"#{table_id}\"".html_safe : '' %>>
<colgroup>
<% headers.each do |_name, _options| %>
<col <%= "opHighlightCol" unless _name == :hierarchy %> >
<% columns.each do |column| %>
<col <%= "opHighlightCol" unless column.attribute == :hierarchy %> >
<% end %>
<col opHighlightCol>
</colgroup>
<thead class="-sticky">
<thead class="-sticky">
<tr>
<% headers.each do |name, options| %>
<% if name == :hierarchy %>
<% columns.each do |column| %>
<% if column.attribute == :hierarchy %>
<th id="project-table--hierarchy-header">
<div class="generic-table--sort-header-outer generic-table--sort-header-outer_no-highlighting">
<div class="generic-table--sort-header">
<div class="generic-table--sort-header"
data-controller="params-from-query"
data-application-target="dynamic"
data-params-from-query-all-anchors-value="true"
data-params-from-query-allowed-value='["query_id", "per_page", "filters", "columns"]'>
<%= content_tag :a,
helpers.op_icon("icon-hierarchy"),
href: href_only_when_not_sort_lft,
Expand All @@ -52,22 +56,15 @@ See COPYRIGHT and LICENSE files for more details.
</div>
</div>
</th>
<% elsif sortable_column?(name) %>
<%= build_sort_header name,
options.merge(data:
{
controller: "params-from-query",
'application-target': "dynamic",
'params-from-query-allowed-value': '["query_id"]',
'params-from-query-all-anchors-value': "true"
}
) %>
<% elsif sortable_column?(column) %>
<%= build_sort_header column.attribute,
order_options(column) %>
<% else %>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= options[:caption] %>
<%= column.caption %>
</span>
</div>
</div>
Expand Down Expand Up @@ -100,8 +97,8 @@ See COPYRIGHT and LICENSE files for more details.
<div data-controller="params-from-query"
data-application-target="dynamic"
data-params-from-query-all-anchors-value="true"
data-params-from-query-allowed-value='["query_id"]'>
<%= helpers.pagination_links_full model, { blocked_url_params: [:query_id] } %>
data-params-from-query-allowed-value='["query_id", "columns"]'>
<%= helpers.pagination_links_full model, { blocked_url_params: [:query_id, :columns] } %>
</div>
<% end %>
</div>
Loading

0 comments on commit cc980fe

Please sign in to comment.