CSV export of project memberships (#37480).

Patch by Mizuki ISHIKAWA (user:ishikawa999).


git-svn-id: https://svn.redmine.org/redmine/trunk@24487 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Go MAEDA
2026-03-11 10:19:50 +00:00
parent efb1d432f3
commit 86754ba098
6 changed files with 78 additions and 6 deletions

View File

@@ -28,17 +28,24 @@ class MembersController < ApplicationController
require_sudo_mode :create, :update, :destroy
include MembersHelper
def index
scope = @project.memberships
@offset, @limit = api_offset_and_limit
@member_count = scope.count
@member_pages = Paginator.new @member_count, @limit, params['page']
@offset ||= @member_pages.offset
@members = scope.includes(:principal, :roles).order(:id).limit(@limit).offset(@offset).to_a
@members = scope.includes(:principal, :roles).order(:id)
respond_to do |format|
format.html {head :not_acceptable}
format.api
format.api do
@offset, @limit = api_offset_and_limit
@member_count = scope.count
@member_pages = Paginator.new @member_count, @limit, params['page']
@offset ||= @member_pages.offset
@members = @members.limit(@limit).offset(@offset).to_a
end
format.csv do
send_data(members_to_csv(@members), type: 'text/csv; header=present', filename: "#{@project.identifier}-members.csv")
end
end
end

View File

@@ -62,4 +62,23 @@ module MembersHelper
content_tag('em', content.join(", "), :class => "info")
end
end
def members_to_csv(members)
Redmine::Export::CSV.generate(encoding: params[:encoding]) do |csv|
# csv headers
csv << [l(:label_name), l(:label_role), l(:field_principal), l(:label_project)]
# csv lines
members.each do |member|
member.roles.each do |role|
csv << [
member.principal.name,
role.name,
member.principal.is_a?(Group) ? l(:label_group) : l(:label_user),
member.project.name
]
end
end
end
end
end

View File

@@ -45,6 +45,19 @@
<% end %>
</tbody>
</table>
<% other_formats_links do |f| %>
<%= f.link_to_with_query_parameters "CSV", {}, :onclick => "showModal('csv-export-options', '330px'); return false;" %>
<% end %>
<div id="csv-export-options" style="display: none;">
<h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
<%= form_tag(project_memberships_path(project_id: @project.id, format: 'csv'), :method => :get, :id => 'csv-export-form') do %>
<%= export_csv_encoding_select_tag %>
<p class="buttons">
<%= submit_tag l(:button_export), :name => nil, :onclick => 'hideModal(this);', :data => {:disable_with => false} %>
<%= link_to_function l(:button_cancel), 'hideModal(this);' %>
</p>
<% end %>
</div>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>

View File

@@ -657,6 +657,7 @@ en:
label_document_new: New document
label_document_plural: Documents
label_document_added: Document added
label_name: Name
label_role: Role
label_role_plural: Roles
label_role_new: New role

View File

@@ -493,6 +493,7 @@ ja:
label_document_new: 新しい文書
label_document_plural: 文書
label_document_added: 文書の追加
label_name: 名前
label_role: ロール
label_role_plural: ロール
label_role_new: 新しいロール

View File

@@ -25,6 +25,37 @@ class MembersControllerTest < Redmine::ControllerTest
@request.session[:user_id] = 2
end
def test_index_with_csv_format_should_export_csv
project = Project.find(5)
get(
:index,
params: {
project_id: project.id,
format: 'csv'
}
)
assert_response :success
assert_equal 'text/csv; header=present', response.media_type
lines = response.body.chomp.split("\n")
# Number of lines
assert_equal project.memberships.sum{|m| m.roles.count } + 1, lines.size
# Header
assert_equal "Name,Role,User or Group,Project", lines.first
# Details
to_test = [
'John Smith,Manager,User,Private child of eCookbook',
'A Team,Manager,Group,Private child of eCookbook',
'A Team,Developer,Group,Private child of eCookbook',
'User Misc,Manager,User,Private child of eCookbook',
'User Misc,Developer,User,Private child of eCookbook',
'Redmine Admin,Manager,User,Private child of eCookbook'
]
to_test.each do |expected|
assert_includes lines, expected
end
end
def test_new
get(:new, :params => {:project_id => 1})
assert_response :success