Skip to content

App: the Executive summary opens with 8 queries rather than 18 (cut 1, the landing diet) - #155

Merged
Keith McGrane (Keithland89) merged 1 commit into
mainfrom
keithmcgrane-microsoft-cut-1-landing-diet
Oct 21, 2026
Merged

Keith McGrane (Keithland89) merged 1 commit into
mainfrom
keithmcgrane-microsoft-cut-1-landing-diet

Conversation

@Keithland89

@Keithland89 Keith McGrane (Keithland89) commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

For 0.6.8 if it's ready in time, else 0.6.9. Keith decides the merge.

Summary

Cut 1 of the 20k cut list, the landing diet. The Executive summary now opens with 8 foreground queries rather than 18, or 9 with org-scoped access. Both counts include the summary's hours range, which #154 (cut 2) asks on its own. The app runs at most four queries at a time, so the page's own queries wait behind fewer others. Nothing on the first view changes. What users lose: the org menu's first open waits about a second for its values, or up to about three seconds if it opens before the org columns have loaded in the background (deviation 2).

These are the cut list's figures, from 0.6.8-rc.1 (465de3b) on an F64 trial with 20,000 people, as it marks them: (m) measured with the executeQueries API, est. estimated from its model of the app's 4-slot query queue. None were re-measured for this PR, which made no Power BI calls.

  • "17 foreground queries at open → 7 (8 with cut 2; one more when access scoping is on)."
  • The merged probe: "≈ 0.7 s (m; 0.64–1.07 over 3 rounds)". The merged credits: "≈ 0.6 s (est.)".
  • "C 4.52 → 2.87–3.10 s on rc.1's model; the summary is then last." C is the landing's share of the ready time: from the first query to the last landing query's answer. Since App: ask the hours range on its own, so the Executive summary's figures don't wait for it #154, the cut list's model with App: ask the hours range on its own, so the Executive summary's figures don't wait for it #154's split added gives 4.62–4.76 → 2.56 s, or 2.81 s with org-scoped access, with the credits then last (est.; see "Saved task times").
  • Package table, all est.:
    • ready −1.4 to −1.7 s;
    • first KPI, date change, Adoption and Leaderboards: 0;
    • users lose "The filter menu's first open waits ≈ 1 s";
    • effort M, risk low–medium; both paths.
  • "On Azure, cuts 1–4 save about the same, and the diet saves more (−0.5 to −3 s), because each query there also pays a token exchange and a REST call."

The landing's queries, numbered as in the cut list, with Consumption Central and every optional module installed, as on the trial:

Query On main (fd6a42d) Now
c00 The day the data runs to On screen Unchanged
c01 Load status On screen Background
c02 Filter options: agent types and names, first and last dates On screen Unchanged
c03 __me: whose data the viewer sees On screen Only with org-scoped access on
c04, c07, c08, c09, c12 The optional sources' probes 5 queries 1 query
c05 Consumption Central's org attributes On screen Once a filter menu other than the dates opens, or an org filter is set
c06 The org values On screen Once a filter menu other than the dates opens
c10 The hours trend After c02 With the first queries, when no date filter is set
c11 The org columns On screen Background. On screen once a filter menu other than the dates opens or a breakdown by org shows (deviation 2)
c13 Consumption Central's rates On screen Unchanged
c14, c15 The credits in the trend's months and in the date range 2 queries, after c02 1 query, after c02 (deviation 1)
c16 The summary On screen Unchanged
c16, part The summary's hours range, asked on its own since #154 (cut 2) On screen Unchanged

So these go on screen at open: c00, c02, the probe, the trend, c13, the credits, c16 and its hours range, plus __me with org-scoped access. c01 and then c11 load in the background. A copy of the new whole-app test, run against main (fd6a42d), counts 18 on screen and none in the background, with org-scoped access off or on: main asks __me either way. Before #154 (3b843e3), it counted 17.

