FN-5878: re-hide revealed secrets from the eye toggle
Let users hide a revealed secret again from the row visibility toggle. - add a hideSecret helper that clears reveal timers and resets the revealed value - make the row eye toggle switch between reveal and hide states instead of reveal-only - add a SecretsView regression test covering reveal, hide, and copy button state changes Files changed: packages/dashboard/app/components/SecretsView.tsx | 18 +++++++++- packages/dashboard/app/components/__tests__/SecretsView.test.tsx | 40 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-5878 Fusion-Task-Lineage: 936195db-88c9-48f5-8273-9b930bf6b858
This commit is contained in:
@@ -222,12 +222,22 @@ export const SecretsView = ({ addToast }: SecretsViewProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const hideSecret = (secret: SecretRecord) => {
|
||||
const existing = revealTimersRef.current.get(secret.id);
|
||||
if (existing) {
|
||||
clearTimeout(existing);
|
||||
revealTimersRef.current.delete(secret.id);
|
||||
}
|
||||
setRevealedValues((current) => ({ ...current, [secret.id]: null }));
|
||||
};
|
||||
|
||||
const revealSecret = async (secret: SecretRecord) => {
|
||||
const data = await request<{ key: string; value: string }>(`/api/secrets/${secret.scope}/${secret.id}/reveal`, { method: "POST" });
|
||||
setRevealedValues((current) => ({ ...current, [secret.id]: data.value }));
|
||||
addToast?.("Revealed", "success");
|
||||
const timer = setTimeout(() => {
|
||||
setRevealedValues((current) => ({ ...current, [secret.id]: null }));
|
||||
revealTimersRef.current.delete(secret.id);
|
||||
}, 30000);
|
||||
const existing = revealTimersRef.current.get(secret.id);
|
||||
if (existing) clearTimeout(existing);
|
||||
@@ -314,7 +324,13 @@ export const SecretsView = ({ addToast }: SecretsViewProps) => {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon secrets-visibility-toggle"
|
||||
onClick={() => void revealSecret(secret)}
|
||||
onClick={() => {
|
||||
if (revealed) {
|
||||
hideSecret(secret);
|
||||
return;
|
||||
}
|
||||
void revealSecret(secret);
|
||||
}}
|
||||
aria-label={revealed ? "Hide" : "Reveal"}
|
||||
>
|
||||
{revealed ? <EyeOff {...actionIconProps} /> : <Eye {...actionIconProps} />}
|
||||
|
||||
@@ -225,6 +225,46 @@ describe("SecretsView", () => {
|
||||
expectVisibleActionIcon(screen.getByRole("button", { name: "Delete" }));
|
||||
});
|
||||
|
||||
it("revealed secret can be hidden again from the row toggle", async () => {
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
mockJsonResponse({
|
||||
ok: true,
|
||||
body: {
|
||||
secrets: [
|
||||
{ id: "secret-1", key: "VISIBLE", scope: "project", description: null, accessPolicy: "prompt", envExportable: false, envExportKey: null, lastReadAt: null },
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
.mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { configured: false } }))
|
||||
.mockResolvedValueOnce(mockJsonResponse({ ok: true, body: { key: "VISIBLE", value: "super-secret-value" } }));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
render(<SecretsView addToast={vi.fn()} />);
|
||||
await screen.findByText("VISIBLE");
|
||||
|
||||
const revealButton = screen.getByRole("button", { name: "Reveal" });
|
||||
const copyButton = screen.getByRole("button", { name: "Copy" });
|
||||
expect(copyButton).toBeDisabled();
|
||||
|
||||
await userEvent.click(revealButton);
|
||||
|
||||
expect(await screen.findByText("super-secret-value")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Hide" })).toBeInTheDocument();
|
||||
expect(copyButton).toBeEnabled();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Hide" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText("super-secret-value")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByRole("button", { name: "Reveal" })).toBeInTheDocument();
|
||||
expect(copyButton).toBeDisabled();
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it.each(["dark", "light"] as const)("keeps the modal value toggle icon visible in %s theme", async (theme) => {
|
||||
document.documentElement.dataset.theme = theme;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user