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

CSV exports #1020

Merged
merged 5 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
67 changes: 67 additions & 0 deletions home/service/details_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from home.service.details import (
DashboardDetailsService,
DatabaseDetailsService,
DatasetDetailsService,
)


class DatasetDetailsCsvFormatter:
def __init__(self, details_service: DatasetDetailsService):
self.details_service = details_service

def data(self):
return [
(
column.name,
column.display_name,
column.type,
column.nullable,
MatMoore marked this conversation as resolved.
Show resolved Hide resolved
column.description,
)
for column in self.details_service.table_metadata.column_details
]

def headers(self):
return [
"name",
"display_name",
"type",
"nullable",
MatMoore marked this conversation as resolved.
Show resolved Hide resolved
"description",
]


class DatabaseDetailsCsvFormatter:
def __init__(self, details_service: DatabaseDetailsService):
self.details_service = details_service

def data(self):
return [
(
table.entity_ref.urn,
table.entity_ref.display_name,
table.description,
)
for table in self.details_service.entities_in_database
]

def headers(self):
return [
"urn",
"display_name",
"description",
]


class DashboardDetailsCsvFormatter:
def __init__(self, details_service: DashboardDetailsService):
self.details_service = details_service

def data(self):
return [
(chart.entity_ref.urn, chart.entity_ref.display_name, chart.description)
for chart in self.details_service.children
]

def headers(self):
return ["urn", "display_name", "description"]
3 changes: 0 additions & 3 deletions home/tests.py

This file was deleted.

5 changes: 5 additions & 0 deletions home/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
views.metadata_specification_view,
name="metadata_specification",
),
path(
"details/<str:result_type>/<str:urn>.csv",
views.details_view_csv,
name="details_csv",
),
path(
"details/<str:result_type>/<str:urn>",
views.details_view,
Expand Down
96 changes: 49 additions & 47 deletions home/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import csv
from urllib.parse import urlparse

from data_platform_catalogue.client.exceptions import EntityDoesNotExist
from data_platform_catalogue.search_types import DomainOption
from django.conf import settings
from django.http import Http404, HttpResponseBadRequest
from django.http import Http404, HttpResponse, HttpResponseBadRequest
from django.shortcuts import render
from django.utils.translation import gettext as _
from django.views.decorators.cache import cache_control
Expand All @@ -15,6 +16,11 @@
DatabaseDetailsService,
DatasetDetailsService,
)
from home.service.details_csv import (
DashboardDetailsCsvFormatter,
DatabaseDetailsCsvFormatter,
DatasetDetailsCsvFormatter,
)
from home.service.domain_fetcher import DomainFetcher
from home.service.glossary import GlossaryService
from home.service.metadata_specification import MetadataSpecificationService
Expand All @@ -33,60 +39,56 @@ def home_view(request):

@cache_control(max_age=300, private=True)
def details_view(request, result_type, urn):
if result_type == "table":
service = dataset_service(urn)
return render(request, service.template, service.context)
if result_type == "database":
context = database_details(urn)
return render(request, "details_database.html", context)
if result_type == "chart":
context = chart_details(urn)
return render(request, "details_chart.html", context)
if result_type == "dashboard":
context = dashboard_details(urn)
return render(request, "details_dashboard.html", context)


def database_details(urn):
try:
service = DatabaseDetailsService(urn)
except EntityDoesNotExist:
raise Http404("Asset does not exist")

context = service.context

return context

if result_type == "table":
service = DatasetDetailsService(urn)
template = service.template
elif result_type == "database":
service = DatabaseDetailsService(urn)
template = "details_database.html"
elif result_type == "chart":
service = ChartDetailsService(urn)
template = "details_chart.html"
elif result_type == "dashboard":
service = DashboardDetailsService(urn)
template = "details_dashboard.html"
else:
raise Http404("Invalid result type")

return render(request, template, service.context)

def dataset_service(urn):
try:
service = DatasetDetailsService(urn)
except EntityDoesNotExist:
raise Http404("Asset does not exist")

return service
raise Http404(f"{result_type} '{urn}' does not exist")


def chart_details(urn):
try:
service = ChartDetailsService(urn)
except EntityDoesNotExist:
raise Http404("Asset does not exist")

context = service.context

return context


def dashboard_details(urn):
try:
@cache_control(max_age=300, private=True)
def details_view_csv(request, result_type, urn) -> HttpResponse:
if result_type == "table":
service = DatasetDetailsService(urn)
csv_formatter = DatasetDetailsCsvFormatter(service)
elif result_type == "database":
service = DatabaseDetailsService(urn)
csv_formatter = DatabaseDetailsCsvFormatter(service)
elif result_type == "dashboard":
service = DashboardDetailsService(urn)
except EntityDoesNotExist:
raise Http404("Asset does not exist")
csv_formatter = DashboardDetailsCsvFormatter(service)
else:
raise Http404("CSV not available")

# In case there are any quotes in the filename, remove them in order to
# not to break the header.
unsavoury_characters = str.maketrans({'"': ""})
filename = urn.translate(unsavoury_characters) + ".csv"

