Skip to content

blob_showcase_infra - #314

Open
dani-vaibhav wants to merge 6 commits into
rohit-r-kumar/issue258from
blob_showcase
Open

blob_showcase_infra#314
dani-vaibhav wants to merge 6 commits into
rohit-r-kumar/issue258from
blob_showcase

Conversation

@dani-vaibhav

@dani-vaibhav dani-vaibhav commented Aug 6, 2026

Copy link
Copy Markdown

Summary by Sourcery

Add a permission-controlled Blob Storage Explorer to Tech Admin and extend blob services and APIs to support scalable browsing, filtering, preview, and download workflows.

New Features:

  • Add a permission-gated staff Blob Storage Explorer for browsing containers, navigating virtual folders, filtering blob metadata and tags, paginating results, previewing supported content, and downloading blobs.
  • Expose blob container listing, hierarchical exploration, bounded downloads, and read SAS URLs through the blob storage service, application services, and GraphQL API.

Bug Fixes:

  • Ensure test server ports are fully released before replacement processes start.
  • Make portless route pruning best-effort so stale route locks do not prevent the test proxy from starting.

Enhancements:

  • Extend staff permission propagation and Tech Admin routing to support Blob Storage Explorer access.
  • Improve process test server cleanup with graceful termination, forced termination, and port availability checks.

Build:

  • Use the Azure Functions Core Tools installer task in the monorepo build pipeline.
  • Refresh dependency lockfile and package security overrides.

Documentation:

  • Document the expanded blob storage operations, hierarchical exploration, metadata and tag support, pagination, and bounded downloads.

Tests:

  • Add coverage for blob storage operations, GraphQL authorization and resolvers, Blob Storage Explorer interactions, and process server port cleanup.

@sourcery-ai

sourcery-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds a tech-admin Blob Storage Explorer feature spanning backend, GraphQL, and UI layers, including new hierarchical blob listing, container enumeration, bounded blob download, and permission-gated access for staff users.

Sequence diagram for techAdminBlobList hierarchical listing

sequenceDiagram
    actor StaffUser
    participant BlobStorageExplorerContainer
    participant GraphQLServer
    participant TechAdminResolvers
    participant TechAdminApplicationService as TechAdminService
    participant BlobStorageOperations as BlobStorageService

    StaffUser->>BlobStorageExplorerContainer: Select container / Apply filters
    BlobStorageExplorerContainer->>GraphQLServer: TechAdminBlobStorageExplorerList
    GraphQLServer->>TechAdminResolvers: techAdminBlobList(args)

    TechAdminResolvers->>TechAdminResolvers: assertCanViewBlobExplorer(context)
    TechAdminResolvers->>TechAdminResolvers: buildBlobListQueryCommand(args)
    TechAdminResolvers->>TechAdminApplicationService: TechAdminService.ListBlobHierarchy(command)

    TechAdminApplicationService->>BlobStorageOperations: listBlobHierarchy(command)
    BlobStorageOperations-->>TechAdminApplicationService: BlobHierarchyPage
    TechAdminApplicationService-->>TechAdminResolvers: BlobHierarchyPage

    TechAdminResolvers-->>GraphQLServer: BlobHierarchyPage mapped to GraphQL types
    GraphQLServer-->>BlobStorageExplorerContainer: techAdminBlobList result
    BlobStorageExplorerContainer-->>StaffUser: Render folders, blobs, continuationToken
Loading

Sequence diagram for techAdminBlobContent preview and SAS download URL

