diff --git a/src/palace/manager/celery/tasks/generate_inventory_and_hold_reports.py b/src/palace/manager/celery/tasks/generate_inventory_and_hold_reports.py index 7453f561d..8748cefb3 100644 --- a/src/palace/manager/celery/tasks/generate_inventory_and_hold_reports.py +++ b/src/palace/manager/celery/tasks/generate_inventory_and_hold_reports.py @@ -150,7 +150,7 @@ def run(self) -> None: uid = uuid.uuid4() key = ( - f"inventory_and_holds/{library.short_name}/" + f"{S3Service.DOWNLOADS_PREFIX}/inventory_and_holds/{library.short_name}/" f"inventory-and-holds-for-library-{file_name_modifier}-{uid}.zip" ) self.s3_service.store_stream( diff --git a/src/palace/manager/scripts/playtime_entries.py b/src/palace/manager/scripts/playtime_entries.py index c050abe30..c52033cfb 100644 --- a/src/palace/manager/scripts/playtime_entries.py +++ b/src/palace/manager/scripts/playtime_entries.py @@ -3,6 +3,7 @@ import argparse import csv import os +import uuid from collections import defaultdict from collections.abc import Iterable from datetime import datetime, timedelta @@ -18,6 +19,7 @@ from palace.manager.core.config import Configuration from palace.manager.scripts.base import Script +from palace.manager.service.storage.s3 import S3Service from palace.manager.sqlalchemy.model.time_tracking import PlaytimeEntry, PlaytimeSummary from palace.manager.util.datetime_helpers import previous_months, utc_now @@ -182,17 +184,17 @@ def do_run(self): email_subject = f"{subject_prefix}Playtime Summaries {formatted_start_date} - {formatted_until_date}" reporting_name_with_no_spaces = reporting_name.replace(" ", "_") + "-" - attachment_extension = "csv" - attachment_name = ( + link_extension = "csv" + linked_file_name = ( f"playtime-summary-{reporting_name_with_no_spaces}" - f"{formatted_start_date}-{formatted_until_date}.{attachment_extension}" + f"{formatted_start_date}-{formatted_until_date}.{link_extension}" ) # Write to a temporary file so we don't overflow the memory with TemporaryFile( "w+", prefix=f"playtimereport{formatted_until_date}", - suffix=attachment_extension, + suffix=link_extension, ) as temp: # Write the data as a CSV writer = csv.writer(temp) @@ -207,12 +209,30 @@ def do_run(self): recipient = os.environ.get( Configuration.REPORTING_EMAIL_ENVIRONMENT_VARIABLE ) + if recipient: + uid = uuid.uuid4() + key = ( + f"{S3Service.DOWNLOADS_PREFIX}/{reporting_name}/" + f"{linked_file_name}" + ) + + s3_service = self.services.storage.public() + s3_service.store_stream( + key, + temp, + content_type="text/csv", + ) + + s3_file_link = s3_service.generate_url(key) + self.services.email.send_email( subject=email_subject, receivers=[recipient], - text="", - attachments={attachment_name: temp.read()}, + text=( + f"Download Report here -> {s3_file_link} \n\n" + f"This report will be available for download for 30 days." + ), ) else: self.log.error("No reporting email found, logging complete report.") diff --git a/src/palace/manager/service/storage/s3.py b/src/palace/manager/service/storage/s3.py index d68941f21..7a34f3fde 100644 --- a/src/palace/manager/service/storage/s3.py +++ b/src/palace/manager/service/storage/s3.py @@ -108,6 +108,7 @@ def exception(self) -> BaseException | None: class S3Service(LoggerMixin): MINIMUM_MULTIPART_UPLOAD_SIZE = 5 * 1024 * 1024 # 5MB + DOWNLOADS_PREFIX = "downloads" def __init__( self, diff --git a/tests/manager/scripts/test_playtime_entries.py b/tests/manager/scripts/test_playtime_entries.py index c15886a35..e936453cf 100644 --- a/tests/manager/scripts/test_playtime_entries.py +++ b/tests/manager/scripts/test_playtime_entries.py @@ -25,7 +25,7 @@ from palace.manager.sqlalchemy.model.time_tracking import PlaytimeEntry, PlaytimeSummary from palace.manager.util.datetime_helpers import datetime_utc, previous_months, utc_now from tests.fixtures.database import DatabaseTransactionFixture -from tests.fixtures.services import ServicesEmailFixture +from tests.fixtures.services import ServicesEmailFixture, ServicesFixture def create_playtime_entries( @@ -533,6 +533,7 @@ def test_do_run( self, db: DatabaseTransactionFixture, services_email_fixture: ServicesEmailFixture, + services_fixture: ServicesFixture, ): identifier = db.identifier() collection = db.default_collection() @@ -663,6 +664,11 @@ def test_do_run( ) reporting_name = "test cm" + + mock_s3_service = MagicMock() + mock_s3_service.generate_url.return_value = "http://test" + services_fixture.services.storage.public.override(mock_s3_service) + with ( patch("palace.manager.scripts.playtime_entries.csv.writer") as writer, patch( @@ -799,15 +805,15 @@ def test_do_run( # verify the number of unique loans assert len(loan_identifiers) == sum([x.args[0][7] for x in call_args[1:]]) assert services_email_fixture.mock_emailer.send.call_count == 1 + mock_s3_service.store_stream.assert_called_once() + mock_s3_service.generate_url.assert_called_once() assert services_email_fixture.mock_emailer.send.call_args == call( subject=f"{reporting_name}: Playtime Summaries {cutoff} - {until}", sender=services_email_fixture.sender_email, receivers=["reporting@test.email"], - text="", + text="Download Report here -> http://test \n\nThis report will be available for download for 30 days.", html=None, - attachments={ - f"playtime-summary-{reporting_name.replace(' ', '_')}-{cutoff}-{until}.csv": "" - }, # Mock objects do not write data + attachments=None, ) def test_no_reporting_email(self, db: DatabaseTransactionFixture):