mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 08:03:50 +00:00
fix(frontend): tag each hub detail with the slug that asked for it
`resource()` assigns whatever its fetcher returns, with no guard for a run that has been superseded, so handing back the previously fetched project on a stale response published that project. With two slow requests in flight — pick A, leave B loading, pick C — B's answer put A's name, author and counts on the card while the plan underneath still said C, and Import wrote C. Each answer now carries its own slug and is read only while that slug is the chosen one, which also drops the local the previous shape needed to keep the resource's type from going circular. The hub-telemetry tests reset their shared fixture per case; the private-hub one had been passing on what the case above it left behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG
This commit is contained in:
co-authored by
Claude Opus 5
parent
63b8d9757b
commit
ac6295722a
@@ -141,24 +141,21 @@
|
||||
// card renders from the pick until it lands — counts are the only thing missing, and
|
||||
// zero counts render as no badges rather than as zeroes.
|
||||
//
|
||||
// The answer is checked against the slug that asked for it: `resource()` aborts the
|
||||
// previous controller but `fetchHubProject` takes no signal, and nothing orders the
|
||||
// responses — so picking A, dismissing, then picking B can land A's name, author and
|
||||
// counts over an import that writes B.
|
||||
// Kept in a local rather than read back off `detail.current` inside `detail`'s own fetcher,
|
||||
// which makes the resource's type circular and resolves it to `any`.
|
||||
let lastDetail = $state<ImportProjectSummary | undefined>(undefined)
|
||||
// Each answer carries the slug that asked for it, and is read only while that slug is
|
||||
// still the chosen one: `resource()` aborts the previous controller but `fetchHubProject`
|
||||
// takes no signal, and nothing orders the responses — so picking A, dismissing, then
|
||||
// picking B can land A's name, author and counts over an import that writes B. Tagging
|
||||
// rather than substituting, because whatever the fetcher returns is published: handing
|
||||
// back the previous project on a superseded response is how it reaches the card.
|
||||
const detail = resource(
|
||||
() => slug,
|
||||
async (s) => {
|
||||
if (!s) return undefined
|
||||
const fetched = await fetchHubProject(s)
|
||||
if (s === slug) lastDetail = fetched
|
||||
return lastDetail
|
||||
}
|
||||
async (s) => (s ? { slug: s, project: await fetchHubProject(s) } : undefined)
|
||||
)
|
||||
let fetchedDetail = $derived<ImportProjectSummary | undefined>(
|
||||
detail.current?.slug === slug ? detail.current?.project : undefined
|
||||
)
|
||||
let project = $derived<ImportProjectSummary | undefined>(
|
||||
detail.current ??
|
||||
fetchedDetail ??
|
||||
(pick
|
||||
? {
|
||||
slug: pick.slug,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('$lib/gen', () => ({ OpenAPI: { BASE: '/api' } }))
|
||||
|
||||
@@ -133,6 +133,13 @@ describe('hubScriptUsageKey', () => {
|
||||
})
|
||||
|
||||
describe('hubProjectUsageKey', () => {
|
||||
// The fixture is module-level and mutable, so each case states the world it needs rather
|
||||
// than inheriting whatever the case above it left behind.
|
||||
beforeEach(() => {
|
||||
hubBaseUrl.url.set('https://hub.windmill.dev')
|
||||
hubBaseUrl.known.set(true)
|
||||
})
|
||||
|
||||
it('reports the slug for every spelling of the public hub', () => {
|
||||
for (const hub of [
|
||||
'https://hub.windmill.dev',
|
||||
@@ -150,7 +157,6 @@ describe('hubProjectUsageKey', () => {
|
||||
it('answers private until the instance has said which hub it points at', () => {
|
||||
// The store is seeded with the public hub, so a settings read that failed must not
|
||||
// read as permission to report the name.
|
||||
hubBaseUrl.url.set('https://hub.windmill.dev')
|
||||
hubBaseUrl.known.set(false)
|
||||
expect(hubProjectUsageKey('acme-payroll')).toBe('private')
|
||||
hubBaseUrl.known.set(true)
|
||||
|
||||
Reference in New Issue
Block a user