fix(ui): show Registry subscription and setup prompts (#13038)

This commit is contained in:
Alejandro Bailo authored and GitHub committed 2026-10-14 13:51:08 +02:00
1 parent 286ae6c7cf
commit 64c19b3db6
23 files changed
+571 -73

No files matched your search

+7 -3
View File
@@ -190,6 +190,9 @@ export async function classifyRegistryFailure(
endpoint: RegistryEndpoint,
credentialStatus: RegistryCredentialStatus | null,
): Promise<RegistryFailureResult> {
if (response.status === 402) {
return { status: REGISTRY_FAILURE.SUBSCRIPTION_REQUIRED };
}
if (response.status === 401 || response.status === 403) {
return { status: REGISTRY_FAILURE.ACCESS_DENIED };
}
@@ -198,15 +201,16 @@ export async function classifyRegistryFailure(
return { status: REGISTRY_FAILURE.ERROR };
}
const code = await getRegistryErrorCode(response);
if (
response.status === 409 &&
credentialStatus !== null &&
!isActiveRegistryCredential(credentialStatus)
(code === "registry_credential_not_configured" ||
(credentialStatus !== null &&
!isActiveRegistryCredential(credentialStatus)))
) {
return { status: REGISTRY_FAILURE.ONBOARDING };
}
const code = await getRegistryErrorCode(response);
if (response.status === 502 && code === REGISTRY_ERROR_CODE.KEY_REJECTED) {
return { status: REGISTRY_FAILURE.RECONNECT };
}
+39
View File
@@ -115,6 +115,45 @@ function mockRequestDeadlines() {
}
describe("installed Registry provider discovery", () => {
it("reports a missing Registry key as setup rather than a connection failure", async () => {
// Given
mockDiscovery({
failedEndpoint: "providers",
failureStatus: 409,
failureBody: { errors: [{ code: "registry_credential_not_configured" }] },
});
// When / Then
expect(await getInstalledRegistryProviderOptions()).toEqual({
status: "onboarding",
});
});
it("shows the paywall when bootstrapping Registry requires a subscription", async () => {
// Given
fetchMock.mockResolvedValueOnce(jsonResponse({}, 402));
// When / Then
expect(await getRegistryBootstrap()).toEqual({
status: "subscription_required",
});
expect(fetchMock).toHaveBeenCalledOnce();
});
it.each(["available-artifacts", "artifacts", "providers"])(
"offers a paid plan when the %s read requires a subscription",
async (failedEndpoint) => {
// Given
mockDiscovery({ failedEndpoint, failureStatus: 402 });
// When
const result = await getInstalledRegistryProviderOptions();
// Then
expect(result).toEqual({ status: "subscription_required" });
},
);
function mockDiscovery({
emptyMetadata = false,
failedEndpoint,
+24 -4
View File
@@ -138,6 +138,8 @@ async function classifyDiscoveryFailure(
credential: RegistryCredentialStatus | null,
) {
if (response.status === 409 && credential === null) {
const failure = await classifyRegistryFailure(response, endpoint, null);
if (failure.status === REGISTRY_FAILURE.ONBOARDING) return failure;
const currentCredential = await readRegistryCredential(accessToken);
if (currentCredential.status === REGISTRY_FAILURE.ACCESS_DENIED) {
return currentCredential;
@@ -211,6 +213,18 @@ export async function getInstalledRegistryProviderOptions(): Promise<RegistryPro
)
)
return { status: REGISTRY_PROVIDER_DISCOVERY.ACCESS_DENIED };
if (
[catalog.status, installed.status, providers.status].some(
(status) => status === REGISTRY_FAILURE.SUBSCRIPTION_REQUIRED,
)
)
return { status: REGISTRY_PROVIDER_DISCOVERY.SUBSCRIPTION_REQUIRED };
if (
[catalog.status, providers.status].some(
(status) => status === REGISTRY_FAILURE.ONBOARDING,
)
)
return { status: REGISTRY_PROVIDER_DISCOVERY.ONBOARDING };
if (
catalog.status !== REGISTRY_CATALOG.COMPLETE ||
installed.status !== "ready" ||
@@ -303,8 +317,11 @@ function bootstrapReady(
function bootstrapFailure(
failure: RegistryFailureResult,
): RegistryBootstrapResult {
if (failure.status === REGISTRY_FAILURE.ACCESS_DENIED) {
return { status: REGISTRY_FAILURE.ACCESS_DENIED };
if (
failure.status === REGISTRY_FAILURE.ACCESS_DENIED ||
failure.status === REGISTRY_FAILURE.SUBSCRIPTION_REQUIRED
) {
return { status: failure.status };
}
return bootstrapReady({
status:
@@ -340,8 +357,11 @@ export async function getRegistryBootstrap(): Promise<RegistryBootstrapResult> {
}
const catalog = await readCompleteRegistryCatalog(access, credential);
if (catalog.status === REGISTRY_FAILURE.ACCESS_DENIED) {
return { status: REGISTRY_FAILURE.ACCESS_DENIED };
if (
catalog.status === REGISTRY_FAILURE.ACCESS_DENIED ||
catalog.status === REGISTRY_FAILURE.SUBSCRIPTION_REQUIRED
) {
return { status: catalog.status };
}
if (catalog.status === REGISTRY_CATALOG.INCOMPLETE) {
return bootstrapReady({