Skip to content

Commit

Permalink
Address PR feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
jshearer committed Feb 9, 2024
1 parent 401967b commit 65efafb
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 255 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import { isFinite } from "https://cdn.skypack.dev/lodash";
import { commonTemplate } from "../template.ts";
import { Recipient } from "../template.ts";

interface DataProcessingArguments {
interface DataMovementStalledArguments {
bytes_processed: number;
recipients: Recipient[];
evaluation_interval: string;
spec_type: string;
}

type DataNotProcessedRecord = AlertRecord<"data_not_processed_in_interval_v2", DataProcessingArguments>;
type DataMovementStalledRecord = AlertRecord<"data_movement_stalled", DataMovementStalledArguments>;

const getTaskDetailsPageURL = (catalogName: string, specType: string) =>
`https://dashboard.estuary.dev/${specType}s/details/overview?catalogName=${catalogName}`;

const formatAlertEmail = ({
arguments: { recipients, evaluation_interval, spec_type },
catalog_name,
}: DataNotProcessedRecord): EmailConfig[] => {
}: DataMovementStalledRecord): EmailConfig[] => {
let formattedEvaluationInterval = evaluation_interval;

// A postgresql interval in hour increments has the following format: 'HH:00:00'.
Expand Down Expand Up @@ -54,7 +54,7 @@ const formatAlertEmail = ({
const formatConfirmationEmail = ({
arguments: { recipients, spec_type },
catalog_name,
}: DataNotProcessedRecord): EmailConfig[] => {
}: DataMovementStalledRecord): EmailConfig[] => {
const subject = `Estuary Flow: Alert resolved for ${spec_type} ${catalog_name}`;

const detailsPageURL = getTaskDetailsPageURL(catalog_name, spec_type);
Expand All @@ -73,7 +73,7 @@ const formatConfirmationEmail = ({
}));
};

export const dataNotProcessedInIntervalEmail = (request: DataNotProcessedRecord): EmailConfig[] => {
export const dataMovementStalledEmail = (request: DataMovementStalledRecord): EmailConfig[] => {
if (request.resolved_at) {
return formatConfirmationEmail(request);
} else {
Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/alerts/alert_types/free_trial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Recipient } from "../template.ts";
import { commonTemplate } from "../template.ts";

interface FreeTrialArguments {
tenant: string;
// This feels like it should apply to all alert types, and doesn't belong here..
recipients: Recipient[];
trial_start: string;
trial_end: string;
tenant: string;
has_credit_card: boolean;
}

Expand Down
2 changes: 1 addition & 1 deletion supabase/functions/alerts/alert_types/free_trial_ending.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Recipient } from "../template.ts";
import { commonTemplate } from "../template.ts";

interface FreeTrialEnding {
tenant: string;
// This feels like it should apply to all alert types, and doesn't belong here..
recipients: Recipient[];
trial_start: string;
trial_end: string;
tenant: string;
has_credit_card: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { AlertRecord, EmailConfig } from "../index.ts";
import { Recipient } from "../template.ts";
import { commonTemplate } from "../template.ts";

interface DelinquentTenantArguments {
interface FreeTrialStalledArguments {
tenant: string;
// This feels like it should apply to all alert types, and doesn't belong here..
recipients: Recipient[];
trial_start: string;
trial_end: string;
tenant: string;
}

type DelinquentTenantRecord = AlertRecord<"delinquent_tenant", DelinquentTenantArguments>;
type FreeTrialStalledRecord = AlertRecord<"free_trial_stalled", FreeTrialStalledArguments>;

const delinquentTenant = (req: DelinquentTenantRecord, started: boolean): EmailConfig[] => {
// This alert only fires if they don't have a CC entered and they're >=5 days after the end of their trial
// So this alert resolving implicitly means they entered a CC.
const freeTrialStalled = (req: FreeTrialStalledRecord, started: boolean): EmailConfig[] => {
return req.arguments.recipients.map((recipient) => ({
// emails: [recipient.email],
// TODO(jshearer): Remove [email protected] after testing
emails: ["[email protected]", "[email protected]"],
subject: `Free Tier Grace Period for ${req.arguments.tenant}: ${started ? "No CC 💳❌" : "CC Entered 💳✅"}`,
Expand All @@ -31,6 +32,6 @@ const delinquentTenant = (req: DelinquentTenantRecord, started: boolean): EmailC
}));
};

export const delinquentTenantEmail = (request: DelinquentTenantRecord): EmailConfig[] => {
return delinquentTenant(request, request.resolved_at !== null);
export const freeTrialStalledEmail = (request: FreeTrialStalledRecord): EmailConfig[] => {
return freeTrialStalled(request, request.resolved_at !== null);
};
73 changes: 73 additions & 0 deletions supabase/functions/alerts/alert_types/missing_payment_method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { AlertRecord, EmailConfig } from "../index.ts";
import { commonTemplate, Recipient } from "../template.ts";

interface MissingPaymentMethodArguments {
// This feels like it should apply to all alert types, and doesn't belong here..
recipients: Recipient[];
trial_start: string;
trial_end: string;
tenant: string;
plan_state: "free_tier" | "free_trial" | "paid";
}

const faq = [
{
question: "Where is my data stored?",
answer: "By default, all collection data is stored in an Estuary-owned cloud storage bucket" +
"with a 30 day retention plicy. Now that you have a paid account, you can update this " +
"to store data in your own cloud storage bucket. We support GCS, S3, and Azure Blob storage.",
},
{
question: "How can I access Estuary Support?",
answer: "Reach out to [email protected] or join our slack.",
},
{
question: "Is it possible to schedule data flows?",
answer: "Estuary moves most data in real-time by default, without the need for scheduling, " +
"but you can add “update delays” to data warehouses to enable more downtime on your " +
"warehouse for cost savings. This can be enabled under “advanced settings” and default " +
"settings are 30 minutes for a warehouse.",
},
];

type MissingPaymentRecord = AlertRecord<"missing_payment_method", MissingPaymentMethodArguments>;

const paymentMethodProvided = (req: MissingPaymentRecord): EmailConfig[] => {
return req.arguments.recipients.map((recipient) => ({
emails: [recipient.email],
subject: `Estuary: Thanks for Adding a Payment Method🎉`,
content: commonTemplate(
`
<mj-text font-size="17px">We hope you are enjoying Estuary Flow. We have received your payment method for your account <a class="identifier">${req.arguments.tenant}</a>. ${
req.arguments.plan_state === "free_trial"
? `After your free trial ends on <strong>${req.arguments.trial_end}</strong>, you will automatically be switched the paid tier.`
: `you are now on the paid tier.`
}</mj-text>
<mj-button href="https://dashboard.estuary.dev/admin/billing">📈 See your bill</mj-button>
<mj-divider border-width="1px" border-style="dashed" border-color="lightgrey" padding-top="40px" padding-bottom="10px" />
<mj-text align="center" font-weight="bold" font-size="22px">Frequently Asked Questions</mj-text>
<mj-divider border-width="1px" border-style="dashed" border-color="lightgrey" padding-bottom="20px" />
${
faq.map(({ question, answer }) => `
<mj-text font-weight="bold" font-size="19px">${question}</mj-text>
<mj-text font-size="17px">${answer}</mj-text>
`).join("\n")
}
`,
recipient,
),
}));
};

export const missingPaymentMethodEmail = (request: MissingPaymentRecord): EmailConfig[] => {
// We should only send an email on the trailing edge of this alert, i.e
// "payment method is no longer missing"
if (request.resolved_at) {
return paymentMethodProvided(request);
} else {
// Maaaaaybe we want to send an email here on tenant creation that says something like
// "Welcome to Estuary! You're on the free tier, but you'll need to add a payment method to continue using the platform after your trial ends."
return [];
}
};
46 changes: 0 additions & 46 deletions supabase/functions/alerts/alert_types/paid_tenant.ts

This file was deleted.

24 changes: 12 additions & 12 deletions supabase/functions/alerts/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { serve } from "https://deno.land/[email protected]/http/server.ts";

import { corsHeaders } from "../_shared/cors.ts";
import { dataNotProcessedInIntervalEmail } from "./alert_types/data_not_processed_in_interval.ts";
import { delinquentTenantEmail } from "./alert_types/delinquent_tenant.ts";
import { dataMovementStalledEmail } from "./alert_types/data_movement_stalled.ts";
import { freeTrialStalledEmail } from "./alert_types/free_trial_stalled.ts";
import { freeTrialEndingEmail } from "./alert_types/free_trial_ending.ts";
import { paidTenantEmail } from "./alert_types/paid_tenant.ts";
import { missingPaymentMethodEmail } from "./alert_types/missing_payment_method.ts";
import { freeTrialEmail } from "./alert_types/free_trial.ts";

export interface AlertRecord<T extends keyof typeof emailTemplates, A> {
Expand All @@ -22,11 +22,11 @@ export interface EmailConfig {
}

const emailTemplates = {
"delinquent_tenant": delinquentTenantEmail,
"free_trial_ending": freeTrialEndingEmail,
"free_trial": freeTrialEmail,
"paid_tenant": paidTenantEmail,
"data_not_processed_in_interval_v2": dataNotProcessedInIntervalEmail,
"free_trial_ending": freeTrialEndingEmail,
"free_trial_stalled": freeTrialStalledEmail,
"missing_payment_method": missingPaymentMethodEmail,
"data_movement_stalled": dataMovementStalledEmail,
};

// This is a temporary type guard for the POST request that provides shallow validation
Expand Down Expand Up @@ -144,19 +144,19 @@ serve(async (rawRequest: Request): Promise<Response> => {
// templates have different arguments, that looks like (never)=>EmailConfig[].
// [1]: https://github.com/microsoft/TypeScript/issues/30581
switch (request.alert_type) {
case "data_not_processed_in_interval_v2":
case "free_trial":
pendingEmails = emailTemplates[request.alert_type](request);
break;
case "free_trial":
case "free_trial_ending":
pendingEmails = emailTemplates[request.alert_type](request);
break;
case "paid_tenant":
case "free_trial_stalled":
pendingEmails = emailTemplates[request.alert_type](request);
break;
case "free_trial_ending":
case "missing_payment_method":
pendingEmails = emailTemplates[request.alert_type](request);
break;
case "delinquent_tenant":
case "data_movement_stalled":
pendingEmails = emailTemplates[request.alert_type](request);
break;
default: {
Expand Down
Loading

0 comments on commit 65efafb

Please sign in to comment.