feat(serenity): expose semrush provisioning status on brand reads (LLMO-7352/LLMO-7418) - #3250
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
a048d64 to
c7c0239
Compare
|
Rebased onto Phase 4's (#3249) fix commit — no changes needed in this PR itself, the fixed |
|
This PR will trigger a minor release when merged. |
4d3123e to
a964882
Compare
c7c0239 to
2be9230
Compare
135eec6 to
e888c2a
Compare
2be9230 to
581d363
Compare
e888c2a to
f822111
Compare
581d363 to
feb52c7
Compare
f822111 to
a587be3
Compare
feb52c7 to
60e70ad
Compare
60e70ad to
24911fc
Compare
24911fc to
d8befb8
Compare
b0a89f6 to
90986fd
Compare
d8befb8 to
d4706fd
Compare
90986fd to
f77926a
Compare
d4706fd to
fdc0a01
Compare
fdc0a01 to
8473538
Compare
af1cbe8 to
fe80d7d
Compare
8473538 to
3925628
Compare
3925628 to
7fc9a1f
Compare
7fc9a1f to
ba32f7d
Compare
6d2220d to
e797823
Compare
ba32f7d to
3b99f06
Compare
e797823 to
78019c4
Compare
3b99f06 to
ab359b2
Compare
78019c4 to
b93aa8b
Compare
ab359b2 to
555eacc
Compare
luis6156
left a comment
There was a problem hiding this comment.
Clean, minimal, correct. The single mapping point (mapDbBrandToV2) covers all three GET paths simultaneously, no SELECT changes needed since PostgREST's '*' already returns every column, and the "Null unless semrushProvisioningStatus is failed" comment is a DB-level guarantee (confirmed: beginProvisioningAttempt explicitly writes semrush_provisioning_error: null in its UPDATE, so a successful retry atomically clears any prior error). Two small items:
Finding 1 — Test coverage missing pending and ready states
The test exercises failed-with-error and null (never provisioned), but the two states the frontend UI actually renders differently are pending (show "Setting up..." spinner) and ready (silent success). Adding cases for both would ensure a future rename of an enum value or a mapping typo doesn't go undetected:
it('maps semrush_provisioning_status: pending and ready', async () => {
for (const status of ['pending', 'ready']) {
const row = makeBrandRow({ semrush_provisioning_status: status });
const query = createChainableQuery({ data: row, error: null });
const result = await getBrandById(ORG_ID, BRAND_ID, { from: sinon.stub().returns(query) });
expect(result.semrushProvisioningStatus).to.equal(status);
expect(result.semrushProvisioningError).to.equal(null); // error only set on failed
}
});Finding 2 — OpenAPI description doesn't explain the null / ready distinction for clients
The semrushProvisioningStatus description says "show a persistent Setting-up/Ready/Setup-failed indicator without relying on … jobId" but doesn't tell a reader what each value means from the client side. The null vs ready split is particularly subtle: both mean "workspace is usable", but null indicates a brand that went through the sync path (or predates async provisioning) while ready indicates an async-provisioned brand. A frontend developer implementing LLMO-7419 should not need to read the provisioning worker source to know this.
Suggested description:
semrushProvisioningStatus:
description: >-
Read-only: the brand's async Semrush provisioning state.
`pending` — a provisioning attempt is in flight; the client should
show a "Setting up" indicator and may poll the job status via `jobId`.
`ready` — async provisioning completed successfully.
`failed` — the last attempt failed; see `semrushProvisioningError` for detail.
`null` — the brand was created via the sync path or before async
provisioning was introduced; no active provisioning issue.
For display purposes, treat both `null` and `ready` as "workspace available".The four-state breakdown also confirms the OpenAPI enum [pending, ready, failed, null] is complete.
b93aa8b to
a688e78
Compare
…e for clients Luis's review on #3250, both findings. The DTO mapping test covered only `failed`-with-error and absent. The two states the frontend actually branches on — `pending`, which drives the "Setting up" indicator, and `ready`, the silent-success case — were untested, so a renamed enum value or a mapping typo in either would have shipped undetected. The OpenAPI description said what the field is FOR but not what each value means to a client. The `null` vs `ready` split is the subtle one: both mean the workspace is available, but `null` is a brand created through the synchronous path or predating async provisioning, while `ready` is an async-provisioned one. A frontend developer should not have to read the provisioning worker to learn that. Now documents all four values and states plainly that `null` and `ready` are equivalent for display. eslint, type-check and docs:lint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
555eacc to
397b59b
Compare
a688e78 to
59dbd25
Compare
…e for clients Luis's review on #3250, both findings. The DTO mapping test covered only `failed`-with-error and absent. The two states the frontend actually branches on — `pending`, which drives the "Setting up" indicator, and `ready`, the silent-success case — were untested, so a renamed enum value or a mapping typo in either would have shipped undetected. The OpenAPI description said what the field is FOR but not what each value means to a client. The `null` vs `ready` split is the subtle one: both mean the workspace is available, but `null` is a brand created through the synchronous path or predating async provisioning, while `ready` is an async-provisioned one. A frontend developer should not have to read the provisioning worker to learn that. Now documents all four values and states plainly that `null` and `ready` are equivalent for display. eslint, type-check and docs:lint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
397b59b to
574d9eb
Compare
luis6156
left a comment
There was a problem hiding this comment.
Review — feat(serenity): expose semrush provisioning status on brand reads
Clean and focused. The single-mapping-point strategy (mapDbBrandToV2 covers all three GET paths and the write-response path via withSerenityState) is correct, and no SELECT/query changes are needed since PostgREST's '*' already returns every column.
null vs ready semantics
The OpenAPI description correctly documents the distinction: null = synchronous create or predates async provisioning; ready = async-provisioned workspace confirmed created. Worth confirming with the frontend that they treat both as "workspace available" — the description now states this plainly ("clients should treat null and ready as equivalent for display"), but the UI rendering code is the authoritative check.
Non-Semrush / flat-mode brands
semrushProvisioningStatus will always be null for flat-mode organizations since the provisioning columns are never written for them. The OpenAPI schema marks the field nullable and not required, which is correct. One thing worth noting for frontend consumers: null can mean either "synchronous Semrush brand" or "non-Semrush brand" — a flat-mode client should treat null as "not applicable" rather than "provisioning status unknown." The current description says null and ready are equivalent for display, which is accurate for the Semrush case but could mislead a flat-mode consumer into polling for a status that will never change. A clarifying note ("for non-Semrush organizations this field is always null") in the schema description would close that ambiguity.
Non-blocking. Approving.
59dbd25 to
79e847f
Compare
…e for clients Luis's review on #3250, both findings. The DTO mapping test covered only `failed`-with-error and absent. The two states the frontend actually branches on — `pending`, which drives the "Setting up" indicator, and `ready`, the silent-success case — were untested, so a renamed enum value or a mapping typo in either would have shipped undetected. The OpenAPI description said what the field is FOR but not what each value means to a client. The `null` vs `ready` split is the subtle one: both mean the workspace is available, but `null` is a brand created through the synchronous path or predating async provisioning, while `ready` is an async-provisioned one. A frontend developer should not have to read the provisioning worker to learn that. Now documents all four values and states plainly that `null` and `ready` are equivalent for display. eslint, type-check and docs:lint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
574d9eb to
8cf602c
Compare
79e847f to
f7227b1
Compare
…MO-7352/LLMO-7418) Surfaces brands.semrush_provisioning_status/semrush_provisioning_error as read-only fields (semrushProvisioningStatus/semrushProvisioningError) on the V2 Brand DTO. These columns previously existed only for the async provisioning worker's own internal CAS bookkeeping (getBrandProvisioningState's narrow PROVISIONING_SELECT) -- no GET response exposed them, so a client had no way to show a persistent provisioning-status indicator without relying on an `async: true` create/activate response's one-shot jobId. mapDbBrandToV2 is the single mapping point for all 3 GET paths (listBrandsForOrg/getBrandForOrg/getBrandForOrgSite) and the create/update write-response paths (all route through withSerenityState, which just spreads whatever the mapper produced) -- fixing it there propagates everywhere for free, no SELECT/query changes needed since PostgREST's `'*'` already returns every brands column. Prerequisite for the frontend UI-states work (LLMO-7419): a persistent Setting-up/Ready/Setup-failed indicator on the brand list/detail views can't exist without a backend field to read it from. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e for clients Luis's review on #3250, both findings. The DTO mapping test covered only `failed`-with-error and absent. The two states the frontend actually branches on — `pending`, which drives the "Setting up" indicator, and `ready`, the silent-success case — were untested, so a renamed enum value or a mapping typo in either would have shipped undetected. The OpenAPI description said what the field is FOR but not what each value means to a client. The `null` vs `ready` split is the subtle one: both mean the workspace is available, but `null` is a brand created through the synchronous path or predating async provisioning, while `ready` is an async-provisioned one. A frontend developer should not have to read the provisioning worker to learn that. Now documents all four values and states plainly that `null` and `ready` are equivalent for display. eslint, type-check and docs:lint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8cf602c to
bcb52cf
Compare
Summary
Fifth and last in the stack (base: #3249). Small, focused prerequisite for the frontend UI-states work (LLMO-7419, adobe/project-elmo-ui#3128): surfaces
brands.semrush_provisioning_status/semrush_provisioning_erroras read-onlysemrushProvisioningStatus/semrushProvisioningErroron the V2 Brand DTO.Why this PR exists: those two columns previously existed only for the async provisioning worker's own internal CAS bookkeeping (
getBrandProvisioningState's narrowPROVISIONING_SELECT) — noGETresponse exposed them. Without this the frontend cannot show a persistent "Setting up / Ready / Setup failed" indicator on the brand list or detail view; it would have to rely on the one-shotjobIdanasync: trueresponse carries, which is useless on any later page load.Changes
mapDbBrandToV2(src/support/brands-storage.js) — the single mapping point for all 3 GET paths (listBrandsForOrg/getBrandForOrg/getBrandForOrgSite) and the create/update write-response paths (all route throughwithSerenityState) — now copies the two columns onto the DTO. NoSELECTchanges needed: PostgREST's'*'already returns everybrandscolumn.V2Brandindocs/openapi/schemas.yaml— both fields added asreadOnly, nullable, not inrequired.Test plan
npm run lint,npm run type-check,npm run docs:lint— cleanfailed-with-error case and the null/never-provisioned casemainRelated Issues
🤖 Generated with Claude Code