Skip to content

[2 of 2] ENG-3121: Google Workspace integration, identity resolution UI, dynamic consumer scopes#7832

Open
galvana wants to merge 4 commits intoplatform-identity-resolutionfrom
platform-data-consumer-ui
Open

[2 of 2] ENG-3121: Google Workspace integration, identity resolution UI, dynamic consumer scopes#7832
galvana wants to merge 4 commits intoplatform-identity-resolutionfrom
platform-data-consumer-ui

Conversation

@galvana
Copy link
Copy Markdown
Contributor

@galvana galvana commented Apr 6, 2026

Ticket ENG-3121

Description Of Changes

Adds Google Workspace as a new integration type for identity group resolution via Google Groups. Adds an "Identity resolution" tab to BigQuery, Google Workspace, and Mock integration pages. Replaces the hardcoded consumer type dropdown with dynamic types from configured identity group providers, and scope fields with selects populated from real groups/roles/service accounts. Mock integration is gated to development environments only.

Code Changes

  • New google_workspace connection type with GoogleWorkspaceSchema (keyfile + delegation_subject + domain)
  • GoogleWorkspaceConnector for connection testing (credential validation only, no DSR)
  • Alembic migration adding google_workspace to the PostgreSQL connectiontype enum
  • IDENTITY_RESOLUTION and QUERY_LOGGING added to IntegrationFeature enum
  • Identity Resolution tab component (IdentityResolutionTab.tsx) — enable/disable toggle + test connection
  • Identity group provider RTK Query slice (CRUD + test endpoints)
  • Consumer form: type dropdown grouped by platform (Google Workspace, GCP), scope select from GET /plus/pbac/available-scopes
  • Consumer table: scope column shows primary identifier (group email, role name, service account email)
  • Removed contact_email and tags from consumer form and table
  • useConsumerTypeOptions hook for fetching and grouping dynamic consumer types
  • Google Workspace integration info page with setup instructions
  • Mock integration (test_datastore) gated to NEXT_PUBLIC_APP_ENV=development, maps to mock provider type

Steps to Confirm

  1. Start dev stack with nox -s "dev(slim)" -- fides-pkg fides-admin-ui
  2. Navigate to Integrations → Add → verify Google Workspace appears under Identity Provider
  3. In development mode, verify "Mock integration" appears with Identity Resolution tab
  4. Configure a BigQuery integration → verify "Identity resolution" tab appears
  5. Configure a Google Workspace integration → verify "Identity resolution" tab appears
  6. Enable identity resolution on both → test connection
  7. Navigate to Data Consumers → Add → verify type dropdown shows grouped platform types
  8. Select GCP IAM Role → verify scope dropdown shows real IAM roles from the project
  9. Create consumer → verify scope persists on edit, name field appears after scope
  10. Change type → verify scope clears

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Add a db-migration label to the entry if your change includes a DB migration
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • All UX related changes have been reviewed by a designer
  • Followup issues:
    • No followup issues
  • Database migrations:
    • Ensure that your downrev is up to date with the latest revision on main
    • Ensure that your downgrade() migration is correct and works
  • Documentation:
    • No documentation updates required

…ynamic consumer UI

- New connection type: google_workspace with GoogleWorkspaceSchema (keyfile + delegation_subject + domain)
- GoogleWorkspaceConnector for connection testing
- Alembic migration adding google_workspace to ConnectionType enum
- IDENTITY_RESOLUTION IntegrationFeature for BigQuery and Google Workspace integrations
- Identity Resolution tab on integration pages (enable/disable + test connection)
- Identity group provider RTK Query slice (CRUD + test)
- Consumer form: dynamic type dropdown (grouped by platform), scope select from available-scopes API
- Consumer table: scope column shows primary identifier, removed contact_email and tags columns
- useConsumerTypeOptions hook for fetching and grouping dynamic consumer types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@galvana galvana requested review from a team as code owners April 6, 2026 02:56
@galvana galvana requested review from dsill-ethyca and jpople and removed request for a team April 6, 2026 02:56
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
fides-plus-nightly Ignored Ignored Preview Apr 9, 2026 10:02pm
fides-privacy-center Ignored Ignored Apr 9, 2026 10:02pm

Request Review

@galvana galvana requested review from lucanovera and thabofletcher and removed request for dsill-ethyca and jpople April 6, 2026 02:57
@galvana galvana changed the title Add Google Workspace integration + identity resolution UI + dynamic consumer scopes [3 of 4] ENG-3121: Google Workspace integration, identity resolution UI, dynamic consumer scopes Apr 6, 2026
@galvana galvana changed the title [3 of 4] ENG-3121: Google Workspace integration, identity resolution UI, dynamic consumer scopes [2 of 2] ENG-3121: Google Workspace integration, identity resolution UI, dynamic consumer scopes Apr 6, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Title Lines Statements Branches Functions
admin-ui Coverage: 7%
5.86% (2547/43433) 4.87% (1199/24608) 3.94% (508/12863)
fides-js Coverage: 78%
78.98% (1962/2484) 65.55% (1214/1852) 72.57% (336/463)
privacy-center Coverage: 88%
85.93% (330/384) 81.1% (176/217) 78.87% (56/71)

Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Code Review — PR #7832 (Google Workspace integration + Data Consumer scope)

Overall this is a well-structured PR that adds the Google Workspace connector and refactors data consumer types from a static enum to a dynamic API-driven model. The architecture is sound and the separation of concerns is clean. A few issues worth addressing before merge:

Must fix

  • test_connection has no exception handling — raw Google auth exceptions will leak out rather than returning ConnectionTestStatus.failed. See inline comment.
  • Migration filename uses xx_ prefix — non-standard and may break Alembic discovery tooling. See inline comment.
  • providesTags: ["DataConsumer"] on getAvailableScopes — causes spurious refetches on every consumer mutation. See inline comment.

Should fix

  • Lazy imports in GoogleWorkspaceConnectorfrom google.oauth2 import service_account and from google.auth.transport.requests import Request inside method bodies should move to the module top level.
  • Magic string "google_workspace" in IdentityResolutionTab.tsx — use ConnectionType.GOOGLE_WORKSPACE from the generated types enum.
  • Duplicated scope-display logicgetDisplayNameForScope in the form and the inline scope.group_email ?? scope.role ?? ... in the table are the same logic. Extract to a shared util.

Minor

  • Hidden Form.Item using <Input /> — the pattern works but is fragile; consider tracking scope value in local state and merging at submit instead.
  • PROVIDER_TYPE_OPTIONS export appears unused — remove or add a comment explaining intent.


def create_client(self) -> Any:
config = GoogleWorkspaceSchema(**self.configuration.secrets)
from google.oauth2 import service_account
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lazy imports inside method bodies are discouraged — they make import errors harder to catch at startup and are inconsistent with the rest of the codebase. Move from google.oauth2 import service_account (and the google.auth import in test_connection) to the top of the module alongside the other imports.

from google.auth.transport.requests import Request as AuthRequest

creds = self.create_client()
creds.refresh(AuthRequest())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

test_connection has no exception handling. If creds.refresh() raises a Google auth exception (e.g. google.auth.exceptions.TransportError, DefaultCredentialsError), it will propagate as an unhandled exception rather than returning ConnectionTestStatus.failed. Other connectors catch exceptions here and return the failed status. Wrap this in a try/except to conform to the expected interface:

def test_connection(self) -> Optional[ConnectionTestStatus]:
    from google.auth.exceptions import GoogleAuthError
    from google.auth.transport.requests import Request as AuthRequest

    try:
        creds = self.create_client()
        creds.refresh(AuthRequest())
        return ConnectionTestStatus.succeeded
    except GoogleAuthError as e:
        logger.error("Google Workspace connection test failed: %s", e)
        return ConnectionTestStatus.failed

return "google_workspace";
}
return "gcp";
}, [integration.connection_type]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Magic string — use ConnectionType.GOOGLE_WORKSPACE from ~/types/api instead of the literal "google_workspace". This avoids a silent mismatch if the value ever changes and makes the intent clear:

import { ConnectionType } from "~/types/api";

const providerType = useMemo(() => {
  if (integration.connection_type === ConnectionType.GOOGLE_WORKSPACE) {
    return "google_workspace";
  }
  return "gcp";
}, [integration.connection_type]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The migration filename starts with xx_, which is non-standard. Other migrations in this repo use the revision hash prefix (e.g. b3c8d5e7f2a1_...). Please rename to match the convention so Alembic can discover it correctly and CI tooling doesn't skip it.

): string => {
if (type === "google_group") {
return scope.group_email ?? "";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The getDisplayNameForScope function here and the inline scope label logic in useDataConsumersTable.tsx (the scope.group_email ?? scope.role ?? scope.email ?? ... pattern) are effectively the same logic duplicated in two places. Consider extracting this into a shared utility (e.g. in constants.ts or a scopeUtils.ts) so both the form and the table stay in sync if the set of scope key names ever changes.

<Select
placeholder="Select scope"
options={allScopeOptions}
value={selectedScopeValue}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using a visible <Input /> as the child of a hidden Form.Item whose value is a Record<string, string> is a fragile pattern. Ant Design's Form.Item expects its child to be a compatible control, but here the actual value is set externally via form.setFieldsValue({ scope }) — the <Input /> is a no-op placeholder. If anything ever reads form.getFieldValue("scope") expecting an Input value this will silently return the object. A cleaner approach is to use a custom controlled component or just track the scope value in local state and merge it into the payload at submit time.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 6, 2026

Codecov Report

❌ Patch coverage is 68.42105% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.44%. Comparing base (389fb53) to head (5834b50).

Files with missing lines Patch % Lines
...i/service/connectors/google_workspace_connector.py 54.54% 15 Missing ⚠️
...nfiguration/connection_secrets_google_workspace.py 83.33% 3 Missing ⚠️

❌ Your patch check has failed because the patch coverage (68.42%) is below the target coverage (100.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your project check has failed because the head coverage (82.44%) is below the target coverage (85.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@                       Coverage Diff                        @@
##           platform-identity-resolution    #7832      +/-   ##
================================================================
- Coverage                         82.94%   82.44%   -0.50%     
================================================================
  Files                               624      615       -9     
  Lines                             40498    40099     -399     
  Branches                           4711     4667      -44     
================================================================
- Hits                              33592    33061     -531     
- Misses                             5827     5980     +153     
+ Partials                           1079     1058      -21     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…e to mock provider

- Mock integration only shown when NEXT_PUBLIC_APP_ENV=development
- Identity resolution tab maps test_datastore connection to mock provider type
- Updated description text for mock provider

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.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.

1 participant