Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): add budget spend management #1224

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

ubinatus
Copy link
Contributor

@ubinatus ubinatus commented Mar 24, 2024

What does this PR do?

Fixes #1223

I've added a Loom so that you can see the feature of interacting with budgets in action:
https://www.loom.com/share/986fc21fb8aa4ed6910494838826780d?sid=0a5fe5cd-c94b-4655-85ba-37afaf085ccc

The email that users will receive is the following:

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

Requirements for testing:

  • Tinybird (to retrieve active keys and verification for usage calculation).

  • Stripe (basically for creating a paid workspace). Believe you could use dummy env vars for this so that stripeSchema doesn't throw when you create a paid workspace.

  • App

  • Worfklows (trigger.dev)

  • Test A: Handle a Budget.

    1. Go to the Settings section and head to the Billings tab
    2. You should be able to create a budget with the "Create Budget" button on the "Budgets section". There you can pass the name of the budget, the amount an additional emails to notify whenever the spending reaches the limit.
    3. Once created, you are able to edit or delete your bucket.
  • Test B: Receive a spending limit notification

    1. This test requires you spin the "workflows" trigger app.
    2. Once the job is running (it's running on a 10min interval basis), try to exceed the specified limit directly on Tinybird and wait for the queue to check on your workspace budget.
    3. After the job catched your spending exceeded you should: i) receive an email about the usage, ii) have a lastReachedAt property set on your budget (see db) and iii) should not receive more emails about that budget for the rest of the month cycle.

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Copy link

changeset-bot bot commented Mar 24, 2024

⚠️ No Changeset found

Latest commit: cd38410

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Mar 24, 2024

@ubinatus is attempting to deploy a commit to the Unkey Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

github-actions bot commented Mar 24, 2024

Thank you for following the naming conventions for pull request titles! 🙏

@@ -71,7 +71,7 @@ client.defineJob({
});

const users = await io.runTask(`get users for workspace ${ws.id}`, () =>
getUsers(ws.tenantId),
getClerkOrganizationUsers(ws.tenantId),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Centralized the clerk usage with an util fn. It should handle Clerk's rate limit.

const year = t.getUTCFullYear();
const month = t.getUTCMonth() + 1; // months are 0 indexed

// TODO: Find a way to pipe Tinybird for a given set of workspaceIds.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case it becomes an expensive op, would be great to have a custom pipe to retrieve the active keys and verifications for a given list of workspaces IDs.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, but it's fine for now

apps/workflows/jobs/spend-management.ts Outdated Show resolved Hide resolved
Comment on lines +177 to +179
// TODO: Assumming admin users will not change frequently their emails, we could store in in the budget entity to avoid this step.
await io.runTask(
"retrieve workpaces admin emails from Clerk for exceeded budgets",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Unkey is not storing emails but retrieving them directly from Clerk, this job will need to retrieve the involved workspace admin's emails. So either i) retrieve it directly on the job or ii) save it on the budget and assume the admin user email won't change.

Comment on lines +35 to +39
/**
* Webhook URL to POST trigger when metered resources reached set amount.
* @dev TODO: Not used currently.
*/
webhookUrl?: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just explanatory to showcase future customization of budget notification with webhookUrls, Slack, etc.

@@ -101,4 +102,45 @@ export class Resend {
console.error("Error occurred sending payment issue email ", JSON.stringify(error));
}
}

public async sendBatchBudgetExceeded(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds the batch feature from Resend in order to send the budget spending limit emails.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use the create-budget-button, as it only adds the enabled switch form item and interacts with a different mutation.

@chronark chronark self-assigned this Mar 25, 2024
Copy link

vercel bot commented Mar 25, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
workflows ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 25, 2024 10:36am

@ubinatus ubinatus marked this pull request as ready for review March 31, 2024 02:18
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I had to remove the "use client" directive.

Idk why but the job was failing when calling resend. (It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.). Very weird behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created this billing util that is currently used in the spend-management workflow and in the ProUsage web component

Copy link
Contributor Author

@ubinatus ubinatus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ready for review. By the way, I've noted that the endpoint__ratelimits_by_workspace__v1 pipe is missing in the internals/tinybird.

So, for the matter of this testing, I assumed the following pipe:

%
SELECT
    time
FROM mv__ratelimits_monthly__v1
where
    workspaceId = {{ String(workspaceId, required=True) }}
    and time = makeDate({{ Int64(year) }}, {{ Int64(month) }}, 1)
group by time

@ubinatus ubinatus requested a review from chronark March 31, 2024 03:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add spend management budgets
2 participants