Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add csv export for assets #1392

Merged
merged 4 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,60 @@ def security_objectives(self, request):
def disaster_recovery_objectives(self, request):
return Response({"results": Asset.DEFAULT_DISASTER_RECOVERY_OBJECTIVES})

@action(detail=False, name="Export assets as CSV")
def export_csv(self, request):
try:
(viewable_assets_ids, _, _) = RoleAssignment.get_accessible_object_ids(
Folder.get_root_folder(), request.user, Asset
)
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="assets_export.csv"'

writer = csv.writer(response, delimiter=";")
columns = [
"internal_id",
"name",
"description",
"type",
"security_objectives",
"disaster_recovery_objectives",
"link",
"owners",
"parent_assets",
"labels",
]
writer.writerow(columns)

for asset in Asset.objects.filter(id__in=viewable_assets_ids).iterator():
row = [
asset.id,
asset.name,
asset.description,
asset.type,
",".join(
[i["str"] for i in asset.get_security_objectives_display()]
),
",".join(
[
i["str"]
for i in asset.get_disaster_recovery_objectives_display()
]
),
asset.reference_link,
",".join([o.email for o in asset.owner.all()]),
",".join([o.name for o in asset.parent_assets.all()]),
",".join([o.label for o in asset.filtering_labels.all()]),
]
writer.writerow(row)

return response

except Exception as e:
logger.error(f"Error exporting assets to CSV: {str(e)}")
return HttpResponse(
status=500, content="An error occurred while generating the CSV export."
)


class ReferenceControlViewSet(BaseModelViewSet):
"""
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/app.postcss
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ body {
.btn-mini-secondary {
@apply text-gray-50 bg-pink-500 hover:bg-pink-400;
}
.btn-mini-tertiary {
@apply text-gray-50 bg-sky-500 hover:bg-sky-400;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@
on:click={handleClickForGT}
><i class="fa-solid fa-file-circle-plus"></i>
</button>
{#if URLModel === 'applied-controls'}
{#if ['applied-controls', 'assets'].includes(URLModel)}
<a
href="{URLModel}/export/"
class="inline-block p-3 btn-mini-secondary w-12 focus:relative"
class="inline-block p-3 btn-mini-tertiary w-12 focus:relative"
title={m.exportButton()}
data-testid="export-button"><i class="fa-solid fa-download mr-2" /></a
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { BASE_API_URL } from '$lib/utils/constants';

import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ fetch }) => {
const URLModel = 'applied-controls';
export const GET: RequestHandler = async ({ params, fetch }) => {
const URLModel = params.model;
const endpoint = `${BASE_API_URL}/${URLModel}/export_csv/`;

const res = await fetch(endpoint);
if (!res.ok) {
error(400, 'Error fetching the CSV file');
}

Comment on lines +5 to 13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling and add model validation.

The error handling could be more specific to help debug issues. Also, consider validating the model parameter.

 export const GET: RequestHandler = async ({ params, fetch }) => {
 	const URLModel = params.model;
+	const validModels = ['applied-controls', 'assets'];
+	if (!validModels.includes(URLModel)) {
+		error(400, `Invalid model: ${URLModel}`);
+	}
+
 	const endpoint = `${BASE_API_URL}/${URLModel}/export_csv/`;
 
 	const res = await fetch(endpoint);
 	if (!res.ok) {
-		error(400, 'Error fetching the CSV file');
+		error(res.status, `Failed to fetch CSV: ${await res.text()}`);
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const GET: RequestHandler = async ({ params, fetch }) => {
const URLModel = params.model;
const endpoint = `${BASE_API_URL}/${URLModel}/export_csv/`;
const res = await fetch(endpoint);
if (!res.ok) {
error(400, 'Error fetching the CSV file');
}
export const GET: RequestHandler = async ({ params, fetch }) => {
const URLModel = params.model;
const validModels = ['applied-controls', 'assets'];
if (!validModels.includes(URLModel)) {
error(400, `Invalid model: ${URLModel}`);
}
const endpoint = `${BASE_API_URL}/${URLModel}/export_csv/`;
const res = await fetch(endpoint);
if (!res.ok) {
error(res.status, `Failed to fetch CSV: ${await res.text()}`);
}

const fileName = `applied-controls-${new Date().toISOString()}.csv`;
const fileName = `${URLModel}-${new Date().toISOString()}.csv`;

return new Response(await res.blob(), {
headers: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
on:click={modalCreateForm}
><i class="fa-solid fa-file-circle-plus"></i>
</button>
{#if URLModel === 'applied-controls'}
{#if ['applied-controls', 'assets'].includes(URLModel)}
<a
href="{URLModel}/export/"
class="inline-block p-3 text-gray-50 bg-pink-500 hover:bg-pink-400 w-12 focus:relative"
Expand Down
Loading