sequenceDiagram
    actor StaffUser
    participant BlobStorageExplorer as BlobStorageExplorerUI
    participant BlobStorageExplorerContainer
    participant GraphQLServer
    participant TechAdminResolvers
    participant TechAdminApplicationService as TechAdminService
    participant BlobStorageOperations as BlobStorageService
    participant ClientUploadOperations as ClientUploadService

    StaffUser->>BlobStorageExplorer: Click View on blob
    BlobStorageExplorer->>BlobStorageExplorerContainer: onViewBlob(blob)
    BlobStorageExplorerContainer->>GraphQLServer: TechAdminBlobStorageExplorerContent
    GraphQLServer->>TechAdminResolvers: techAdminBlobContent(args)

    TechAdminResolvers->>TechAdminResolvers: assertCanViewBlobExplorer(context)
    TechAdminResolvers->>TechAdminApplicationService: TechAdminService.GetBlobContent({containerName, blobName})

    TechAdminApplicationService->>BlobStorageOperations: downloadBlob({containerName, blobName})
    BlobStorageOperations-->>TechAdminApplicationService: BlobDownloadResult
    TechAdminApplicationService->>ClientUploadOperations: generateReadSasToken({containerName, blobName, expiresOn})
    ClientUploadOperations-->>TechAdminApplicationService: sasToken
    TechAdminApplicationService-->>TechAdminResolvers: BlobContentResult (contentBase64, metadata, tags, downloadUrl)

    TechAdminResolvers-->>GraphQLServer: BlobContent
    GraphQLServer-->>BlobStorageExplorerContainer: techAdminBlobContent result
    BlobStorageExplorerContainer-->>BlobStorageExplorer: preview
    BlobStorageExplorer-->>StaffUser: Show preview and Download action
Loading

File-Level Changes

Change Details Files
Extend framework blob-storage service with hierarchical listing, container enumeration, and bounded in-memory download support.
  • Introduce new blob interfaces for containers, folders, explorer items, hierarchy pagination, and download results, plus a global max download size constant.
  • Implement ServiceBlobStorage.listContainers, listBlobHierarchy (prefix-based and tag-based variants), and downloadBlob with content-type heuristics and size enforcement.
  • Add internal helpers for prefix normalization, name/metadata/tag filtering, tag value escaping, text-compatibility detection, and streaming a readable into a bounded Uint8Array.
  • Export new types and BLOB_DOWNLOAD_MAX_BYTES through both cellix and ocom service-blob-storage indexes and extend BlobStorageOperations/ClientUploadOperations contracts.
  • Update blob storage tests and mocks to cover hierarchy listing, tag-based filtering paths, container enumeration, and bounded download behavior.
packages/cellix/service-blob-storage/src/service-blob-storage.ts
packages/cellix/service-blob-storage/src/interfaces.ts
packages/cellix/service-blob-storage/src/index.ts
packages/cellix/service-blob-storage/tests/index.test.ts
packages/ocom/service-blob-storage/src/index.ts
packages/ocom/service-blob-storage/src/blob-storage.contract.ts
packages/cellix/service-blob-storage/manifest.md
packages/cellix/service-blob-storage/README.md
packages/ocom-verification/acceptance-api/src/mock-application-services.ts
Expose new blob-explorer capabilities via application services and GraphQL tech-admin resolvers with strong input validation and authorization.
  • Add TechAdmin ListBlobContainers, ListBlobHierarchy, and GetBlobContent application-service functions that wrap the blob-storage operations, validate inputs, map records to key/value arrays, and optionally generate short-lived SAS URLs for downloads.
  • Introduce buildBlobListQueryCommand to validate/normalize GraphQL blob list query arguments (container, prefix, continuationToken, pageSize, filters).
  • Extend the ApplicationServices factory to pass blobStorageService and clientOperationsService into User and TechAdmin contexts, and wire new methods into TechAdminApplicationService and UserContextApplicationService.
  • Add GraphQL schema types and queries for BlobContainer, BlobFolder, BlobExplorerBlob, BlobHierarchyPage, BlobContent, and resolvers techAdminBlobContainers, techAdminBlobList, techAdminBlobContent with shared permission checks (assertCanViewBlobExplorer) and error mapping to BAD_USER_INPUT/UNAUTHENTICATED.
  • Update tech-admin resolver tests and Gherkin feature specs to cover authorization for blob queries, list/preview behavior, and database tests adjustments.
packages/ocom/application-services/src/contexts/tech-admin/index.ts
packages/ocom/application-services/src/contexts/tech-admin/blob-list.command-mapper.ts
packages/ocom/application-services/src/contexts/tech-admin/list-blob-containers.ts
packages/ocom/application-services/src/contexts/tech-admin/list-blob-hierarchy.ts
packages/ocom/application-services/src/contexts/tech-admin/get-blob-content.ts
packages/ocom/application-services/src/index.ts
packages/ocom/graphql/src/schema/types/tech-admin.graphql
packages/ocom/graphql/src/schema/types/tech-admin.resolvers.ts
packages/ocom/graphql/src/schema/types/tech-admin.resolvers.test.ts
packages/ocom/graphql/src/schema/types/features/tech-admin.resolvers.feature
packages/ocom/application-services/src/contexts/user/index.ts
packages/ocom-verification/acceptance-api/src/mock-application-services.ts
Add a staff-portal Blob Storage Explorer UI (route, container, and presentational component) integrated with GraphQL and staff permissions.
  • Introduce a BlobStorageExplorer React component with container selection, breadcrumb-based prefix navigation, server-side filtering controls (name, metadata, tags), hierarchical table view, load-more pagination, and a rich preview modal (text/image/PDF) with download support.
  • Add a BlobStorageExplorerContainer that wires Apollo queries for containers, list, and content, manages accumulated paging state, filters, prefix/container changes, and preview loading.
  • Expose a Blob Storage Explorer page under /staff/tech/blob-storage-explorer, wrapped in RequireRole and using the shared SubPageLayout.
  • Update section layout, staff route shell, shared navigation, and permission hooks (useStaffPermissions, RequireRole, StaffAuth, SectionLayout) to recognize the new canViewBlobExplorer permission and show tech-admin nav when applicable.
  • Add tests and Storybook stories for the Blob Storage Explorer UI and tweak existing database-explorer tests to be more robust with multiple matching elements.
packages/ocom/ui-staff-route-tech-admin/src/components/blob-storage-explorer.tsx
packages/ocom/ui-staff-route-tech-admin/src/components/blob-storage-explorer.container.tsx
packages/ocom/ui-staff-route-tech-admin/src/components/blob-storage-explorer.test.tsx
packages/ocom/ui-staff-route-tech-admin/src/components/blob-storage-explorer.stories.tsx
packages/ocom/ui-staff-route-tech-admin/src/pages/blob-storage-explorer.tsx
packages/ocom/ui-staff-route-tech-admin/src/index.tsx
packages/ocom/ui-staff-route-tech-admin/src/section-layout.tsx
packages/ocom/ui-staff-shared/src/section-layout.tsx
packages/ocom/ui-staff-shared/src/staff-route-shell.tsx
apps/ui-staff/src/hooks/use-staff-permissions.ts
apps/ui-staff/src/App.tsx
packages/ocom/ui-staff-shared/src/require-role.tsx
packages/ocom/ui-staff-shared/src/staff-route-shell.tsx
packages/ocom/ui-staff-route-tech-admin/src/components/database-explorer.test.tsx
apps/ui-staff/src/hooks/use-staff-permissions.ts

Possibly linked issues

  • #(none provided): PR fully implements the requested Blob Storage Explorer page, including auth, GraphQL, navigation, filtering, preview, download, and tests.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In listBlobHierarchyPage, the folder loop skips all folders when request.metadataKey is set (if (request.metadataKey?.trim()) continue;), which contradicts the comment that folders should still appear for navigation; consider either removing this condition or aligning the comment and behavior so filtered listings still expose folder paths as intended.
  • The BlobStorageExplorer component creates object URLs for previews via URL.createObjectURL but never revokes them, which can leak memory over time; wrap this in a useEffect that revokes the URL in a cleanup function when the preview changes or the component unmounts.
  • BlobStorageExplorer maintains its own draftFilters state while the container also tracks filters and passes filters/onChangeFilters, leading to duplicated and potentially out-of-sync state; simplifying this so the container is the single source of truth (and the presentational component is fully controlled) will make filter behavior easier to reason about.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `listBlobHierarchyPage`, the folder loop skips all folders when `request.metadataKey` is set (`if (request.metadataKey?.trim()) continue;`), which contradicts the comment that folders should still appear for navigation; consider either removing this condition or aligning the comment and behavior so filtered listings still expose folder paths as intended.
- The `BlobStorageExplorer` component creates object URLs for previews via `URL.createObjectURL` but never revokes them, which can leak memory over time; wrap this in a `useEffect` that revokes the URL in a cleanup function when the preview changes or the component unmounts.
- `BlobStorageExplorer` maintains its own `draftFilters` state while the container also tracks filters and passes `filters`/`onChangeFilters`, leading to duplicated and potentially out-of-sync state; simplifying this so the container is the single source of truth (and the presentational component is fully controlled) will make filter behavior easier to reason about.

## Individual Comments

