Replace invoice email attachement with bucket link (#2299)

This commit is contained in:
Pavlo Tkach 2024-01-25 14:08:08 -05:00 committed by GitHub
parent b21e1a1935
commit f9e0908022
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 61 additions and 41 deletions

View file

@ -163,7 +163,12 @@ public class InvoicingPipeline implements Serializable {
}
}
/** Saves the billing events to a single overall invoice CSV file. */
/**
* Saves the billing events to a single overall invoice CSV file. TextIO always produces the file
* of type text/plain, which we then update to desired text/csv before sending an email to billing
* team {@link google.registry.reporting.billing.BillingEmailUtils#emailOverallInvoice()
* emailOverallInvoice}
*/
static void saveInvoiceCsv(
PCollection<google.registry.beam.billing.BillingEvent> billingEvents,
InvoicingPipelineOptions options) {

View file

@ -721,6 +721,17 @@ public final class RegistryConfig {
return "gs://" + billingBucket;
}
/**
* Returns origin part of the URL of the billing invoice file
*
* @see google.registry.beam.billing.InvoicingPipeline
*/
@Provides
@Config("billingInvoiceOriginUrl")
public static String provideBillingInvoiceOriginUrl(RegistryConfigSettings config) {
return config.billing.billingInvoiceOriginUrl;
}
/**
* Returns whether or not we should publish invoices to partners automatically by default.
*

View file

@ -173,6 +173,7 @@ public class RegistryConfigSettings {
public List<String> invoiceEmailRecipients;
public String invoiceReplyToEmailAddress;
public String invoiceFilePrefix;
public String billingInvoiceOriginUrl;
}
/** Configuration for Registry Data Escrow (RDE). */

View file

@ -381,6 +381,7 @@ billing:
# Optional return address that overrides the default.
invoiceReplyToEmailAddress: null
invoiceFilePrefix: REG-INV
billingInvoiceOriginUrl: https://billing-origin-url/
rde:
# URL prefix of ICANN's server to upload RDE reports to. Nomulus adds /TLD/ID

View file

@ -118,6 +118,14 @@ public class GcsUtils implements Serializable {
storage().delete(blobId);
}
/** Update file content type on existing GCS file */
public void updateContentType(BlobId blobId, String contentType) throws StorageException {
if (existsAndNotEmpty(blobId)) {
Blob blob = storage().get(blobId);
blob.toBuilder().setContentType(contentType).build().update();
}
}
/**
* Returns a list of all object names within a bucket for a given prefix.
*

View file

@ -15,20 +15,17 @@
package google.registry.reporting.billing;
import static com.google.common.base.Throwables.getRootCause;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.StorageException;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.groups.GmailClient;
import google.registry.reporting.billing.BillingModule.InvoiceDirectoryPrefix;
import google.registry.util.EmailMessage;
import google.registry.util.EmailMessage.Attachment;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
@ -37,6 +34,7 @@ import org.joda.time.YearMonth;
/** Utility functions for sending emails involving monthly invoices. */
public class BillingEmailUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final GmailClient gmailClient;
private final YearMonth yearMonth;
private final InternetAddress outgoingEmailAddress;
@ -46,6 +44,7 @@ public class BillingEmailUtils {
private final String billingBucket;
private final String invoiceFilePrefix;
private final String invoiceDirectoryPrefix;
private final String billingInvoiceOriginUrl;
private final GcsUtils gcsUtils;
@Inject
@ -58,6 +57,7 @@ public class BillingEmailUtils {
@Config("invoiceReplyToEmailAddress") Optional<InternetAddress> replyToEmailAddress,
@Config("billingBucket") String billingBucket,
@Config("invoiceFilePrefix") String invoiceFilePrefix,
@Config("billingInvoiceOriginUrl") String billingInvoiceOriginUrl,
@InvoiceDirectoryPrefix String invoiceDirectoryPrefix,
GcsUtils gcsUtils) {
this.gmailClient = gmailClient;
@ -69,31 +69,36 @@ public class BillingEmailUtils {
this.billingBucket = billingBucket;
this.invoiceFilePrefix = invoiceFilePrefix;
this.invoiceDirectoryPrefix = invoiceDirectoryPrefix;
this.billingInvoiceOriginUrl = billingInvoiceOriginUrl;
this.gcsUtils = gcsUtils;
}
/** Sends an e-mail to all expected recipients with an attached overall invoice from GCS. */
void emailOverallInvoice() {
public void emailOverallInvoice() {
try {
String invoiceFile = String.format("%s-%s.csv", invoiceFilePrefix, yearMonth);
String fileUrl = billingInvoiceOriginUrl + invoiceDirectoryPrefix + invoiceFile;
BlobId invoiceFilename = BlobId.of(billingBucket, invoiceDirectoryPrefix + invoiceFile);
try (InputStream in = gcsUtils.openInputStream(invoiceFilename)) {
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setSubject(String.format("Domain Registry invoice data %s", yearMonth))
.setBody(
String.format("Attached is the %s invoice for the domain registry.", yearMonth))
.setFrom(outgoingEmailAddress)
.setRecipients(invoiceEmailRecipients)
.setReplyToEmailAddress(replyToEmailAddress)
.setAttachment(
Attachment.newBuilder()
.setContent(CharStreams.toString(new InputStreamReader(in, UTF_8)))
.setContentType(MediaType.CSV_UTF_8)
.setFilename(invoiceFile)
.build())
.build());
try {
gcsUtils.updateContentType(invoiceFilename, "text/csv");
} catch (StorageException e) {
// We want to continue with email anyway, it just will be less convenient for billing team
// to process the file.
logger.atWarning().withCause(e).log("Failed to update invoice file type");
}
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setSubject(String.format("Domain Registry invoice data %s", yearMonth))
.setBody(
String.format(
"<p>Use the following link to download %s invoice for the domain registry -"
+ " <a href=\"%s\">invoice</a>.</p>",
yearMonth, fileUrl))
.setFrom(outgoingEmailAddress)
.setRecipients(invoiceEmailRecipients)
.setReplyToEmailAddress(replyToEmailAddress)
.setContentType(MediaType.HTML_UTF_8)
.build());
} catch (Throwable e) {
// Strip one layer, because callWithRetry wraps in a RuntimeException
sendAlertEmail(

View file

@ -21,17 +21,12 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.cloud.storage.BlobId;
import com.google.common.collect.ImmutableList;
import com.google.common.net.MediaType;
import google.registry.gcs.GcsUtils;
import google.registry.groups.GmailClient;
import google.registry.util.EmailMessage;
import google.registry.util.EmailMessage.Attachment;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
@ -45,18 +40,14 @@ class BillingEmailUtilsTest {
private GmailClient gmailClient;
private BillingEmailUtils emailUtils;
private GcsUtils gcsUtils;
private ArgumentCaptor<EmailMessage> contentCaptor;
private GcsUtils gcsUtils;
@BeforeEach
void beforeEach() throws Exception {
gmailClient = mock(GmailClient.class);
gcsUtils = mock(GcsUtils.class);
when(gcsUtils.openInputStream(BlobId.of("test-bucket", "results/REG-INV-2017-10.csv")))
.thenReturn(
new ByteArrayInputStream("test,data\nhello,world".getBytes(StandardCharsets.UTF_8)));
contentCaptor = ArgumentCaptor.forClass(EmailMessage.class);
gcsUtils = mock(GcsUtils.class);
emailUtils = getEmailUtils(Optional.of(new InternetAddress("reply-to@test.com")));
}
@ -72,6 +63,7 @@ class BillingEmailUtilsTest {
replyToAddress,
"test-bucket",
"REG-INV",
"www.google.com/",
"results/",
gcsUtils);
}
@ -89,14 +81,11 @@ class BillingEmailUtilsTest {
ImmutableList.of(
new InternetAddress("hello@world.com"), new InternetAddress("hola@mundo.com")))
.setSubject("Domain Registry invoice data 2017-10")
.setBody("Attached is the 2017-10 invoice for the domain registry.")
.setBody(
"<p>Use the following link to download 2017-10 invoice for the domain registry -"
+ " <a href=\"www.google.com/results/REG-INV-2017-10.csv\">invoice</a>.</p>")
.setReplyToEmailAddress(new InternetAddress("reply-to@test.com"))
.setAttachment(
Attachment.newBuilder()
.setContent("test,data\nhello,world")
.setContentType(MediaType.CSV_UTF_8)
.setFilename("REG-INV-2017-10.csv")
.build())
.setContentType(MediaType.HTML_UTF_8)
.build();
assertThat(emailMessage).isEqualTo(expectedContent);
}