context = service.context
response = HttpResponse(
content_type="text/csv",
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)
writer = csv.writer(response)
writer.writerow(csv_formatter.headers())
writer.writerows(csv_formatter.data())

return context
return response


@cache_control(max_age=60, private=True)
Expand Down
2 changes: 1 addition & 1 deletion lib/datahub-client/data_platform_catalogue/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ class EntitySummary(BaseModel):
)
description: str = Field(description="A description of the entity")
entity_type: str = Field(
description="indicates the tpye of entity that is summarised"
description="indicates the type of entity that is summarised"
)
tags: list[TagRef] = Field(description="Any tags associated with the entity")

Expand Down
5 changes: 5 additions & 0 deletions templates/details_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
{% endfor %}
</tbody>
</table>
<form action="{% url 'home:details_csv' result_type='dashboard' urn=entity.urn %}">
<button type="submit" class="govuk-button govuk-button--secondary" data-module="govuk-button">
{% translate 'Download chart descriptions (CSV format)' %}
</button>
</form>
{% else %}
<h2 class="govuk-heading-m">{% translate "Dashboard content" %}</h2>
<p class="govuk-body">{% translate "This dashboard is missing chart information." %}</p>
Expand Down
5 changes: 5 additions & 0 deletions templates/details_database.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
{% endfor %}
</tbody>
</table>
<form action="{% url 'home:details_csv' result_type='database' urn=entity.urn %}">
<button type="submit" class="govuk-button govuk-button--secondary" data-module="govuk-button">
{% translate 'Download table descriptions (CSV format)' %}
</button>
</form>
{% else %}
<h2 class="govuk-heading-m">{% translate "Database content" %}</h2>
<p class="govuk-body">{% translate "This database is missing table information." %}</p>
Expand Down
6 changes: 6 additions & 0 deletions templates/details_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
{% endfor %}
</tbody>
</table>

<form action="{% url 'home:details_csv' result_type='table' urn=entity.urn %}">
<button type="submit" class="govuk-button govuk-button--secondary" data-module="govuk-button">
{% translate 'Download table schema (CSV format)' %}
</button>
</form>
{% else %}
<h2 class="govuk-heading-m">{% translate "Table schema" %}</h2>
<p class="govuk-body">{% translate "The schema for this table is not available." %}</p>
Expand Down
119 changes: 119 additions & 0 deletions tests/home/service/test_details_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from unittest.mock import MagicMock

from data_platform_catalogue.entities import Column, EntityRef, EntitySummary

from home.service.details import (
DashboardDetailsService,
DatabaseDetailsService,
DatasetDetailsService,
)
from home.service.details_csv import (
DashboardDetailsCsvFormatter,
DatabaseDetailsCsvFormatter,
DatasetDetailsCsvFormatter,
)


def test_dataset_details_csv_formatter(example_table):
details_service = MagicMock(spec=DatasetDetailsService)
columns = [
Column(
name="foo",
display_name="Foo",
type="string",
description="an example",
nullable=False,
is_primary_key=True,
),
Column(
name="bar",
display_name="Bar",
type="integer",
description="another example",
nullable=True,
is_primary_key=False,
),
]
details_service.table_metadata = example_table
example_table.column_details = columns
csv_formatter = DatasetDetailsCsvFormatter(details_service)

assert csv_formatter.headers() == [
"name",
"display_name",
"type",
"nullable",
"description",
]
assert csv_formatter.data() == [
(
"foo",
"Foo",
"string",
False,
"an example",
),
(
"bar",
"Bar",
"integer",
True,
"another example",
),
]


def test_database_details_csv_formatter(example_database):
tables = [
EntitySummary(
entity_ref=EntityRef(display_name="foo", urn="urn:foo"),
description="an example",
entity_type="Table",
tags=[],
),
EntitySummary(
entity_ref=EntityRef(display_name="bar", urn="urn:bar"),
description="another example",
entity_type="Table",
tags=[],
),
]

details_service = MagicMock(spec=DatabaseDetailsService)
details_service.entities_in_database = tables

csv_formatter = DatabaseDetailsCsvFormatter(details_service)

assert csv_formatter.headers() == ["urn", "display_name", "description"]
assert csv_formatter.data() == [
("urn:foo", "foo", "an example"),
("urn:bar", "bar", "another example"),
]


def test_dashboard_details_csv_formatter(example_dashboard):
charts = [
EntitySummary(
entity_ref=EntityRef(display_name="foo", urn="urn:foo"),
description="an example",
entity_type="Chart",
tags=[],
),
EntitySummary(
entity_ref=EntityRef(display_name="bar", urn="urn:bar"),
description="another example",
entity_type="Chart",
tags=[],
),
]

details_service = MagicMock(spec=DashboardDetailsService)
details_service.children = charts

csv_formatter = DashboardDetailsCsvFormatter(details_service)

assert csv_formatter.headers() == ["urn", "display_name", "description"]
assert csv_formatter.data() == [
("urn:foo", "foo", "an example"),
("urn:bar", "bar", "another example"),
]
Loading