### Comment 1
<location path="packages/ocom/ui-staff-route-tech-admin/src/components/blob-storage-explorer.tsx" line_range="165" />
<code_context>
+	onViewBlob,
+	onClosePreview,
+}) => {
+	const [draftFilters, setDraftFilters] = useState(filters);
+
+	const breadcrumbItems = useMemo(() => {
</code_context>
<issue_to_address>
**issue (bug_risk):** Local filter state is not synced with `filters` prop, leading to potential stale UI when parent resets filters.

`draftFilters` is only initialized from `filters` and never updated when `filters` changes, so the component and its parent can diverge (e.g. after a parent reset or state restore). Consider either making `BlobStorageExplorer` fully controlled (use `filters` directly and call `onChangeFilters` on every change), or syncing `draftFilters` with `filters` via `useEffect(() => setDraftFilters(filters), [filters])` so external updates are reflected.
</issue_to_address>

### Comment 2
<location path="packages/ocom/ui-staff-route-tech-admin/src/components/blob-storage-explorer.tsx" line_range="291-300" />
<code_context>
+		},
+	];
+
+	const previewObjectUrl = useMemo(() => {
+		if (!preview?.contentBase64) {
+			return null;
+		}
+		if (!isImageContentType(preview.contentType) && !isPdfContentType(preview.contentType)) {
+			return null;
+		}
+		const binary = atob(preview.contentBase64);
+		const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
+		const blob = new Blob([bytes], { type: preview.contentType ?? 'application/octet-stream' });
+		return URL.createObjectURL(blob);
+	}, [preview]);
+
+	const handleDownload = () => {
</code_context>
<issue_to_address>
**issue (performance):** Object URL created for previews is never revoked, which can leak memory over time.

`previewObjectUrl` is created with `URL.createObjectURL` but never revoked, so repeated preview opens in a long-lived session can accumulate blob URLs and increase memory usage. Consider cleaning it up in an effect that runs when the URL changes/unmounts, e.g.:

```ts
const previewObjectUrl = useMemo(() => {
  if (!preview?.contentBase64) return null;
  if (!isImageContentType(preview.contentType) && !isPdfContentType(preview.contentType)) return null;
  const binary = atob(preview.contentBase64);
  const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
  const blob = new Blob([bytes], { type: preview.contentType ?? 'application/octet-stream' });
  return URL.createObjectURL(blob);
}, [preview]);

useEffect(() => () => {
  if (previewObjectUrl) URL.revokeObjectURL(previewObjectUrl);
}, [previewObjectUrl]);
```
</issue_to_address>

### Comment 3
<location path="packages/cellix/service-blob-storage/tests/index.test.ts" line_range="272-281" />
<code_context>
+			expect(result).toEqual([{ name: 'member-assets' }, { name: 'private' }]);
+		});
+
+		it('lists one hierarchy level with folders, blob properties, and a continuation token', async () => {
+			const service = new ServiceBlobStorage({ accountName });
+			await service.startUp();
+
+			const result = await service.listBlobHierarchy({
+				containerName: 'member-assets',
+				prefix: '',
+				pageSize: 20,
+			});
+
+			expect(listBlobsByHierarchyMock).toHaveBeenCalledWith('/', {
+				prefix: undefined,
+				includeMetadata: true,
</code_context>
<issue_to_address>
**suggestion (testing):** Hierarchy listing assertion is over‑specific about the `prefix` option and may become brittle.

In the `lists one hierarchy level with folders...` test, this assertion couples the test to the exact options shape. If the implementation simply omits a falsy `prefix`, the behaviour remains correct but this test will fail. To make it more robust, assert only the relevant fields:

```ts
expect(listBlobsByHierarchyMock).toHaveBeenCalledWith(
  '/',
  expect.objectContaining({
    includeMetadata: true,
    includeTags: true,
  }),
);
```

You can then add a separate test with a non‑empty prefix that explicitly checks the `prefix` value is forwarded.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/cellix/service-blob-storage/tests/index.test.ts
dani-vaibhav and others added 4 commits August 27, 2026 02:07
…eline (#323)

* chore: add debug task for npm configuration before Azure Functions tools installation to investigate npm auth failure

* fix: replace npm global install with FuncToolsInstaller for Azure Functions Core Tools and remove temporary debug step

* chore: update dependency ovveride versions in pnpm lock and workspace files to resolve audit and snyk vulnerabilities

---------

Co-authored-by: Copilot Bot <devnull@example.com>
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.

3 participants