What changed:

  • A background lane for any query. useSemanticModelQuery takes background, which asks in the scheduler's low-priority lane, as the Prefetcher's queries do.
    • A background query waits until nothing on screen is running or waiting.
    • One that stops being background moves on screen, still sent once. One already sent is read rather than sent again.
    • Load status asks in it (useLoadStatus).
  • __me only with org-scoped access. useAccessScope asks ACCESS_SCOPE_QUERY only when the app's config names access.rlsOrgAttribute.
    • The installer writes that whenever org-scoped access is on (publicAccess in installer/src/steps/access.js), for both hosts. On Fabric, it offers to redeploy the app when access changes.
    • On Azure, the web app's /app.config.json dropped it: its own publicAccess (azure/web/src/server.js) passed on only the group and who to ask. It now passes rlsOrgAttribute too, so __me is still asked there.
    • Otherwise the scope is off at once. Main read off from the model's missing Access Scope table.
  • One probe for the optional sources. mergedSourceProbe counts every source in one ROW, one column per source, with the five probes' own expressions (SOURCE_COUNTS). It covers only the sources whose module wasn't left off. readMergedSourceState reads each column as each probe's row was read: a count above 0 is present, else missing. useSourceProbes:
    • hides nothing while the answer is pending ("checking"), as before;
    • asks each source on its own, as main did, if the probe fails for any reason but a busy model. That covers an older model without one of the tables, so each keeps its own "missing";
    • reads every source as unknown, which hides nothing, if the probe fails busy. The scheduler has already retried it, up to ten times.
  • What only the filter menus show loads when one first opens.
    • FilterMenu calls onOpen as its popover opens. FilterBar passes requestFilterChoices as onOpen from the org, agent type, agent name and Group by menus. The dates' menu doesn't call it: its presets and fields come with the filter options.
    • FilterProvider then asks for the org values (c06) and Consumption Central's org attributes (c05).
    • It also asks for c05 whenever an org filter is set, as the cut list asks. Today only the org menu sets one, and its opening has already asked, so that's a safeguard. While c05 is pending, the org filter applies anyway: lib/filter-profiles.ts:219 reads it only to rule out an attribute Consumption Central lacks.
  • The org columns (c11) in the background. They move on screen once a filter menu other than the dates opens, or a breakdown by org calls useOrgAttribute(true), except on a page fetched ahead.
  • The org menu lists values only once c11 has settled the column. c11 can change the column in use, so FilterProvider asks for the org values (c06) only once c11 has answered or failed.
    • Until then, it asks c06 for the column in use now with useSpeculativeQuery, beside c11. The column seldom changes, so the real ask then joins that query or reads its answer, and c06 is sent once. If it does change, that answer is wasted.
    • It offers values only once the query for the column in use has answered, so the menu never lists another column's values. That also fixes main's org menu after Group by changes the column: it listed the last column's values under the new name until the new ones loaded.
    • The org menu is no longer keyed by the column, which remounted it and closed it if the column changed while it was open. Its search box now clears when the column changes, as the remount did.
  • The trend starts with the first queries.
    • With no date filter, the trend asks executiveRecentMonths(): the same months query, held to the six months to the last day of activity.
    • The query reads that day itself, past every filter: CALCULATE(MAX('Chat + Agent Interactions (Audit Logs)'[ActivityDate]), REMOVEFILTERS()). That's the last date the filter options give the page.
    • It's asked with lead and speculate, like the summary and its hours range. With a date filter, it still waits for the dates.
    • Either way, the page shows it only once the dates are known, as the cards and the chart read it by them. So it looks as before.
  • One credits query. executiveCreditParts asks the credits query twice in a UNION: once for the trend's months and once for the date range.
    • Each part is filtered as the page filtered it alone (reportingDateFilter), and tagged with a "Part" column.
    • useConsumptionTableParts, on useTablePartsQuery and splitQueryParts, splits the answer back into the two tables the page read before.
    • The page's own filters wrap the whole query, through the same Consumption Central profile as before.
  • DAX helpers. wrapEvaluateBodies wraps each EVALUATE body, leaving DEFINE and ORDER BY in place; applyDaxFilters now uses it. evaluateBody returns the first body.
  • Docs. docs/ARCHITECTURE.md gains "How the app opens", and app/README.md a bullet on it. azure/web/README.md says what /app.config.json's access carries. CHANGELOG.md: an entry at the end of ## Unreleased.

Deviations from the cut list

Keith accepted both.

1. The credits still wait for the filter options (c02). The cut list starts the trend and the credits with the first query. The trend does, but the credits can't, because both their windows come from c02's first and last dates:

  • the trend's months are the six months to the end of the date range;
  • the range total covers the date range, held to the days with activity (useExecutiveRange).

The credits are asked of Consumption Central, which can't read the ValueLens audit log's dates, so they can't find their own window as the trend does. Starting them early would mean asking at guessed dates and again when those differ, or showing credits for other dates.

