Skip to content

(NII-Proposal) feat(files): add dynamic file provider registry for foreign addon support - #1066

Draft
chiku-samugari wants to merge 1 commit into
CenterForOpenScience:developfrom
chiku-samugari:feat/file-provider-dynamic-registry
Draft

chiku-samugari wants to merge 1 commit into
CenterForOpenScience:developfrom
chiku-samugari:feat/file-provider-dynamic-registry

Conversation

@chiku-samugari

@chiku-samugari chiku-samugari commented Sep 28, 2026 •

Copy link
Copy Markdown

Purpose

The Files route /{guid}/files/{provider} is guarded by isFileProvider, which admits only the provider
names listed in the build-time constant FileProvider. A storage service that GravyValet serves under any
other name cannot be opened: the route does not match, the router falls through to the file-detail route,
and the page shows an empty file view for a file that does not exist.

GravyValet PR #318 (Foreign Addon Imps)
lets a deployment add storage add-on implementations as external packages. Such a service is registered in
GravyValet, served by osf.io and WaterButler, listed on the Add-ons page, and browsable in the files
widget of the project overview, but the Files page refuses its name. This PR extends that last build-time allow-list.

Summary of Changes

  • Add FileProviderRegistryService, the registry of valid provider names.
    • The built-in FileProvider values are known from the start and are answered without any request.
    • The first time the registry is asked for a name it does not know, it fetches the
      external_service_name of every service returned by GravyValet's GET /v1/external-storage-services
      and keeps the result until the page is reloaded.
    • While the request is pending, the full-screen loader is shown, as ResourceService.getResourceById
      does for the resource lookup of the :id guards.
  • Make isFileProvider ask the registry asynchronously instead of the build-time list.
  • Introduce a new type FileProviderType for the provider field of the files state.
    The type states what the field holds with this PR: a built-in name, or a name that GravyValet lists.
  • Fall back to the name served by GravyValet for a service that the AddonServiceNames enum does not list.
    This avoids an empty name in the success toasts and in the header of the disconnect dialog.
  • Tests
    • Add new specs for the registry and AddonDialogService
    • Rewrite spec for the guard
    • Revive specs of ConnectConfiguredAddonComponent and ConnectAddonComponent that cover
      the create and update flows and the name in the success toast.

Screenshot(s)

n/a

Side Effects

  • The Files route now depends on GravyValet for names that are not built-in. Opening
    /{guid}/files/{name} with a name other than the built-in providers sends one request to GravyValet
    (GET /v1/external-storage-services) before the route is matched, once per page load. This includes
    the file-detail URLs /{guid}/files/{fileGuid}, which share the route prefix and do not use GravyValet
    today.
  • The provider of the files state can now be a name that FileProvider does not list. The type
    allowed this before (it resolved to string), so this is a change of the values at run time, not of the
    types.

QA Notes

  • Pages: Files page of a project and of a registration; project overview files widget; move/copy dialog.
  • Cases:
    • a built-in provider (/{guid}/files/osfstorage, /{guid}/files/googledrive) opens as before;
    • with a foreign storage add-on configured on the project, /{guid}/files/<name> opens, including on a
      direct page load and reload;
    • an unknown file provider name (/{guid}/files/nope) is still rejected;
    • logged-out visitor on a public project: Files page works;
    • a file-detail page (/{guid}/files/{fileGuid}) opens as before.
  • Add-on names (project Add-ons page and user settings Add-ons page):
    • storage service that uses froeign addon imp: the disconnect dialog header and the success toast after
      connecting show the service's name instead of an empty one;
    • built-in service: the texts are not changed.
  • Risk: low. The guard becomes permissive only for names GravyValet lists.
  • Cross-browser testing: not required.
  • Requires a GravyValet that includes PR #318,
    with at least one foreign storage add-on registered.

…port

The Files route was guarded by a build-time list of provider names, so a
storage service that GravyValet serves under any other name (a foreign
addon imp) could not be opened.

Changes:
- add `FileProviderRegistryService`, which accepts the built-in names
  without a request and fetches the external storage service names from
  GravyValet the first time it is asked for a name it does not know
    - `metadata` is reserved and always refused as a file provider name
    - the first time access of file detail page also triggers this
      request because file GUIDs under `files/` are unknown names too
- make `isFileProvider` ask the registry asynchronously and it refuses
  the name when the request fails
- fall back to the provider/display name in the connect toasts and the
  disconnect dialog when the service has no entry in `AddonServiceNames`
- add `FileProviderType` to document that provider names can be dynamic

Tests:
- add a spec for `FileProviderRegistryService`: built-in and reserved
  names need no request, one request serves concurrent and later
  lookups, and the failure and timeout paths (report, loader, retry)
- rewrite the `isFileProvider` guard spec against a mocked registry,
  including the refusal when the registry rejects
- add a spec for `AddonDialogService`, covering the name in the
  disconnect dialog header
- revive the skipped specs of `ConnectConfiguredAddonComponent` and
  `ConnectAddonComponent`: the router mock supplies the navigation
  state that both components read in their constructor. They now cover
  the create/update flows and the name in the success toast
@chiku-samugari
chiku-samugari force-pushed the feat/file-provider-dynamic-registry branch from 9fc8552 to f54ef66 Compare October 4, 2026 17:58
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