Skip to content

v0.44.0

Choose a tag to compare

@github-actions github-actions released this 06 Nov 08:48
007fad7

What's changed

Read the PR list and Commits through TanStack Query

Lists felt sluggish because every load hook dropped back to shimmer rows
whenever it ran again: coming back from a PR, switching tab, paging,
changing a filter or pressing Try again all blanked the list, even when
the rows were in the 30 s cache.

This adds @tanstack/react-query (v4: v5 needs React 18, and
azure-devops-ui pins React 16) with one QueryClient per page, and moves
the two lists that flash most onto it:

  • Pull requests: useQuery under prListKey. Back from a PR, or back to a
    tab, shows the rows at once and refreshes them in the background.
    A new page or filter keeps the current rows until its own arrive; the
    pager waits for the page it last asked for.
  • Commits: useInfiniteQuery, a page per "Load more". A filter change
    keeps the rows; another branch starts from shimmer rows, so one
    branch's commits never show under another's name. A failed refresh or
    "Load more" keeps what is shown (Load more says so in a MessageCard).

Fresh for 30 s as before, kept 10 minutes, refetched on focus when stale.
Only a network or 5xx failure is retried, once; sign-in, permission and
rate-limit errors show at once as before. invalidatePrs and
invalidateRefs now also refetch what is on screen and drop the rest
(dropQueries), so a merged PR is not shown under Active afterwards;
sign-out clears the whole cache. listPrs and listCommitsRest no longer
cache themselves.

No hand-rolled markup: the existing Table, Card, ZeroData and
MessageCard are reused.

97e9a67

Add the shared query hook, key roots and IndexedDB persistence

useGh wraps useQuery in the loading/error/ready shape the hubs already
render, so each hub converts the same way: loading only while there is
nothing to show, an error only while there is still nothing to show.
Every query key starts with a root (prs, pr, refs, browse, actions,
security, misc), and invalidateRefs, invalidateBrowse, invalidatePr,
invalidatePrPage and invalidateForks now drop their root too.

queryPersist.tsx keeps opted-in reads (meta.persist) in IndexedDB, one
record per organisation, ADO user and hub, so reopening a hub shows its
last lists at once while they refresh. Only lists and metadata opt in;
records last a day, a new extension version drops them, and Sign out
clears them all. Signing in refetches every read, so sign-in state is
no longer part of the keys (it made a restored cache miss on load).

The PR list and Commits opt in.

9abe811

Test the query persister; keep the mock's scenarios apart

Unit tests (fake-indexeddb) for idbPersister: one debounced write a
second after the last change, Dates restored intact, one record per
id, and Sign out clearing every hub's record; and for shouldPersist,
which keeps only successful reads that opted in.

In the mock harness the user id now carries the scenario (and live
mode), so a list saved under "ok" can't hide a failure scenario's
error, and Reset storage deletes the saved reads too.

46540f1

Read Pushes, Branches, Tags, Releases and commits through queries

The ref hubs dropped to shimmer rows on every revisit, Try again and
write. Their reads now go through useGh under the "refs" root, so they
show what they had while refreshing, and invalidateRefs (Try again, and
every branch or tag write via useRefWrites) refetches what is on screen.

  • Pushes: useInfiniteQuery, a page per "Load more"; a failed page keeps
    the rows and says so in a MessageCard instead of replacing the list.
  • Branches, Tags, Releases, policies, the compare pickers' names, the
    commit header, the viewer and admin flags are kept between visits.
    Compare results, commit file patches, locked branches and the policy
    editor's own ruleset are not (they go stale or must start fresh).
  • invalidateReleases drops ["refs", "releases"] too, since the release
    caches are not among the ref caches.
  • Reload counters and signedIn are gone from the loaders: sign-in now
    refetches everything.

No hand-rolled markup: the new warning is a MessageCard.

c48d9b1

Read the Files hub through queries

Files re-fetched the tree, folder, last changes and README on every
visit, and dropped to shimmer on Try again. Its reads now go through
useGh under the "browse" root (forks, the default branch and the latest
release under "refs"), so going back up a folder or reopening the hub
shows what it had at once.

  • Kept between visits: repo info, the project's connected repos (per
    project), the default branch, the latest release, folder listings,
    last change per entry and History. Never trees, file contents, the
    README, markdown source, blame or compare.
  • The tree, a file, the README, blame and compare are fresh for five
    minutes, so refocusing the frame doesn't re-download a large tree or
    file (they were never refreshed during a visit before).
  • The view reads the tree through the cache, so after a commit (which
    drops "browse") it reads the new tree.
  • History's "Load more" is a useInfiniteQuery; a failed page keeps the
    rows and says so in a MessageCard.
  • Compare builds Monaco from the query's stable value, so a background
    refresh does not rebuild the editor.
  • Opening a file with edit=true waits for the write context.

No hand-rolled markup: the new warning is a MessageCard.

18ce8ff

Share saved reads per project and merge them, not replace them

One record per hub meant two widgets of one kind (or one hub in two
projects) overwrote each other's saved reads, last writer winning. The
record is now per organisation, ADO user and project, shared by every
hub and widget of the project, and each save merges into it inside one
IndexedDB transaction: the newer read of a key wins, reads older than a
day go, and a record from another extension version is replaced.

Because saved reads now outlive the in-memory cache, cacheTime goes
back to ten minutes (it was a day, which held file contents and logs in
memory for that long); useGh takes a cacheTime for large reads.
dropQueries also forgets the saved copies under its key, so the next
visit after a write doesn't start from them.

invalidatePrPage drops only the PR's page and edit reads: dropping the
whole PR refetched every part of it after each action (18 requests in
the mock), and the rest already follow the action store's version.

7c2c055

Read the pull request page, New pull request and hover cards through queries

Switching a PR's tabs, going back to the list and reopening a PR all
started from a spinner. The PR page's reads now go through useGh under
["pr", repo, number, …], so tabs and revisits show what they had while
refreshing; the header is kept between visits.

  • The header still re-reads on an action or feed version bump; edit
    data, deployments and work items re-read on the action store's
    version (useRefetchOn) and keep what they show meanwhile. actionStore
    and feedStore are unchanged.
  • Files, updates, ranges, viewed states and annotations are cached for
    the visit but never saved (they hold patches). A viewed tick writes
    into the cached answer and is put back if GitHub refuses.
  • New pull request reads under ["prs", "new", …]; the preview and the
    form's file count share one compare read.
  • Hover cards read the client directly (fetchQuery), since they also
    open in Monaco zones and widgets with no provider; an answer is fresh
    for ten minutes. The Monaco zone roots in PrFiles get a provider.
  • My PRs (the page and the My work tab) is kept between visits; being
    signed out is an error, so it is never saved. The My work tab's own
    entry renders inside QueryProvider.

Diff file contents, blobs and rendered markdown stay as they were.
No hand-rolled markup.

520d490

Read Actions, Advanced Security and the widgets through queries

The runs list, run pages, environments, library and alert lists
re-fetched from scratch on every revisit and filter change. Their reads
now go through useGh under the "actions" and "security" roots.

  • Polling is refetchInterval: the runs list every 10 s while any loaded
    run is active, a run page every 5 s while it or a job is; both stop
    when nothing is running and while the frame is hidden (2 reads in 21 s
    visible, none in 22 s hidden in the mock).
  • Writes (re-run, approve, delete logs, Run workflow, enable/disable,
    library and alert changes) drop the reads they affect; deleting a run
    removes it from every cached runs list at once.
  • Runs and alert lists are infinite queries: filters keep the rows
    until the new ones arrive, and a failed "Load more" keeps the rows and
    says so in a MessageCard.
  • Kept between visits: runs, workflows, environments, deployments,
    alert lists and counts, widget data. Never logs, artifacts, test
    results, run detail or permissions. Job logs leave memory after five
    minutes.
  • The widgets render inside QueryProvider; being signed out is an error
    there, so it is never saved.

No hand-rolled markup: the new warning is a MessageCard.

738f7ae

Show sign-in and permission errors over saved data; save one page per list

Three things found reviewing the move to the query cache, before release:

  • The pages show an error only when there is nothing else to show, so a
    sign-in that lapsed overnight left yesterday's saved rows on screen
    with no way to sign in. A read GitHub refuses for sign-in ("auth") or
    permission ("access") now clears what it showed, so every page falls
    through to its error view (with Sign in), and forgets the saved copy:
    every saved read for "auth", that read's for "access". Rate-limit and
    network failures still keep what is shown. Done once, in the query
    cache's onError, so it covers useGh and the direct useQuery pages.
  • On a revisit TanStack re-reads every page an infinite list holds, one
    after another: five "Load more" pages saved meant five requests (each
    with GraphQL extras) every time the hub opened. Saved infinite lists
    now keep their first page only.
  • Every read of a project shares one IndexedDB record, so one value the
    browser can't clone would fail the whole write and silently stop
    every hub's reads being kept. Such a read is now left out (with a
    console warning) and the rest are saved. A crawl of every hub and
    widget in the mock saved them all into one record with no warnings.

94a6af2

Release 0.44.0

Every hub now reads GitHub through TanStack Query, and keeps its lists
between visits, so pages stop dropping to shimmer rows on revisits,
tab switches and Try again, and a hub opened again shows its last lists
at once while they refresh.

7df9651

Let a publish re-run finish a release whose upload went through

0.44.0 uploaded to the Marketplace, then the validation poll got a
request timeout from the Marketplace, read it as an empty status and
failed the job, so the tag and GitHub release were never made. A re-run
couldn't recover: it would upload the same version again and fail on
"Version number must increase".

  • A publish refused because the version is already there carries on to
    the validation check instead of failing.
  • A validation poll with no answer counts as pending and is asked
    again; only a real non-pending status fails the job.

007fad7

Full changelog: v0.43.0...v0.44.0