Cost (est., from the cut list's queue model):

2. The org columns (c11) load in the background, not when a menu opens. The cut list defers c11 to the filter menu, since "They only fill that menu". But c11 also picks the org column app-wide:

  • useOrgAttribute gives the org filter its name, and every breakdown by org its grouping;
  • on a model whose Organization column is empty, it falls back to the first column worth grouping by.

So c11 now loads in the background at open. It moves on screen, still sent once, when a filter menu other than the dates opens or a breakdown by org mounts.

Because c11 picks the column, the org menu lists values only once c11 has answered (see "What changed"). So the org menu, first opened before c11 has loaded, waits for c11 as well as its values (c06): up to about 3 s, as c11 took 3.01 s in #118's waterfall (m). On main (fd6a42d), with no task times saved, c11 loads in the background from about 3.3–3.6 s to 6.35–6.60 s after the first query (est.), or from 3.7–3.9 s to 6.7–6.9 s before #154. Opened after that, the menu waits about 1 s, the cut list's figure. Opened while the landing's own queries still run, it also waits for a slot, as c06 did on main. The alternative is to list the values of the column in use at once, and clear any ticked if c11 then changes the column. The column seldom changes, but a ticked value could then vanish.

No breakdown on the landing groups by org: the Executive summary's two by-org stages, "Where it is landing" and "What needs attention", aren't on its first tab. So the landing stays at 8 queries, or 9 with org-scoped access.

Group by lists c11's columns, and shows once they've loaded and there's more than one. When it appears, in seconds from the first query (est.):

Base, task times saved Without this PR With it
main (fd6a42d, with #154), none 4.40 6.35–6.60
main (fd6a42d, with #154), some 5.11–5.27 7.78–8.15
before #154, none 3.79 6.66–6.89
before #154, some 3.79 8.00–8.28

Under the cut list's plan, Group by wouldn't show until another menu opened, and then about 3 s later: c11 took 3.01 s (m) in #118's waterfall. Here, opening any other menu first moves c11 on screen.

It also delays the Prefetcher, which waits until nothing at all is loading (see the notes below).

Saved task times

The trend is asked with speculate: true, as the summary is: from the first query at the model's own task times, then again at the saved times if they differ. The early answer is never shown. speculate's doc comment in use-filtered-query.ts is unchanged. #154 reworded it to a rule the trend fits: "Only for queries a page's first view waits on, where an early answer is worth a wasted one when times are saved."

Since #154, three landing queries speculate: the summary, its hours range and the trend. Landing's queries and C, in seconds from the first query (est.). The model has the summary at 2.87–3.10 s, and saved task times known at 1.2 s:

Base Task times saved Org-scoped access Sent Answers wasted C Last to land
main (fd6a42d), with this PR none off 8 0 2.56 credits
none on 9 0 2.81 credits
some off 11 3 3.99–4.15 credits
some on 12 3 4.10–4.37 credits
main (fd6a42d), without it none either 18 0 4.62–4.76 c15
some either 20 2 5.65–5.82 c15
before #154, with this PR none off 7 0 2.87–3.10 summary
none on 8 0 2.87–3.10 summary
some off 9 2 4.21–4.44 summary
some on 10 2 4.26–4.49 summary
before #154, without it none either 17 0 4.52 c15
some either 18 1 5.27–5.37 c15

Other notes and limitations

  • The Prefetcher starts later. It waits until nothing is loading, on screen or in the background, and then 1.5 s more (IDLE_MS). It now also waits for c01 and then c11 in the background lane. So it starts at 7.85–8.10 s rather than main's 6.12–6.26 s (est.), or, before App: ask the hours range on its own, so the Executive summary's figures don't wait for it #154, at 8.16–8.39 s rather than 6.02 s. Under the cut list's plan, with only c01 in the background, it would have started at 5.12–5.35 s before App: ask the hours range on its own, so the Executive summary's figures don't wait for it #154 (est.). What it fetches is unchanged, but each page is fetched about 2 s later, so one opened in that time loads on screen rather than from what was fetched ahead.
  • An older model without one of the optional tables: the merged probe fails, and the five probes run on their own after it. That's one query more than main, and nothing on the first view waits on it.
  • The credits ask both parts wherever they're read: on the bottom line and on "Is it growing?". Main asked the range's credits only with the bottom line on show. "Is it growing?" reuses the landing's answer, since the query is the same.
  • A model whose Organization column is empty: the org filter takes the name of the column it falls back to once c11 loads, which is now later.
  • A column change with no user action, such as c11 answering again after Power BI was busy, could show the last column's values in the org menu for one frame. Group by's and c11's first answer can't.
  • c05 pending is harmless, as the cut list says. At worst one credits query is asked twice, when Consumption Central lacks the chosen org attribute.
  • Org-scoped access is read from the app's config. If it were on in the model but not in the app's config, say on Fabric after declining to redeploy the app, the app wouldn't ask __me. The header wouldn't say whose data shows, and a viewer with no scope would see empty pages rather than the no-scope notice. The model's row-level security still filters every query, so no one sees more.
  • On Azure with org-scoped access on, the Share panel now offers scoped delegation. share-panel.tsx shows it on Azure only when the app's config names rlsOrgAttribute, which /app.config.json dropped until this PR. The web host has served it all along, on the same condition (DelegationService.enabled), and the release notes say it ships ("scoped delegation from the Share panel (Azure)"). The panel asks /api/delegations and shows nothing unless the host says it's on.
  • No live cloud, tenant or Power BI calls were made.

Scope

Parts affected (installer / app / Fabric notebooks and pipeline / Azure infra, jobs or web / core / Power BI models):

The app: the filter bar, menus and provider, the source probes, the access scope, load status, the semantic model query hook, and the Executive page's data hook and queries. And the Azure web host's /app.config.json, which now passes on access.rlsOrgAttribute.

  • Fabric and Azure host the same app (app/src/lib/runtime-config.ts picks the host), so both get it. The Azure web image's next build carries it, with the config change.
  • Nothing changes in the installer, the Fabric notebooks or pipeline, Azure infra or jobs, core, or the Power BI models.

Source contracts, optional inputs, install record or deployed item names changed:

None. The app reads access.rlsOrgAttribute from its config, which the installer already writes for both hosts. On Azure, /app.config.json now passes it on rather than dropping it.

Validation

  • Ran python -B -m pytest tests -q and recorded results below.
  • Ran npm test (and npm run typecheck for the installer) in each changed Node project.
  • Updated directly related documentation and checked changed links.
  • No credentials, tenant exports, prompts or customer names are included.
  • If .pbit files changed, documented the rebuild method and Desktop refresh / visual checks.

Results, skipped checks and limitations:

The branch is rebased onto main 087209b, which has #148, #151, #152, #153, #154 and #158.

On the original head, before the replay, 004d98d3539846c3a3b3c1dade318452c17b2156, tree f1e8761d7e89159fa514ad8a494b02211c1d3fea, on Windows with Node 24.19.0, npm 11.17.0 and Python 3.12.10:

  • In app/, after npx --no-install fabric-app-data generate -o src/fabric.generated.ts:
    • npm test: 155 test files, 3624 tests, all passed. A first run, under load from other work on the machine, timed out one test this PR doesn't touch (report-export.spec.ts, building a PowerPoint file, at its 5 s limit). It passed alone and in the full rerun.
    • npm run lint: 0 errors, and 2 warnings that main has too (react-refresh/only-export-components in vega-visual.tsx and root.tsx, which this PR doesn't touch).
    • npx --no-install tsc -b: passed.
    • npm run build:fabric: passed.
  • In azure/web/, npm test: 81 passed.
  • At the repo root, python -u -B -m pytest tests -q -ra -p no:cacheprovider: 1222 passed, 5 skipped and 12176 subtests passed. The five skips need Spark and Java, which aren't installed here. CI's pytest job passed on this head too.
  • CI on 004d98d: all passed. tests (pytest, app, installer, valuelens-web and arm-drift), azure-images (Build valuelens-web and Build valuelens-jobs), CodeQL (actions, javascript-typescript and python) and license/cla. Code scanning's summary check is neutral, "1 configuration not found", as on the previous head.
  • Not run: the installer's tests and typecheck, as the installer is unchanged. Nothing was run against Power BI, Fabric or Azure.
  • CI on 695b727 (the replay onto 087209b): all green.

New and changed tests:

  • landing-queries.spec.tsx (new): the whole app on the real SDK and scheduler, with both models and the Prefetcher left out. The landing's queries:
    • are eight on screen, the hours range among them, with load status and the org columns in the background;
    • are nine on screen with org-scoped access on, which asks who is signed in;
    • start the trend with the first queries, while the credits wait for the activity's dates;
    • load nothing more when the dates' menu opens, then the menu's choices and the org columns on screen once another menu opens, each sent once;
    • hold the org values from a menu opened before the org columns load, so it never offers one column's values under another's name;
    • offer no values while those of the column Group by picked load;
    • load the org columns on screen once a breakdown by org opens.
  • filter-provider.spec.tsx (new), 8 tests:
    • the filter options load at open, the org columns in the background, and nothing only a menu shows;
    • a menu opening loads the choices, and the org columns on screen;
    • the values that loaded beside the org columns show once those keep the column in use;
    • no column's values show under another's name when the org columns change the column;
    • the org values are asked at once when the org columns have already loaded, or failed to;
    • an org filter loads Consumption Central's org attributes with no menu opened;
    • a breakdown by org loads the org columns on screen;
    • they stay in the background for a page that groups nothing by org, and for one fetched ahead.
  • filter-bar.spec.tsx (new):
    • every menu but the dates loads the menus' choices as it opens;
    • the org menu stays open as its column changes, and a search typed there ends.
  • optional-sources.spec.ts and use-source-probes.spec.tsx:
    • the merged probe counts each source as its own probe does, in a column named after it, and is read as each probe's row was;
    • it asks nothing when every module was left off;
    • nothing is hidden while it waits;
    • each source is asked on its own once the probe names a missing table, or after any other failure;
    • nothing is hidden or asked again while Power BI is busy.
  • executive-queries.spec.ts:
    • the trend with no date filter is the trend's own query, held to the six months to the last day of activity, read past every filter, and keeps its months when the page's filters wrap it;
    • the credits ask each part as the page asked it alone, in one query, with the credits query's columns and model.
  • query-parts.spec.ts (new, 5) and use-table-query.spec.tsx (new, 3): each part reads as its own query did, a part with no rows is an empty table, and the parts load and fail together.
  • executive-lead-queries.spec.tsx, on App: ask the hours range on its own, so the Executive summary's figures don't wait for it #154's tests for the hours range:
    • pins the trend's speculate: true: it asks the trend at the model's own task times while the app's are still loading;
    • the bottom line, its hours range and the trend go from the first query, in that order, ahead of the queries already waiting;
    • with no date filter the trend doesn't wait for the activity's dates; with one, it does;
    • with task times saved, the trend is asked again at them, as the hours and their range are, and the range and the trend at the model's own times are called off while still queued;
    • the bottom line and the trend keep their places in the queue when the task times come while they wait, none saved.
  • use-semantic-model-query.sdk.spec.tsx, the background lane: it waits while a query on screen loads; it moves on screen, sent once; a query already sent is read, not sent again; and a page fetched ahead stays in the background.
  • dax-filters.spec.ts: wrapEvaluateBodies and evaluateBody.
  • use-load-status.spec.tsx: load status asks in the background lane.
  • azure/web/test/access.test.js: the app config says when org-scoped access is on, with the org attribute it filters on.

Compatibility

Repair or change: nothing to migrate. The app ships whole.

  • On Fabric, the release's Update offers to redeploy the app, which brings it. On Azure, the web image's next build carries it.
  • Saved task times, report packs and settings are unaffected.
  • The ValueLens Power BI templates need no matching change: no model, measure or .dax file changes. The existing months and credits queries are unchanged; the trend's and the credits' new queries wrap them.

@Keithland89
Keith McGrane (Keithland89) force-pushed the keithmcgrane-microsoft-cut-1-landing-diet branch 2 times, most recently from f1caa46 to 004d98d Compare October 20, 2026 21:52
@Keithland89 Keith McGrane (Keithland89) changed the title App: the Executive summary opens with 7 queries rather than 17 (cut 1, the landing diet) App: the Executive summary opens with 8 queries rather than 18 (cut 1, the landing diet) Oct 20, 2026
…, the landing diet)

Cut 1 of the 20k cut list. The landing now sends 8 foreground queries at
open, the hours range's among them, or 9 with org-scoped access, so its
own queries wait behind fewer others in the app's four-slot queue.

- Load status and the org columns load in a background lane; the org
  columns move on screen when a filter menu other than the dates opens or
  a breakdown by org mounts.
- The org values and Consumption Central's org attributes load when such
  a menu first opens (and the attributes once an org filter is set). The
  org menu lists values only once the org columns have settled the column.
- One probe checks the optional sources; each is asked on its own if it
  fails for any reason but a busy model.
- The two credits queries become one; they still wait for the dates.
- The trend starts with the first queries when no date filter is set,
  and, like the summary and its hours range, at the model's own task
  times.
- `__me` is asked only when the app's config says org-scoped access is on.
  On Azure, /app.config.json now passes rlsOrgAttribute on, as the
  installer meant.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@Keithland89
Keith McGrane (Keithland89) force-pushed the keithmcgrane-microsoft-cut-1-landing-diet branch from 004d98d to 695b727 Compare October 21, 2026 06:20
@Keithland89
Keith McGrane (Keithland89) merged commit bf273f1 into main Oct 21, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant