-
Notifications
You must be signed in to change notification settings - Fork 45
feat(sdk): guard the delete organization flow #1881
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
Open
whoAbhishekSah
wants to merge
10
commits into
main
Choose a base branch
from
sdk-org-delete-guards
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
dd2081b
feat(sdk): guard the delete organization flow
whoAbhishekSah d8b1b73
fix(sdk): review fixes for the delete organization guards
whoAbhishekSah cfd992e
feat(sdk): drive the delete button from CheckOrganizationDelete
whoAbhishekSah 3ac4b9d
fix(sdk): say please in the delete blocker instructions
whoAbhishekSah 6706a75
fix(sdk): hold the delete confirm during balance refetches
whoAbhishekSah 43bb2b1
fix(sdk): show mapped instructions instead of raw server errors
whoAbhishekSah f864baf
fix(sdk): say support will reach out for a token debt
whoAbhishekSah 3eb7c09
fix(sdk): drop the token amount from the delete dialog warning
whoAbhishekSah 274f3f3
fix(sdk): explain the token debt blocker in plain words
whoAbhishekSah 009314c
chore(sdk): move @raystack/proton to the latest proton main
whoAbhishekSah File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import { ConnectError } from '@connectrpc/connect'; | ||
|
|
||
| // The server reports why an organization cannot be deleted as a list of | ||
| // blockers, each with a machine-readable type. These are the types the | ||
| // server knows today, mapped to a short instruction the user can act on. | ||
| // The wording must stay in line with the server behavior: a paid | ||
| // subscription must be downgraded, unpaid invoices must be paid, and a | ||
| // token debt is settled through support. | ||
| const BLOCKER_INSTRUCTIONS: Record<string, (count: number) => string> = { | ||
| ACTIVE_SUBSCRIPTION: () => | ||
| 'Please downgrade the subscription to the standard plan', | ||
| UNPAID_INVOICE: count => | ||
| count > 1 | ||
| ? `Please pay the ${count} open invoices from the billing page` | ||
| : 'Please pay the open invoice from the billing page', | ||
| NEGATIVE_TOKEN_BALANCE: () => | ||
| 'Your token balance is negative. Please add tokens from the Tokens page to bring it back up, or pay the invoice when it arrives, and then you can delete' | ||
| }; | ||
|
|
||
| // Shown when the server reports a blocker kind this version does not know, | ||
| // or when the error could not be read at all. | ||
| export const GENERIC_DELETE_BLOCKED_MESSAGE = | ||
| 'Something is blocking the delete right now. Please try again later or contact support.'; | ||
|
|
||
| // instructionLines turns a list of blockers into one instruction per kind | ||
| // of blocker. Blockers of the same kind are counted so the instruction can | ||
| // say "the 2 open invoices". A kind without a known instruction becomes the | ||
| // generic message, once. | ||
| export function instructionLines(blockers: { type: string }[]): string[] { | ||
| const counts = new Map<string, number>(); | ||
| for (const blocker of blockers) { | ||
| counts.set(blocker.type, (counts.get(blocker.type) ?? 0) + 1); | ||
| } | ||
| const lines: string[] = []; | ||
| let hasUnknown = false; | ||
| for (const [type, count] of counts) { | ||
| const instruction = BLOCKER_INSTRUCTIONS[type]; | ||
| if (instruction) { | ||
| lines.push(instruction(count)); | ||
| } else { | ||
| hasUnknown = true; | ||
| } | ||
| } | ||
| if (hasUnknown) { | ||
| lines.push(GENERIC_DELETE_BLOCKED_MESSAGE); | ||
| } | ||
| return lines; | ||
| } | ||
|
|
||
| // deleteBlockedDescription reads the blockers out of a failed_precondition | ||
| // error from DeleteOrganization and returns the instructions as one string. | ||
| // The server attaches them as a google.rpc.PreconditionFailure detail; over | ||
| // the Connect JSON protocol that detail arrives with a ready-made JSON copy | ||
| // in its debug field. When the error carries nothing readable, the generic | ||
| // message is returned, so the raw server text is never shown. | ||
| export function deleteBlockedDescription(err: ConnectError): string { | ||
| for (const detail of err.details) { | ||
| if (!('type' in detail) || detail.type !== 'google.rpc.PreconditionFailure') { | ||
| continue; | ||
| } | ||
| const debug = (detail as { debug?: unknown }).debug; | ||
| if (typeof debug !== 'object' || debug === null) { | ||
| continue; | ||
| } | ||
| const violations = (debug as { violations?: unknown }).violations; | ||
| if (!Array.isArray(violations)) { | ||
| continue; | ||
| } | ||
| const blockers = violations | ||
| .filter( | ||
| (violation): violation is { type: string } => | ||
| typeof violation === 'object' && | ||
| violation !== null && | ||
| typeof (violation as { type?: unknown }).type === 'string' | ||
| ); | ||
| const lines = instructionLines(blockers); | ||
| if (lines.length > 0) { | ||
| return lines.join('. '); | ||
| } | ||
| } | ||
| return GENERIC_DELETE_BLOCKED_MESSAGE; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { create } from '@bufbuild/protobuf'; | ||
| import { RQLFilterSchema } from '@raystack/proton/frontier'; | ||
| import { INVOICE_STATES } from './constants'; | ||
|
|
||
| // An invoice the customer still has to pay: open state with a non-zero | ||
| // amount. This is defined once here so every caller means the same thing | ||
| // by it. | ||
| export function openInvoiceFilters() { | ||
| return [ | ||
| create(RQLFilterSchema, { | ||
| name: 'state', | ||
| operator: 'eq', | ||
| value: { case: 'stringValue', value: INVOICE_STATES.OPEN } | ||
| }), | ||
| create(RQLFilterSchema, { | ||
| name: 'amount', | ||
| operator: 'gt', | ||
| value: { case: 'numberValue', value: 0 } | ||
| }) | ||
| ]; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.