mirror of
https://github.com/google/nomulus.git
synced 2025-07-22 18:55:58 +02:00
Replace invoice email attachement with bucket link (#2299)
This commit is contained in:
parent
b21e1a1935
commit
f9e0908022
7 changed files with 61 additions and 41 deletions
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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). */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue