Skip to content

Commit 8c8c231

Browse files
committed
feat(front): add service account list download button
- Add service account list download button - Implement export endpoint at ServiceAccountController - Add org scope check on download button
1 parent 9418047 commit 8c8c231

File tree

6 files changed

+86
-9
lines changed

6 files changed

+86
-9
lines changed

front/assets/js/service_accounts/components/ServiceAccountsList.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,23 @@ export const ServiceAccountsList = ({
7272
<div className="b">Service Accounts</div>
7373
</div>
7474
</div>
75-
{config.permissions.canManage && (
76-
<button
77-
className="btn btn-primary flex items-center"
78-
onClick={onCreateNew}
79-
>
80-
<span className="material-symbols-outlined mr2">smart_toy</span>
81-
Create Service Account
82-
</button>
83-
)}
75+
<div className="flex">
76+
{config.isOrgScope && (
77+
<a aria-label="Download service accounts as CSV" title="Download service accounts as CSV" className="pointer flex items-center btn-secondary btn nowrap mr2" href={config.urls.export}>
78+
<span className="material-symbols-outlined ">download</span>
79+
Download .csv
80+
</a>
81+
)}
82+
{config.permissions.canManage && (
83+
<button
84+
className="btn btn-primary flex items-center"
85+
onClick={onCreateNew}
86+
>
87+
<span className="material-symbols-outlined mr2">smart_toy</span>
88+
Create Service Account
89+
</button>
90+
)}
91+
</div>
8492
</div>
8593

8694
{serviceAccounts.length === 0 ? (

front/assets/js/service_accounts/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class AppConfig {
1919
urls: {
2020
list: `/service_accounts`,
2121
create: `/service_accounts`,
22+
export: `/service_accounts/export`,
2223
update: (id: string) => `/service_accounts/${id}`,
2324
delete: (id: string) => `/service_accounts/${id}`,
2425
regenerateToken: (id: string) =>

front/assets/js/service_accounts/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export interface Config {
5959
urls: {
6060
list: string;
6161
create: string;
62+
export: string;
6263
update: (id: string) => string;
6364
delete: (id: string) => string;
6465
regenerateToken: (id: string) => string;

front/lib/front_web/controllers/service_account_controller.ex

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,55 @@ defmodule FrontWeb.ServiceAccountController do
138138
|> json(%{error: message})
139139
end
140140
end
141+
142+
def export(conn, _params) do
143+
org_id = conn.assigns.organization_id
144+
145+
data =
146+
Stream.unfold(1, fn
147+
nil ->
148+
nil
149+
150+
page ->
151+
case Models.ServiceAccount.list(org_id, page) do
152+
{:ok, {service_accounts, total_pages}} ->
153+
{service_accounts, next_valid_page_or_nil(total_pages, page)}
154+
155+
_ ->
156+
nil
157+
end
158+
end)
159+
|> Enum.flat_map(& &1)
160+
|> Enum.map(fn e ->
161+
%{
162+
"name" => e.name,
163+
"description" => e.description,
164+
"deactivated" => e.deactivated,
165+
"created_at" => e.created_at,
166+
"updated_at" => e.created_at
167+
}
168+
end)
169+
|> CSV.encode(
170+
headers: [
171+
"name",
172+
"description",
173+
"deactivated",
174+
"created_at",
175+
"updated_at"
176+
]
177+
)
178+
|> Enum.to_list()
179+
180+
send_download(conn, {:binary, data}, filename: "service_accounts.csv")
181+
end
182+
183+
defp next_valid_page_or_nil(total_pages, page) do
184+
next_page_no = page + 1
185+
186+
if next_page_no <= total_pages do
187+
next_page_no
188+
else
189+
nil
190+
end
191+
end
141192
end

front/lib/front_web/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ defmodule FrontWeb.Router do
176176
scope "/service_accounts" do
177177
get("/", ServiceAccountController, :index)
178178
post("/", ServiceAccountController, :create)
179+
get("/export", ServiceAccountController, :export)
179180
get("/:id", ServiceAccountController, :show)
180181
put("/:id", ServiceAccountController, :update)
181182
delete("/:id", ServiceAccountController, :delete)

front/test/front_web/controllers/service_account_controller_test.exs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,21 @@ defmodule FrontWeb.ServiceAccountControllerTest do
268268
end
269269
end
270270

271+
describe "GET /service_accounts/export" do
272+
test "requires service_accounts.view permission", %{
273+
conn: conn,
274+
org_id: org_id,
275+
user_id: user_id
276+
} do
277+
Support.Stubs.PermissionPatrol.remove_all_permissions()
278+
Support.Stubs.PermissionPatrol.add_permissions(org_id, user_id, ["organization.view"])
279+
280+
conn = get(conn, "/service_accounts/export")
281+
282+
assert html_response(conn, 404) =~ "Page not found"
283+
end
284+
end
285+
271286
describe "PUT /service_accounts/:id" do
272287
setup %{org_id: org_id, user_id: user_id} do
273288
Support.Stubs.PermissionPatrol.add_permissions(org_id, user_id, [

0 commit comments

Comments
 (0)