@@ -73,6 +73,11 @@ const NAV_SECTIONS: { group: NavGroup; items: typeof WORKSPACES }[] = (
7373 [ "START" , "OPERATE" , "INSPECT" , "SETUP" ] as NavGroup [ ]
7474) . map ( ( group ) => ( { group, items : WORKSPACES . filter ( ( w ) => w . group === group ) } ) ) ;
7575
76+ // Pass 5 A1: the chart workspaces whose empty state pulls a live DMV bundle in
77+ // place (vs. routing to CONN). Entering one with a connection + null dmv triggers
78+ // an auto-pull. PLAN/SEV are SQL/plan-driven, not DMV-driven, so they're absent.
79+ const DMV_CHART_WORKSPACES : Workspace [ ] = [ "indexes" , "sizes" ] ;
80+
7681export function App ( ) {
7782 // ── Persistent state ────────────────────────────────
7883 const [ ui , setUi ] = useState < P . UiPrefs > ( ( ) => ( {
@@ -114,6 +119,13 @@ export function App() {
114119
115120 // ── Runtime state ───────────────────────────────────
116121 const [ dmv , setDmv ] = useState < unknown > ( null ) ;
122+ // Pass 5 A1: in-workspace DMV pull. The chart workspaces (PLAN/INDEX/SIZE/SEV)
123+ // used to bounce the user to CONN; instead they pull the DMV bundle IN PLACE.
124+ // `dmvLoading` drives the empty-state "Pulling DMVs…" spinner; `dmvErr` shows
125+ // an inline retry (NOT a redirect). pullDmvInline() sets the bundle and the
126+ // existing analyzer effect (keyed on `dmv`) regenerates report.charts.
127+ const [ dmvLoading , setDmvLoading ] = useState ( false ) ;
128+ const [ dmvErr , setDmvErr ] = useState < string | null > ( null ) ;
117129 const [ report , setReport ] = useState < AnalysisReport | null > ( null ) ;
118130 const [ analyzing , setAnalyzing ] = useState ( false ) ;
119131 const [ backendOk , setBackendOk ] = useState < boolean | null > ( null ) ;
@@ -152,6 +164,15 @@ export function App() {
152164 P . save ( "conn" , toStore ) ;
153165 } , [ conn ] ) ;
154166
167+ // A new server·db is a new DMV scope: drop any prior bundle + inline error so
168+ // a chart never shows another server's telemetry and the auto-pull can re-fire.
169+ useEffect ( ( ) => {
170+ setDmv ( null ) ;
171+ setDmvErr ( null ) ;
172+ // Only on identity change of the active server/database.
173+ // eslint-disable-next-line react-hooks/exhaustive-deps
174+ } , [ conn . server , conn . database ] ) ;
175+
155176 // Reflect edits to the active connection back into its saved profile, and
156177 // persist the profile list. Keeps the active server in sync as it's tweaked.
157178 useEffect ( ( ) => {
@@ -284,6 +305,51 @@ export function App() {
284305 reader . readAsText ( file ) ;
285306 }
286307
308+ // ── In-workspace DMV pull (Pass 5 A1) ───────────────
309+ // Pull the DMV bundle IN PLACE from a chart workspace. Mirrors the CONN-path
310+ // pull but keeps the user where they are: on success setDmv(bundle) → the
311+ // analyzer effect (keyed on dmv) rebuilds report.charts; on failure we surface
312+ // an inline error so the chart's empty state can offer Retry. Returns nothing;
313+ // callers just trigger it. A live `dmvLoading` guard prevents double-pulls.
314+ async function pullDmvInline ( ) {
315+ if ( ! conn . server || dmvLoading ) return ;
316+ setDmvLoading ( true ) ;
317+ setDmvErr ( null ) ;
318+ try {
319+ const info = {
320+ server : conn . server ,
321+ database : conn . database || undefined ,
322+ user : conn . auth_mode === "sql" ? conn . user : undefined ,
323+ password : conn . auth_mode === "sql" ? conn . password : undefined ,
324+ trust_cert : conn . trust_cert ,
325+ } ;
326+ const bundle = await backend . pullDmv ( info as any ) ;
327+ setDmv ( bundle ) ;
328+ } catch ( e : any ) {
329+ setDmvErr ( e ?. message ?? String ( e ) ) ;
330+ } finally {
331+ setDmvLoading ( false ) ;
332+ }
333+ }
334+
335+ // Auto-pull on first entry to a chart workspace when connected and dmv is null.
336+ // Keyed on the workspace + connection + whether dmv exists; the dmvLoading /
337+ // dmvErr guards (inside pullDmvInline and here) stop it from looping or
338+ // hammering after a failure (the user retries explicitly via the inline button).
339+ useEffect ( ( ) => {
340+ if (
341+ DMV_CHART_WORKSPACES . includes ( ui . workspace ) &&
342+ ! ! conn . server &&
343+ dmv == null &&
344+ ! dmvLoading &&
345+ ! dmvErr
346+ ) {
347+ void pullDmvInline ( ) ;
348+ }
349+ // pullDmvInline is stable enough for this guard set; deps are the real inputs.
350+ // eslint-disable-next-line react-hooks/exhaustive-deps
351+ } , [ ui . workspace , conn . server , dmv , dmvLoading , dmvErr ] ) ;
352+
287353 const [ explainBusy , setExplainBusy ] = useState ( false ) ;
288354 const [ explainErr , setExplainErr ] = useState < string | null > ( null ) ;
289355
@@ -365,7 +431,13 @@ export function App() {
365431 </ span >
366432 ) }
367433 </ div >
368- { dmv ? (
434+ { dmvLoading ? (
435+ < div className = "group" >
436+ < span className = "dot busy" />
437+ < span className = "k" > dmv</ span >
438+ < span className = "v" > PULLING…</ span >
439+ </ div >
440+ ) : dmv ? (
369441 < div className = "group" >
370442 < span className = "dot ok" />
371443 < span className = "k" > dmv</ span >
@@ -515,6 +587,8 @@ export function App() {
515587 data = { report ?. charts . plan_treemap ?? [ ] }
516588 theme = { ui . theme }
517589 action = { { label : "Generate from SQL" , onClick : ( ) => setUi ( { ...ui , workspace : "analyze" } ) } }
590+ loading = { dmvLoading }
591+ error = { dmvErr }
518592 />
519593 </ ChartContainer >
520594 </ Workspace >
@@ -526,7 +600,13 @@ export function App() {
526600 < IndexHeatmap
527601 data = { report ?. charts . index_heatmap ?? [ ] }
528602 theme = { ui . theme }
529- action = { { label : "Connect & pull DMVs" , onClick : ( ) => setUi ( { ...ui , workspace : "connection" } ) } }
603+ action = {
604+ conn . server
605+ ? { label : "Pull now" , onClick : ( ) => void pullDmvInline ( ) }
606+ : { label : "Connect & pull DMVs" , onClick : ( ) => setUi ( { ...ui , workspace : "connection" } ) }
607+ }
608+ loading = { dmvLoading }
609+ error = { dmvErr }
530610 />
531611 </ ChartContainer >
532612 </ Workspace >
@@ -538,7 +618,13 @@ export function App() {
538618 < SizeTreemap
539619 data = { report ?. charts . size_treemap ?? [ ] }
540620 theme = { ui . theme }
541- action = { { label : "Connect & pull DMVs" , onClick : ( ) => setUi ( { ...ui , workspace : "connection" } ) } }
621+ action = {
622+ conn . server
623+ ? { label : "Pull now" , onClick : ( ) => void pullDmvInline ( ) }
624+ : { label : "Connect & pull DMVs" , onClick : ( ) => setUi ( { ...ui , workspace : "connection" } ) }
625+ }
626+ loading = { dmvLoading }
627+ error = { dmvErr }
542628 />
543629 </ ChartContainer >
544630 </ Workspace >
@@ -551,6 +637,8 @@ export function App() {
551637 data = { report ?. charts . severity_timeline ?? [ ] }
552638 theme = { ui . theme }
553639 action = { { label : "Paste T-SQL to analyze" , onClick : ( ) => setUi ( { ...ui , workspace : "analyze" } ) } }
640+ loading = { dmvLoading }
641+ error = { dmvErr }
554642 />
555643 </ ChartContainer >
556644 </ Workspace >
0 commit comments