Commit a1b120e4 authored by Mark Chao's avatar Mark Chao

Merge branch '343859-admin-membership-export-background' into 'master'

Move admin memberships CSV export to background job

See merge request gitlab-org/gitlab!80391
parents 8cdb0671 3a94d95f
......@@ -172,6 +172,7 @@ By default, impersonation is enabled. GitLab can be configured to [disable imper
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1772) in GitLab 13.8.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292436) in GitLab 13.9.
> - [Moved to delivery by email](https://gitlab.com/gitlab-org/gitlab/-/issues/343859) in GitLab 14.8.
An administrator can export user permissions for all users in the GitLab instance from the Admin Area's Users page.
The export lists direct membership the users have in groups and projects.
......@@ -189,6 +190,11 @@ Only the first 100,000 user accounts are exported.
![user permission export button](img/export_permissions_v13_11.png)
GitLab creates a CSV file and:
- In GitLab 14.7 and earlier, the file is downloaded in your browser.
- In GitLab 14.8 and later, the file is sent to your primary email address.
#### Users statistics
The **Users statistics** page provides an overview of user accounts by role. These statistics are
......
......@@ -6,29 +6,15 @@ class Admin::UserPermissionExportsController < Admin::ApplicationController
before_action :check_user_permission_export_availability!
def index
response = ::UserPermissions::ExportService.new(current_user).csv_data
::Admin::MembershipsMailer.instance_memberships_export(requested_by: current_user).deliver_later
respond_to do |format|
format.csv do
if response.success?
stream_csv_headers(csv_filename)
flash[:success] = _('Report is generating and will be sent to your email address.')
self.response_body = response.payload
else
flash[:alert] = _('Failed to generate report, please try again after sometime')
redirect_to admin_users_path
end
end
end
redirect_to admin_users_path
end
private
def csv_filename
"user-permissions-export-#{Time.current.to_i}.csv"
end
def check_user_permission_export_availability!
render_404 unless current_user.can?(:export_user_permissions)
end
......
# frozen_string_literal: true
class Admin::MembershipsMailer < ApplicationMailer
helper EmailsHelper
layout 'mailer'
def instance_memberships_export(requested_by:)
filename = "gitlab_memberships_#{Date.current.iso8601}.csv"
csv_data = UserPermissions::ExportService.new.execute.payload[:csv_data]
attachments[filename] = { content: csv_data, mime_type: 'text/csv' }
mail(
to: requested_by.notification_email_or_default,
subject: _('GitLab Memberships CSV Export')
)
end
end
......@@ -2,26 +2,14 @@
module UserPermissions
class ExportService
def initialize(current_user)
@current_user = current_user
end
def csv_data
return ServiceResponse.error(message: 'Insufficient permissions') unless allowed?
ServiceResponse.success(payload: csv_builder.render)
def execute
ServiceResponse.success(payload: { csv_data: csv_builder.render })
end
private
attr_reader :current_user
def allowed?
current_user.can?(:export_user_permissions)
end
def csv_builder
@csv_builder ||= CsvBuilders::Stream.new(data, header_to_value_hash)
@csv_builder ||= CsvBuilder.new(data, header_to_value_hash)
end
def data
......
%p{ style: 'font-size:18px; text-align:center; line-height:30px;' }
= _('The CSV export you requested of all user memberships is attached to this email.')
The CSV export you requested of all user memberships is attached to this email.
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::MembershipsMailer do
include EmailSpec::Matchers
describe '.instance_memberships_export' do
let(:user) { create(:admin) }
subject { described_class.instance_memberships_export(requested_by: user) }
it 'contains memberships csv as an attachment' do
freeze_time do
expect(subject.attachments.size).to eq(1)
expect(subject.attachments[0].content_type).to eq('text/csv')
expect(subject.attachments[0].filename).to eq("gitlab_memberships_#{Date.current.iso8601}.csv")
end
end
it { is_expected.to have_subject 'GitLab Memberships CSV Export' }
it { is_expected.to deliver_to user.notification_email_or_default }
it { is_expected.to have_body_text('The CSV export you requested of all user memberships is attached to this email.') }
end
end
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Admin::UserPermissionExportsController do
let_it_be(:admin) { create(:admin) }
subject { get admin_user_permission_exports_path(format: :csv) }
subject { get admin_user_permission_exports_path }
before do
allow(admin).to receive(:can?).and_call_original
......@@ -14,82 +14,18 @@ RSpec.describe Admin::UserPermissionExportsController do
end
describe '#index', :enable_admin_mode do
context 'when user is authorized' do
context 'when authorized' do
let(:authorized) { true }
before do
allow(UserPermissions::ExportService).to receive(:new).and_return(export_csv_service)
end
context 'when successful' do
let(:csv_data) do
<<~CSV
Username,Email,Type,Path,Access,Last Activity
alvina,alvina@test.com,Group,gitlab-org,Developer,2020-12-18
jasper,jasper@test.com,Project,gitlab-org/www,Maintainer,2020-12-16
CSV
end
let(:export_csv_service) do
instance_spy(UserPermissions::ExportService, csv_data: ServiceResponse.success(payload: csv_data))
end
it 'responds with :ok', :aggregate_failures do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['Content-Type']).to eq('text/csv; charset=utf-8; header=present')
end
it 'invokes the Export Service' do
subject
expect(export_csv_service).to have_received(:csv_data)
end
it 'has the appropriate data' do
subject
it 'redirects back to admin users list with notice' do
subject
expect(csv_response).to eq([
[
'Username',
'Email',
'Type',
'Path',
'Access',
'Last Activity'
],
%w(
alvina
alvina@test.com
Group
gitlab-org
Developer
2020-12-18
),
%w(
jasper
jasper@test.com
Project
gitlab-org/www
Maintainer
2020-12-16
)
])
end
expect(response).to redirect_to(admin_users_path)
expect(flash[:success]).to eq('Report is generating and will be sent to your email address.')
end
context 'when Export fails' do
let(:export_csv_service) do
instance_spy(UserPermissions::ExportService, csv_data: ServiceResponse.error(message: 'Something went wrong!'))
end
it 'responds appropriately', :aggregate_failures do
subject
expect(flash[:alert]).to eq 'Failed to generate report, please try again after sometime'
expect(response).to redirect_to(admin_users_path)
end
it 'enqueues a job to generate the CSV file' do
expect { subject }.to have_enqueued_mail(::Admin::MembershipsMailer, :instance_memberships_export)
end
end
......@@ -102,9 +38,5 @@ RSpec.describe Admin::UserPermissionExportsController do
expect(response).to have_gitlab_http_status(:not_found)
end
end
def csv_response
CSV.parse(response.body)
end
end
end
......@@ -3,59 +3,13 @@
require 'spec_helper'
RSpec.describe UserPermissions::ExportService do
let(:service) { described_class.new(current_user) }
let(:service) { described_class.new }
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user, username: 'Jessica', email: 'jessica@test.com', last_activity_on: Date.new(2020, 12, 16)) }
context 'access' do
shared_examples 'allowed to export user permissions' do
it { expect(service.csv_data).to be_success }
end
shared_examples 'not allowed to export user permissions' do
it { expect(service.csv_data).not_to be_success }
end
before do
stub_licensed_features(export_user_permissions: licensed)
end
context 'when user is an admin', :enable_admin_mode do
let(:current_user) { admin }
context 'when licensed' do
let(:licensed) { true }
it_behaves_like 'allowed to export user permissions'
end
context 'when not licensed' do
let(:licensed) { false }
it_behaves_like 'not allowed to export user permissions'
end
end
context 'when user is not an admin' do
let(:current_user) { user }
context 'when licensed' do
let(:licensed) { true }
it_behaves_like 'not allowed to export user permissions'
end
context 'when not licensed' do
let(:licensed) { false }
it_behaves_like 'not allowed to export user permissions'
end
end
end
context 'data verification', :enable_admin_mode do
subject(:csv) { CSV.parse(service.csv_data.payload.to_a.join, headers: true) }
subject(:csv) { CSV.parse(service.execute.payload[:csv_data], headers: true) }
let_it_be(:current_user) { admin }
let_it_be(:group) { create(:group) }
......
......@@ -15032,9 +15032,6 @@ msgstr ""
msgid "Failed to generate export, please try again later."
msgstr ""
msgid "Failed to generate report, please try again after sometime"
msgstr ""
msgid "Failed to get ref."
msgstr ""
......@@ -16510,6 +16507,9 @@ msgstr ""
msgid "GitLab KAS"
msgstr ""
msgid "GitLab Memberships CSV Export"
msgstr ""
msgid "GitLab Pages"
msgstr ""
......@@ -30678,6 +30678,9 @@ msgstr ""
msgid "Report abuse to admin"
msgstr ""
msgid "Report is generating and will be sent to your email address."
msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
......@@ -36346,6 +36349,9 @@ msgstr ""
msgid "The CSV export will be created in the background. Once finished, it will be sent to %{email} in an attachment."
msgstr ""
msgid "The CSV export you requested of all user memberships is attached to this email."
msgstr ""
msgid "The GitLab subscription service (customers.gitlab.com) is currently experiencing an outage. You can monitor the status and get updates at %{linkStart}status.gitlab.com%{linkEnd}."
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment