Skip to content

Feat/aws dynamodb explorer#31

Open
AncientGear wants to merge 2 commits into
floci-io:mainfrom
AncientGear:feat/aws-dynamodb-explorer
Open

Feat/aws dynamodb explorer#31
AncientGear wants to merge 2 commits into
floci-io:mainfrom
AncientGear:feat/aws-dynamodb-explorer

Conversation

@AncientGear

Copy link
Copy Markdown

This PR adds DynamoDB as a dedicated AWS service in the unified Cloud Explorer flow.

Repository contribution notes

I reviewed README.md first.

The repository does not define a formal PR/contribution template or a CONTRIBUTING.md file. Based on the README and existing git history, this repo currently expects changes to follow these conventions:

  • Keep work inside the unified Cloud Explorer model.
  • Preserve the Cloud Proxy API -> Adapter Registry -> Provider Adapter architecture.
  • Render only real runtime data. No fake/demo resources.
  • Use conventional commit style.
  • Keep unsupported services explicit with placeholders or disabled states.

This change follows those conventions.

Summary

  • Add a dedicated AWS DynamoDB service path instead of mixing DynamoDB into the existing Database/RDS view.
  • Show DynamoDB on Console Home when tables exist.
  • Add grouped AWS navigation for Database and DynamoDB.
  • Add backend DynamoDB adapter/service wiring through the existing Cloud Proxy architecture.
  • Add backend and frontend tests, including frontend Vitest bootstrap.

What changed

Backend

  • Added DynamoDB AWS SDK dependency.
  • Added DynamoDB service logic and adapter normalization.
  • Registered DynamoDB in the Cloud Proxy / adapter flow.
  • Added route support for:
    • /api/clouds/:cloud/services/dynamodb/resources
    • compatibility alias /api/clouds/:cloud/dynamodb/resources
  • Added backend tests for service, adapter, schema, AWS client wiring, and routes.

Frontend

  • Added dynamodb as a first-class cloud service type.
  • Added dedicated route support for /cloud-explorer/aws/dynamodb.
  • Added Console Home DynamoDB card/count behavior.
  • Added grouped AWS sidebar navigation:
    • Database
    • DynamoDB
  • Preserved disabled placeholder behavior for unsupported clouds/services.
  • Fixed Console Home active-service counting so it matches visible cards.

Tooling / test setup

  • Bootstrapped frontend Vitest + Testing Library support required by the new UI tests.
  • Ignored local-only assistant/spec artifacts in .gitignore.

Why this approach

The README makes the architecture rule explicit:

  • the UI does not know cloud internals,
  • the proxy does not know provider implementations,
  • adapters do the translation.

Because of that, DynamoDB was added as a separate normalized service instead of being merged into the existing AWS Database/RDS path.

This keeps:

  • AWS RDS behavior intact,
  • DynamoDB explicit in navigation and UX,
  • the multi-cloud adapter architecture consistent.

Out of scope

  • No attempt to unify DynamoDB with Azure/GCP database services.
  • No fake empty-state resources.
  • No broader queue/function/events work.
  • No repo-wide lint cleanup outside this change.

PR: AWS DynamoDB explorer support

This PR adds DynamoDB as a dedicated AWS service in the unified Cloud Explorer flow.

Testing

  • Backend: npm exec --package bun -- bun test packages/api/src
  • Frontend: pnpm --filter @floci/frontend test
  • Type-check: pnpm type-check

Review path

  1. Review backend DynamoDB wiring:
    • packages/api/src/services/dynamodb.ts
    • packages/api/src/adapter-aws/AwsDynamoDbAdapter.ts
    • packages/api/src/routes/clouds.ts
  2. Review frontend service exposure:
    • packages/frontend/src/types/cloud.ts
    • packages/frontend/src/pages/CloudExplorerPage.tsx
    • packages/frontend/src/features/cloud-console/useCloudConsoleHomeData.ts
  3. Review AWS navigation UX:
    • packages/frontend/src/components/Layout.tsx
    • packages/frontend/src/index.css
  4. Review tests.

Commits included

  • feat(aws): add dynamodb explorer support
  • test(frontend): add dynamodb source hygiene guard

@fredpena fredpena self-assigned this Jun 12, 2026
@hectorvent hectorvent added the enhancement New feature or request label Jun 15, 2026
@hectorvent

Copy link
Copy Markdown
Contributor

Hi @AncientGear
Thanks for you contribution.
Please resolve the conflicts.

@hectorvent hectorvent self-requested a review as a code owner June 15, 2026 18:00
@IoannisLafiotis

Copy link
Copy Markdown
Collaborator

Please take a look at the comments and the extendability of the noSQL db as well as naming and the functionality goes.

@AncientGear AncientGear force-pushed the feat/aws-dynamodb-explorer branch from 05001b5 to d711d34 Compare June 26, 2026 17:56
@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds DynamoDB as a first-class service in the unified Cloud Explorer, following the established Cloud Proxy → Adapter Registry → Provider Adapter architecture. The backend and frontend wiring is thorough and consistent with how EKS, RDS, and Lambda were added.

  • Backend: createDynamoDbService paginates ListTablesCommand then fans out DescribeTableCommand calls; AwsDynamoDbAdapter registers under aws/dynamodb; isServiceType and the schema dispatch in CloudProxyService are extended correctly.
  • Frontend: CLOUD_SERVICE.DYNAMODB added to the type union; normalizeService and useCloudConsoleHomeData wire the new card and count; a collapsible DatabaseGroupNav groups Database and DynamoDB in the AWS sidebar.
  • Tests: Backend service, adapter, schema, client, and route tests are added; frontend Vitest bootstrap and hook/component tests are included.

Confidence Score: 3/5

The DynamoDB table view will render blank values for the three service-specific columns (Billing Mode, Item Count, Size) because the adapter puts them in metadata while the resource table reads only top-level CloudResource fields.

All backend wiring — pagination, adapter registration, route gating, schema dispatch, and per-account scoping — is correct and well-tested. The frontend navigation and Console Home card behave as intended. The defect is in dynamodbSchema.ts: the three DynamoDB-specific columns reference field names (billingMode, itemCount, sizeBytes) that are stored in resource.metadata but ResourceTable only reads top-level properties (the same pattern that made version, engine, and instanceClass first-class fields on CloudResource). Until those columns resolve to real values, the table lists tables with only their name and status visible, which degrades the main benefit of the new service view.

packages/api/src/cloud-spi/dynamodbSchema.ts and packages/api/src/adapter-aws/AwsDynamoDbAdapter.ts together: the schema declares columns for billingMode, itemCount, and sizeBytes, but the adapter places those values inside metadata rather than at the top level of CloudResource.

Important Files Changed

Filename Overview
packages/api/src/cloud-spi/dynamodbSchema.ts New DynamoDB schema; the three metadata-only columns (billingMode, itemCount, sizeBytes) will always render blank in the resource table because ResourceTable reads top-level CloudResource fields only.
packages/api/src/adapter-aws/AwsDynamoDbAdapter.ts New adapter follows the established pattern; get() handles ResourceNotFoundException correctly; search filter and schema delegation are correct.
packages/api/src/services/dynamodb.ts Correct pagination loop; Promise.all fan-out is unbounded and rejects entirely on any DescribeTable failure — a potential issue when many tables exist or during table deletion.
packages/api/src/routes/clouds.ts Added 'dynamodb' to isServiceType(); no bespoke route needed — the generic /:cloud/services/:service/resources handler covers it correctly.
packages/api/src/cloudProxy.ts DynamoDB adapter registered with per-account DynamoDBClient; follows the same pattern as all other adapters.
packages/api/src/service/CloudProxyService.ts DynamoDB added to services() list and schema() dispatch; shows 'coming_soon' for Azure/GCP where no adapter is registered.
packages/frontend/src/pages/CloudExplorerPage.tsx Added 'dynamodb' to normalizeService(); no bespoke panel needed — DynamicResourceView handles it generically.
packages/frontend/src/features/cloud-console/useCloudConsoleHomeData.ts DynamoDB card and count wired correctly for AWS; dynamodbResourcesQuery.isLoading used in resourcesLoading without the cloud === 'aws' guard that secretsQuery.isLoading has.
packages/frontend/src/components/Layout.tsx New DatabaseGroupNav collapsible group correctly handles AWS-only DynamoDB link; Azure falls back to a plain Database NavItem; GCP shows a disabled placeholder.
packages/frontend/src/features/cloud-console/cloudConsoleHome.utils.ts activeServicesDetailFor and resourceDetailFor updated to mention DynamoDB; descriptive strings only, no logic impact.
packages/api/src/aws.ts DynamoDBClient added to client registry and buildClients(); awsRegion exported for reuse by the service layer.
packages/frontend/src/types/cloud.ts DYNAMODB added to CLOUD_SERVICE constant and CloudServiceType union; clean, no issues.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as Browser (React :4500)
    participant API as Hono API (:4501)
    participant CPX as CloudProxyService
    participant ADA as AwsDynamoDbAdapter
    participant SVC as DynamoDbService
    participant DDB as DynamoDB SDK (:4566)

    UI->>API: GET /api/clouds/aws/services/dynamodb/resources
    API->>CPX: "listResources('aws', 'dynamodb', {search})"
    CPX->>ADA: "list({search})"
    ADA->>SVC: listTables()
    SVC->>DDB: ListTablesCommand (paginated)
    DDB-->>SVC: TableNames[]
    loop per table name
        SVC->>DDB: DescribeTableCommand (concurrent via Promise.all)
        DDB-->>SVC: TableDescription
    end
    SVC-->>ADA: DynamoDbTable[]
    ADA->>ADA: tableToResource() + filterBySearch()
    ADA-->>CPX: CloudResource[]
    CPX-->>API: CloudResource[]
    API-->>UI: JSON array

    UI->>API: GET /api/clouds/aws/services/dynamodb/schema
    API->>CPX: schema('aws', 'dynamodb')
    CPX->>ADA: schema()
    ADA-->>CPX: ServiceSchema (columns, filters, actions)
    CPX-->>API: ServiceSchema
    API-->>UI: JSON schema drives DynamicResourceView
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as Browser (React :4500)
    participant API as Hono API (:4501)
    participant CPX as CloudProxyService
    participant ADA as AwsDynamoDbAdapter
    participant SVC as DynamoDbService
    participant DDB as DynamoDB SDK (:4566)

    UI->>API: GET /api/clouds/aws/services/dynamodb/resources
    API->>CPX: "listResources('aws', 'dynamodb', {search})"
    CPX->>ADA: "list({search})"
    ADA->>SVC: listTables()
    SVC->>DDB: ListTablesCommand (paginated)
    DDB-->>SVC: TableNames[]
    loop per table name
        SVC->>DDB: DescribeTableCommand (concurrent via Promise.all)
        DDB-->>SVC: TableDescription
    end
    SVC-->>ADA: DynamoDbTable[]
    ADA->>ADA: tableToResource() + filterBySearch()
    ADA-->>CPX: CloudResource[]
    CPX-->>API: CloudResource[]
    API-->>UI: JSON array

    UI->>API: GET /api/clouds/aws/services/dynamodb/schema
    API->>CPX: schema('aws', 'dynamodb')
    CPX->>ADA: schema()
    ADA-->>CPX: ServiceSchema (columns, filters, actions)
    CPX-->>API: ServiceSchema
    API-->>UI: JSON schema drives DynamicResourceView
Loading

Reviews (1): Last reviewed commit: "test(frontend): add dynamodb source hygi..." | Re-trigger Greptile

Comment on lines +3 to +8
const dynamodbColumns: TableColumnSchema[] = [
{name: 'name', label: 'Name'},
{name: 'status', label: 'Status'},
{name: 'billingMode', label: 'Billing Mode'},
{name: 'itemCount', label: 'Item Count'},
{name: 'sizeBytes', label: 'Size (Bytes)'},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 DynamoDB columns will always render as dashes in the resource table

The three service-specific columns — billingMode, itemCount, and sizeBytes — point to field names that don't exist at the top level of CloudResource. ResourceTable resolves columns via resource[column.name as keyof CloudResource], so any column name that isn't a known top-level property returns undefined, which formatValue renders as '-'. Compare: the EKS schema uses a version column because version is a first-class field on CloudResource, and the Database schema uses engine / instanceClass for the same reason.

The fix is either to promote billingMode, itemCount, and sizeBytes to top-level fields on CloudResource (both backend types.ts and frontend types/resource.ts) and expose them directly from tableToResource, or alternatively to teach ResourceTable to fall back to resource.metadata[column.name] when a column name isn't a recognized top-level key.

Comment on lines +31 to +44
async listTables(): Promise<DynamoDbTable[]> {
const tableNames: string[] = []
let lastEvaluatedTableName: string | undefined

do {
const response = await client.send(new ListTablesCommand({
...(lastEvaluatedTableName ? {ExclusiveStartTableName: lastEvaluatedTableName} : {}),
}))
tableNames.push(...(response.TableNames ?? []))
lastEvaluatedTableName = response.LastEvaluatedTableName
} while (lastEvaluatedTableName)

// ListTables only returns table names, so describe each table to populate the CloudResource metadata used by the explorer.
return Promise.all(tableNames.map((tableName) => this.describeTable(tableName)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Concurrent DescribeTable fan-out lacks error isolation

Promise.all rejects as soon as any single DescribeTable call throws. If one table transitions to a terminal state between ListTables and DescribeTable (e.g., DELETING finishing just after the list), the entire listing request fails rather than returning partial results. For a local emulator this is rare, but the call fan-out is unbounded: an account with 200 tables fires 200 concurrent DescribeTable requests before any result is returned. Wrapping each call in Promise.allSettled and filtering fulfilled results would make the listing more resilient.

Comment on lines 102 to +106
const resourcesLoading = storageResourcesQuery.isLoading
|| k8sResourcesQuery.isLoading
|| databaseResourcesQuery.isLoading
|| (cloud === 'aws' && secretsQuery.isLoading)
|| dynamodbResourcesQuery.isLoading

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Inconsistent cloud-guard on dynamodbResourcesQuery.isLoading

secretsQuery.isLoading is guarded with cloud === 'aws' before being OR-ed into resourcesLoading, but dynamodbResourcesQuery.isLoading is included without a guard. The query is disabled for non-AWS clouds via hasAvailableService, so in React Query v5 isLoading is false for disabled queries and this has no runtime impact — but the asymmetry is surprising to readers and could silently cause resourcesLoading to stay true longer than expected if the React Query version or the enabled logic changes. Adding (cloud === 'aws' && dynamodbResourcesQuery.isLoading) matches the pattern used for secretsQuery.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants