mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 08:01:35 +00:00
fix(frontend): make the import counters answer what they claim to
`template_abandon` folded a landed import closed with the X into `idle`, the bucket read as "opened this and bounced" — three outcomes in one number. It gets its own `done` stage. `template_setup` counted skipped rows through `value`, which is an increment: `skipped` accumulated rows while `filled` and `none` counted imports, two units in one counter with no way to recover one from the other. The row count becomes a bucket in the key, so every event is one import and the buckets compare. The import dialog also asked for the hub URL settings at init, and the home list now mounts it for everyone on every arrival — two GETs for a string only the project card renders. Deferred to the first pick. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9
This commit is contained in:
co-authored by
Claude Opus 5
parent
7812095c8d
commit
f14e60cc38
@@ -57,9 +57,17 @@
|
||||
function dismiss() {
|
||||
// Where they left, which is the half of the funnel Finish cannot report: `running`
|
||||
// walked out on an import in progress, `setup` on the credentials it asked for,
|
||||
// `idle` opened the dialog and picked nothing up.
|
||||
// `done` closed a landed import instead of pressing Finish, `idle` opened the dialog
|
||||
// and picked nothing up. `done` is its own bucket because it is a normal exit — folded
|
||||
// into `idle` it would read as people bouncing off a dialog they never used.
|
||||
if (!finishing) {
|
||||
const stage = execution?.running ? 'running' : onSetupStep ? 'setup' : 'idle'
|
||||
const stage: AbandonStage = execution?.running
|
||||
? 'running'
|
||||
: onSetupStep
|
||||
? 'setup'
|
||||
: execution?.done
|
||||
? 'done'
|
||||
: 'idle'
|
||||
logFeatureUsage('home', 'template_abandon', { key: stage })
|
||||
}
|
||||
if (execution?.running) {
|
||||
@@ -117,10 +125,17 @@
|
||||
: undefined)
|
||||
)
|
||||
|
||||
// Asked for on the first pick, not at init: this dialog is mounted by the home list for
|
||||
// every user on every arrival, and the host is a string only the project card renders.
|
||||
let hubHost = $state('hub.windmill.dev')
|
||||
void hubBrowserUrl()
|
||||
.then((u) => (hubHost = new URL(u).host))
|
||||
.catch(() => {})
|
||||
let hubHostAsked = false
|
||||
$effect(() => {
|
||||
if (!slug || hubHostAsked) return
|
||||
hubHostAsked = true
|
||||
void hubBrowserUrl()
|
||||
.then((u) => (hubHost = new URL(u).host))
|
||||
.catch(() => {})
|
||||
})
|
||||
|
||||
// Each open is its own import: a dialog reopened for another project must not inherit
|
||||
// the previous run, or its step would offer to resume a bundle from a different slug.
|
||||
@@ -132,21 +147,37 @@
|
||||
}
|
||||
})
|
||||
|
||||
// The counters' key vocabularies, enumerated here so the whole set is reviewable at once.
|
||||
type AbandonStage = 'running' | 'setup' | 'done' | 'idle'
|
||||
type SetupOutcome = 'filled' | 'skipped' | 'none'
|
||||
type SetupBucket = 'filled' | 'none' | 'skipped_1' | 'skipped_2_5' | 'skipped_6plus'
|
||||
|
||||
// Set for the closing that Finish itself asks for, since that closing reaches `dismiss()`
|
||||
// by the same falling edge as the X.
|
||||
let finishing = false
|
||||
|
||||
/**
|
||||
* How the credentials step ended, counted alongside the import itself: `filled` only when
|
||||
* nothing was left outstanding — the step disables Finish until then — `skipped` carrying
|
||||
* how many rows were walked away from, and `none` where the project asked for nothing.
|
||||
* Skipping with one credential left and skipping with eight are different problems.
|
||||
* nothing was left outstanding — the step disables Finish until then — `none` where the
|
||||
* project asked for nothing, and a `skipped_*` bucket carrying roughly how many rows were
|
||||
* walked away from, since skipping with one credential left and skipping with eight are
|
||||
* different problems.
|
||||
*
|
||||
* A bucket rather than `value`: `value` is an increment, so counting rows there would make
|
||||
* `skipped` a sum of rows while its siblings count imports — two units in one counter, and
|
||||
* no way to recover filled-versus-skipped.
|
||||
*/
|
||||
function finish(setupOutcome: 'filled' | 'skipped' | 'none', outstanding = 1) {
|
||||
function setupKey(outcome: SetupOutcome, outstanding: number): SetupBucket {
|
||||
if (outcome !== 'skipped') return outcome
|
||||
if (outstanding <= 1) return 'skipped_1'
|
||||
return outstanding <= 5 ? 'skipped_2_5' : 'skipped_6plus'
|
||||
}
|
||||
|
||||
function finish(setupOutcome: SetupOutcome, outstanding = 1) {
|
||||
// On the way out rather than on the pick: what is worth counting is an import that
|
||||
// landed, not a dialog that was opened and abandoned.
|
||||
if (slug) logFeatureUsage('home', 'template_import', { key: slug })
|
||||
logFeatureUsage('home', 'template_setup', { key: setupOutcome, value: outstanding })
|
||||
logFeatureUsage('home', 'template_setup', { key: setupKey(setupOutcome, outstanding) })
|
||||
finishing = true
|
||||
onImported?.()
|
||||
onClose()
|
||||
|
||||
@@ -975,15 +975,13 @@
|
||||
// until the first response instead — it is fetched in parallel with the listing,
|
||||
// so it costs no extra wait in practice. Only the first load gates: `current`
|
||||
// survives a refetch, so an in-place scope change refreshes without flashing.
|
||||
let treeCountsPending = $derived(
|
||||
treeLazyMode && ownerCountsRes.current == undefined && ownerCountsRes.loading
|
||||
)
|
||||
|
||||
// An import just landed, so the rows about to replace the empty state are all new: they
|
||||
// fade in one after another rather than appearing as a finished list. Cleared on a timer
|
||||
// because nothing else marks the end — the reload resolves before the rows animate.
|
||||
// The hub import, owned here rather than by either entry point: the empty state's link and
|
||||
// the create menu's Import section open the same dialog, and mounting one per entry point
|
||||
// would put two of them on the page at once while the workspace is still empty.
|
||||
let hubPick = $state<HubProjectPick | undefined>(undefined)
|
||||
let hubPickerOpen = $state(false)
|
||||
|
||||
let justImported = $state(false)
|
||||
let justImportedTimer: ReturnType<typeof setTimeout> | undefined
|
||||
function onImported() {
|
||||
@@ -993,9 +991,11 @@
|
||||
justImportedTimer = setTimeout(() => (justImported = false), 2500)
|
||||
}
|
||||
|
||||
let treeCountsPending = $derived(
|
||||
treeLazyMode && ownerCountsRes.current == undefined && ownerCountsRes.loading
|
||||
)
|
||||
// The hub import, owned here rather than by either entry point: the empty state's link and
|
||||
// the create menu's Import section open the same dialog, and mounting one per entry point
|
||||
// would put two of them on the page at once while the workspace is still empty.
|
||||
let hubPick = $state<HubProjectPick | undefined>(undefined)
|
||||
let hubPickerOpen = $state(false)
|
||||
|
||||
// The workspace itself holds nothing — no filter is narrowing the list away. It stays
|
||||
// false until the first load resolves: a skeleton already means "loading", and the
|
||||
|
||||
@@ -240,7 +240,8 @@
|
||||
>
|
||||
{#snippet subtitleSnippet()}
|
||||
<!-- The way out belongs on the line that says who you are, not in a footer as the page's
|
||||
accent action: leaving is not what anyone came here to do. -->
|
||||
accent action: leaving is not what anyone came here to do. Shown in both states; the
|
||||
picker also carries it in the settings menu below, which the create state hides. -->
|
||||
<span class="text-xs text-tertiary">
|
||||
Logged in as <span class="text-secondary">{$usersWorkspaceStore?.email}</span>
|
||||
·
|
||||
@@ -481,8 +482,8 @@
|
||||
{/if}
|
||||
|
||||
<!-- Settings are for someone who lives here; a user with no workspace yet has one thing
|
||||
to do. Logging out rides in this menu, and on the subtitle line above for the create
|
||||
state, which has no menu. -->
|
||||
to do, so this row stands down for the create state. Logging out is in this menu as
|
||||
well as on the subtitle line, which is the only one of the two the create state has. -->
|
||||
{#if !showCreate}
|
||||
<div class="flex items-center mt-10 flex-wrap gap-2">
|
||||
{#if $superadmin}
|
||||
|
||||
Reference in New Issue
Block a user