From 5f9465614bd1263a3a979e068cd640bf47a85347 Mon Sep 17 00:00:00 2001 From: ruslandoga Date: Thu, 21 Mar 2024 18:35:42 +0800 Subject: [PATCH] Include domain and dates in zip archive filename (#3921) * include domain and dates in zip archive filename * adapt to comments --- lib/plausible/exports.ex | 16 ++++++++++++++++ lib/plausible/s3.ex | 16 +++++++++++++--- lib/plausible/sites.ex | 8 ++++++++ lib/workers/export_csv.ex | 11 ++++++++++- test/plausible/exports_test.exs | 2 ++ 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/lib/plausible/exports.ex b/lib/plausible/exports.ex index 4f2094de3..dbabcd7ff 100644 --- a/lib/plausible/exports.ex +++ b/lib/plausible/exports.ex @@ -6,6 +6,22 @@ defmodule Plausible.Exports do require Plausible import Ecto.Query + @doc """ + Renders filename for the Zip archive containing the exported CSV files. + + Examples: + + iex> archive_filename("plausible.io", ~D[2021-01-01], ~D[2024-12-31]) + "plausible.io_20210101_20241231.zip" + + iex> archive_filename("Bücher.example", ~D[2021-01-01], ~D[2024-12-31]) + "Bücher.example_20210101_20241231.zip" + + """ + def archive_filename(domain, min_date, max_date) do + "#{domain}_#{Calendar.strftime(min_date, "%Y%m%d")}_#{Calendar.strftime(max_date, "%Y%m%d")}.zip" + end + @doc """ Builds Ecto queries to export data from `events_v2` and `sessions_v2` tables into the format of `imported_*` tables for a website. diff --git a/lib/plausible/s3.ex b/lib/plausible/s3.ex index 2768ce9ef..12be32fc2 100644 --- a/lib/plausible/s3.ex +++ b/lib/plausible/s3.ex @@ -84,15 +84,25 @@ defmodule Plausible.S3 do In the current implementation the bucket always goes into the path component. """ - @spec export_upload_multipart(Enumerable.t(), String.t(), Path.t(), keyword) :: + @spec export_upload_multipart(Enumerable.t(), String.t(), Path.t(), String.t(), keyword) :: :uri_string.uri_string() - def export_upload_multipart(stream, s3_bucket, s3_path, config_overrides \\ []) do + def export_upload_multipart(stream, s3_bucket, s3_path, filename, config_overrides \\ []) do config = ExAws.Config.new(:s3) + encoded_filename = URI.encode(filename) + disposition = ~s[attachment; filename="#{encoded_filename}"] + + disposition = + if encoded_filename != filename do + disposition <> "; filename*=utf-8''#{encoded_filename}" + else + disposition + end + # 5 MiB is the smallest chunk size AWS S3 supports chunk_into_parts(stream, 5 * 1024 * 1024) |> ExAws.S3.upload(s3_bucket, s3_path, - content_disposition: ~s|attachment; filename="Plausible.zip"|, + content_disposition: disposition, content_type: "application/zip" ) |> ExAws.request!(config_overrides) diff --git a/lib/plausible/sites.ex b/lib/plausible/sites.ex index 12d4235ad..b41591e3f 100644 --- a/lib/plausible/sites.ex +++ b/lib/plausible/sites.ex @@ -23,6 +23,14 @@ defmodule Plausible.Sites do Repo.get_by!(Site, domain: domain) end + def get_domain!(site_id) do + Plausible.Repo.one!( + from s in Plausible.Site, + where: [id: ^site_id], + select: s.domain + ) + end + @spec toggle_pin(Auth.User.t(), Site.t()) :: {:ok, Site.UserPreference.t()} | {:error, :too_many_pins} def toggle_pin(user, site) do diff --git a/lib/workers/export_csv.ex b/lib/workers/export_csv.ex index 5b46d4fd5..be7d6d395 100644 --- a/lib/workers/export_csv.ex +++ b/lib/workers/export_csv.ex @@ -43,6 +43,10 @@ defmodule Plausible.Workers.ExportCSV do ) ) else + domain = Plausible.Sites.get_domain!(site_id) + export_archive_filename = Plausible.Exports.archive_filename(domain, min_date, max_date) + s3_config_overrides = s3_config_overrides(args) + download_url = DBConnection.run( ch, @@ -55,7 +59,12 @@ defmodule Plausible.Workers.ExportCSV do ), format: "CSVWithNames" ) - |> Plausible.S3.export_upload_multipart(s3_bucket, s3_path, s3_config_overrides(args)) + |> Plausible.S3.export_upload_multipart( + s3_bucket, + s3_path, + export_archive_filename, + s3_config_overrides + ) end, timeout: :infinity ) diff --git a/test/plausible/exports_test.exs b/test/plausible/exports_test.exs index 7f0f1e01f..24ac8fd1d 100644 --- a/test/plausible/exports_test.exs +++ b/test/plausible/exports_test.exs @@ -1,6 +1,8 @@ defmodule Plausible.ExportsTest do use Plausible.DataCase, async: true + doctest Plausible.Exports, import: true + # for e2e export->import tests please see Plausible.Imported.CSVImporterTest describe "export_queries/2" do