fix(ui): keep naming the workspace in the failed-revocation notice

- Snapshot the workspace name when disconnecting, so the notice keeps
  naming it after the refresh removes the integration record
This commit is contained in:
Pablo F.G
2026-08-18 17:46:57 +02:00
parent d9f957a892
commit 0c296b3227
2 changed files with 27 additions and 8 deletions
@@ -674,6 +674,11 @@ describe("disconnecting a workspace", () => {
// When
expect(await harness.disconnect()).toBe(REVOCATION_OUTCOME.NOT_REVOKED);
// And — the disconnect revalidates the page, so the server data refreshes
// under the notice and comes back with no integration at all: the record
// the workspace was read from is gone by the time the user reads about it.
await harness.refreshPageData();
// Then — the user reads what is true of both sides: nothing is left in
// Prowler to retry, and the app may still be installed at Slack. Saying
// "there is nothing to retry here" is the point — the one thing a user
@@ -92,6 +92,17 @@ type TestMessageState =
| TestMessageSent
| TestMessageFailed;
/**
* A disconnect that removed the row without Slack confirming the revocation.
* The workspace travels with it rather than being read from the record: naming
* the one to clean up is the point of the notice, and the record is gone by the
* time the disconnect's revalidation lands.
*/
interface UnconfirmedRevocation {
/** As the record named it, or `null` when it named none. */
workspace: string | null;
}
// The name may be missing: the id decides what the UI can do with it.
interface SlackChannelRef {
id: string;
@@ -135,8 +146,8 @@ export const SlackIntegrationManager = ({
// and this page is what the user is looking at. The server component's own
// revalidation refreshes the same thing on the next navigation.
const [disconnected, setDisconnected] = useState(false);
/** A disconnect that removed the row without Slack confirming the revocation. */
const [revocationUnconfirmed, setRevocationUnconfirmed] = useState(false);
const [unconfirmedRevocation, setUnconfirmedRevocation] =
useState<UnconfirmedRevocation | null>(null);
/**
* The `code` of the last refusal any Slack-backed call on this page ran into,
* or `null` when the last answer was not a refusal. Every call records it
@@ -417,8 +428,9 @@ export const SlackIntegrationManager = ({
};
const handleDisconnect = async (id: string) => {
const workspace =
integration?.attributes.configuration.team_name ?? "your Slack workspace";
const recordedWorkspace =
integration?.attributes.configuration.team_name ?? null;
const workspace = recordedWorkspace ?? "your Slack workspace";
setIsDisconnecting(true);
try {
@@ -444,7 +456,9 @@ export const SlackIntegrationManager = ({
// Only an explicit "not revoked" sends the user to finish the job in
// Slack. An unreported outcome is neither a failed revocation nor a
// confirmed one, so it claims neither.
setRevocationUnconfirmed(revoked === false);
setUnconfirmedRevocation(
revoked === false ? { workspace: recordedWorkspace } : null,
);
if (revoked !== false) {
toast({
@@ -525,7 +539,7 @@ export const SlackIntegrationManager = ({
</Alert>
)}
{revocationUnconfirmed && (
{unconfirmedRevocation && (
<Alert variant="warning">
<AlertTitle>
Slack disconnected remove Prowler&apos;s access in Slack
@@ -534,8 +548,8 @@ export const SlackIntegrationManager = ({
The integration and the token Prowler had stored are gone from
Prowler, so there is nothing to retry here. Slack did not confirm
the revocation, so the Prowler app may still be installed in{" "}
{workspaceName ?? "the workspace"} remove it from that
workspace&apos;s Slack app settings.
{unconfirmedRevocation.workspace ?? "the workspace"} remove it
from that workspace&apos;s Slack app settings.
</AlertDescription>
</Alert>
)}