Skip to content

Feature/3754 timebound access revoke#3920

Open
HannesDS wants to merge 24 commits into
mainfrom
feature/3754-timebound-access-revoke
Open

Feature/3754 timebound access revoke#3920
HannesDS wants to merge 24 commits into
mainfrom
feature/3754-timebound-access-revoke

Conversation

@HannesDS

@HannesDS HannesDS commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator
  • Update the release notes document if needed in the release notes.
  • When updating the UI please provide screenshots or videos to the reviewers.
  • Updated sample data so the new feature can be easily tested.

#3754
Add revoke/cancel actions for input port access

Producers could only end a consumer's access by hard-deleting the
input port link, and denying a request doubled as a way to pull
already-approved access, which didn't reflect what actually happened.
Consumers had the same ambiguity: cancelling a pending request and
giving up approved access were both the same hard delete.

Introduce revoke (ends an approved grant, available to either side)
and cancel (withdraws a still-pending request, consumer-only) as
first-class, non-destructive actions. Revoke sets revoked_at /
revoked_by_id on the approved request without touching its decision;
cancel moves the request's decision to a new CANCELLED value on a
dedicated InputPortRequestDecision enum, kept separate from the
shared DecisionStatus used by other approval flows. Deny is now
scoped to pending requests only.

recompute_status and current_request were reworked so a pending
request never hides a link's real prior outcome (Expired/Revoked/
Denied) on its own - only a first-ever request, or a fresh re-ask
after a plain denial, still reports Pending, since nothing else
would otherwise signal that a decision is awaited.

The UI only ever exposes revoke and cancel. The original hard-delete
routes (producer-side /remove, consumer-side DELETE) stay alive in
the backend as a deliberate two-way door, unreferenced by any button,
in case fully removing an input port ever turns out to be genuinely
needed.

Input Port table view (consumer side)
image

Consumers table view (producer side)
image

HannesDS and others added 18 commits July 16, 2026 08:23
We made the backend return new objects for
input ports and users my requests and pending requests
  The backend split input ports into a link plus time-bound access
  requests (ADR-0021) and now reports an InputPortStatus (pending,
  approved, denied, expired) alongside a renewal status. This adapts
  the frontend and seed data to that model.

  - Add a Status column to the consumer and producer input-port
    tables with a tag for renewal pending, renewal declined, or
    expiring soon, plus an Expiry date column (blank while
    pending/denied, 'Permanent Access' when open-ended, else the date).
  - Migrate the input-port display path from DecisionStatus to
    InputPortStatus so expired grants render correctly.
  - Add an id to the user input-port response so a pending action can
    be matched back to its output-port row.
  - Seed all seven access states for John Scientist from both the
    consumer and producer perspective.
  - Fix renewal_status to require a prior grant, not just a non-
    approved latest request, so first-time pending/denied requests
    no longer show a false renewal tag.
  - Split RenewalTag into RenewalTag (renewal-status only) and
    IsExpiringSoonTag per review comment.
  - Move access-duration-type lookup onto OutputPort and restore the
    PERMANENT short-circuit; it must never require a DB row, since
    access_durations rows aren't guaranteed to exist for every type.
  - Add a check constraint enforcing days is set iff time_bound.
  - Seed an 'expiring soon' input port so the new tag is testable.
  - Add a renewal_status test suite and auto-provision the matching
    AccessDuration row from OutputPortFactory so existing tests don't
    need updating
  Renewing access previously reused the "request new access"
  endpoint and service method (existing links were silently
  updated instead of rejected), which was flagged in review as
  conflating two distinct actions.

  Split AbstractDataProductService into add/renew methods and
  add a dedicated renew endpoint on both the data products and
  explorations routers. Surface it as a Renew button in the
  input-port table for links that are expired or expiring soon.
  Color was overloaded across the status badge and renewal/expiry
  tags, making the real status hard to scan. Now only the status
  badge carries color (expired is red, requested is grey); renewal
  and expiring-soon indicators are neutral icon+text instead of
  colored tags. 'Expiring soon' moved to the Expiry date column,
  and the renewal tag is hidden when status is already 'Requested'
  since that's redundant for end users.
  Producers could only end a consumer's access by hard-deleting the
  input port link, and denying a request doubled as a way to pull
  already-approved access, which didn't reflect what actually happened.
  Consumers had the same ambiguity: cancelling a pending request and
  giving up approved access were both the same hard delete.

  Introduce revoke (ends an approved grant, available to either side)
  and cancel (withdraws a still-pending request, consumer-only) as
  first-class, non-destructive actions. Revoke sets revoked_at /
  revoked_by_id on the approved request without touching its decision;
  cancel moves the request's decision to a new CANCELLED value on a
  dedicated InputPortRequestDecision enum, kept separate from the
  shared DecisionStatus used by other approval flows. Deny is now
  scoped to pending requests only.

  recompute_status and current_request were reworked so a pending
  request never hides a link's real prior outcome (Expired/Revoked/
  Denied) on its own - only a first-ever request, or a fresh re-ask
  after a plain denial, still reports Pending, since nothing else
  would otherwise signal that a decision is awaited.

  The UI only ever exposes revoke and cancel. The original hard-delete
  routes (producer-side /remove, consumer-side DELETE) stay alive in
  the backend as a deliberate two-way door, unreferenced by any button,
  in case fully removing an input port ever turns out to be genuinely
  needed.

  Updates ADR-0021 and adds test coverage for the full status/renewal
  display matrix on both backend and frontend.

Copilot AI 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.

Pull request overview

Adds first-class, non-destructive “revoke” (end an approved grant) and “cancel” (withdraw a pending request) actions for Input Port access, updating backend state handling (revocation tracking + request decision enum), regenerating clients/SDK/CLI, and aligning the UI to expose revoke/cancel/renew actions with updated status display.

Changes:

  • Backend: introduce revoke/cancel/renew endpoints, add revocation tracking to input_port_requests, and separate request decision enum from shared approval enums.
  • Frontend: update input-port tables/actions, status labeling/badges, and add tests for access status and action button behavior.
  • Regenerated clients: update OpenAPI-derived frontend services, Python SDK models/apis, and Go CLI validators/params/ops.

Reviewed changes

Copilot reviewed 65 out of 78 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
sdk/sdk/api_client/models/revoke_output_port_as_input_port_request.py Adds SDK request model for producer-side revoke endpoint.
sdk/sdk/api_client/models/revoke_input_port_for_exploration_response.py Adds SDK response model for exploration revoke.
sdk/sdk/api_client/models/revoke_input_port_for_data_product_response.py Adds SDK response model for data product revoke.
sdk/sdk/api_client/models/renew_input_port_for_exploration_response.py Adds SDK response model for exploration renew.
sdk/sdk/api_client/models/renew_input_port_for_data_product_response.py Adds SDK response model for data product renew.
sdk/sdk/api_client/models/input_port_status.py Extends SDK status enum with revoked/cancelled.
sdk/sdk/api_client/models/input_port_request.py Switches decision enum, changes valid_until to date, adds revoke metadata fields.
sdk/sdk/api_client/models/input_port_request_decision.py Introduces dedicated SDK enum for input port request decisions.
sdk/sdk/api_client/models/input_port_request_base.py Mirrors InputPortRequest base changes (decision enum/date/revoke fields).
sdk/sdk/api_client/models/cancel_input_port_for_exploration_response.py Adds SDK response model for exploration cancel.
sdk/sdk/api_client/models/cancel_input_port_for_data_product_response.py Adds SDK response model for data product cancel.
sdk/sdk/api_client/models/init.py Exports newly added SDK models.
sdk/sdk/api_client/api/explorations/revoke_input_port_for_exploration.py Adds SDK API call for exploration revoke endpoint.
sdk/sdk/api_client/api/explorations/renew_input_port_for_exploration.py Adds SDK API call for exploration renew endpoint.
sdk/sdk/api_client/api/explorations/remove_input_port_for_exploration.py Updates naming in generated docs to “For Exploration”.
sdk/sdk/api_client/api/explorations/cancel_input_port_for_exploration.py Adds SDK API call for exploration cancel endpoint.
sdk/sdk/api_client/api/data_products/revoke_input_port_for_data_product.py Adds SDK API call for data product revoke endpoint.
sdk/sdk/api_client/api/data_products/renew_input_port_for_data_product.py Adds SDK API call for data product renew endpoint.
sdk/sdk/api_client/api/data_products/remove_input_port_for_data_product.py Updates naming in generated docs to “For Data Product”.
sdk/sdk/api_client/api/data_products/cancel_input_port_for_data_product.py Adds SDK API call for data product cancel endpoint.
sdk/sdk/api_client/api/data_products_output_ports_input_ports/revoke_output_port_as_input_port.py Adds SDK API call for producer-side revoke.
frontend/src/utils/status.helper.ts Adds labels/badge mappings for revoked/cancelled and request decision union typing.
frontend/src/tests/mocks/explorations.ts Updates mocks to use InputPortRequestDecision.
frontend/src/tests/mocks/dataProducts.ts Updates mocks to use InputPortRequestDecision.
frontend/src/tests/components/input-port/access-status.test.tsx Adds unit tests for expiring/renewal display and expiry date rendering.
frontend/src/tests/components/abstract-data-products/input-port-table/input-port-action-button.test.tsx Adds unit tests for renew/cancel/revoke button visibility logic.
frontend/src/store/api/services/tags/explorationTags.ts Adds tag invalidation for exploration renew/revoke/cancel mutations.
frontend/src/store/api/services/tags/dataProductTags.ts Adds tag invalidation for data product revoke/cancel/renew mutations.
frontend/src/store/api/services/tags/dataProductsOutputPortsInputPortsTags.ts Renames remove→revoke tags wiring for producer-side revoke mutation.
frontend/src/store/api/services/generated/usersApi.ts Regenerates request decision type + adds revoke metadata on request objects.
frontend/src/store/api/services/generated/explorationsApi.ts Regenerates to add renew/revoke/cancel exploration mutations and enums.
frontend/src/store/api/services/generated/dataProductsOutputPortsInputPortsApi.ts Regenerates to add producer-side revoke mutation and enums.
frontend/src/store/api/services/generated/dataProductsApi.ts Regenerates to add data product renew/revoke/cancel mutations and enums.
frontend/src/pages/product-studio/components/requests/request-utils.ts Widens decision typing to include InputPortRequestDecision.
frontend/src/pages/product-studio/components/my-requests-tab/use-table-columns.tsx Adjusts decision renderer typing for mixed decision enums.
frontend/src/pages/exploration/components/exploration-input-ports.tsx Wires exploration UI to cancel/revoke/renew endpoints.
frontend/src/pages/dataset/components/dataset-tabs/consumers-tab/components/consumers-table/revoke-access-modal.tsx Removes decision-note revoke modal (revoke now confirmation-only).
frontend/src/pages/dataset/components/dataset-tabs/consumers-tab/components/consumers-table/consumers-table.component.tsx Switches producer UI from hard-delete/deny-as-revoke to proper revoke endpoint.
frontend/src/pages/dataset/components/dataset-tabs/consumers-tab/components/consumers-table/consumers-table-columns.tsx Updates producer table actions/status layout and adds revoke confirmation.
frontend/src/pages/data-product/components/data-product-tabs/input-ports/input-port-tab.tsx Wires data product UI to cancel/revoke/renew endpoints.
frontend/src/components/input-port/access-status.tsx Updates renewal/expiring display and exports helpers for reuse/testing.
frontend/src/components/abstract-data-products/input-port-tab/input-port-tab.tsx Updates tab props to pass cancel/revoke/renew handlers.
frontend/src/components/abstract-data-products/input-port-tab/components/input-port-table/input-port-table.component.tsx Threads new handler props into table column builder.
frontend/src/components/abstract-data-products/input-port-tab/components/input-port-table/input-port-table-columns.tsx Uses new action button component + passes validity/renewal state.
frontend/src/components/abstract-data-products/input-port-tab/components/input-port-table/input-port-action-button.component.tsx New unified cancel/revoke/renew button component with confirmation prompts.
frontend/src/components/abstract-data-products/input-port-tab/components/input-port-table/input-port-action-botton.component.tsx Removes old “remove/cancel” mixed-action button component.
frontend/public/locales/en/translation.json Adds/removes i18n keys for revoke/cancel/renew UI messaging.
docs/adr/0021-input-ports-and-input-port-requests.md Updates ADR for revoke/cancel semantics and expanded status/decision model.
cli/golang/pkg/api/oas_validators_gen.go Regenerates CLI validators for new enums/fields.
cli/golang/pkg/api/oas_request_encoders_gen.go Regenerates encoder for revoke request body.
cli/golang/pkg/api/oas_parameters_gen.go Regenerates operation parameter structs for new endpoints.
cli/golang/pkg/api/oas_operations_gen.go Regenerates operation name constants for new endpoints.
cli/golang/pkg/api/oas_interfaces_gen.go Regenerates response interfaces for new endpoints.
backend/tests/factories/input_port.py Updates factories for new decision enum + revoked metadata on requests.
backend/tests/factories/input_port_request.py Updates factory decision type and decided_by logic.
backend/tests/app/explorations/test_router.py Adds tests for exploration renew/revoke/cancel + hard-delete behavior.
backend/tests/app/data_products/output_ports/input_ports/test_service.py Adds/updates service tests for deny restrictions and revoke behavior.
backend/tests/app/data_products/output_ports/input_ports/test_router.py Adds/updates router tests for renew/revoke/cancel and hard-delete behavior.
backend/tests/app/abstract_data_product/test_request_input_ports.py Splits “request” vs “renew” behavior and adds revoke/cancel tests.
backend/tests/app/abstract_data_product/input_ports/test_model.py Expands model tests for recompute/current_request semantics (revoked/cancelled).
backend/app/explorations/schema_response.py Adds typed responses for exploration renew/revoke/cancel endpoints.
backend/app/explorations/router.py Implements exploration renew/revoke/cancel endpoints with ownership checks.
backend/app/events/enums.py Adds event types for revoked/cancelled link events.
backend/app/database/alembic/versions/2026_07_22_1200-c3d8a1f56e42_add_revoke_tracking_to_input_port_requests.py Adds DB columns + FK for revoked_at/revoked_by_id.
backend/app/data_products/schema_response.py Adds typed responses for data product renew/revoke/cancel endpoints.
backend/app/data_products/router.py Adds consumer-side renew/revoke/cancel routes and centralizes request notifications/events.
backend/app/data_products/output_ports/input_ports/service.py Scopes deny to pending-only and adds revoke service method.
backend/app/data_products/output_ports/input_ports/schema.py Updates schema types for date-based valid_until + revoke metadata + new decision enum.
backend/app/data_products/output_ports/input_ports/schema_request.py Adds schema for producer-side revoke request body.
backend/app/data_products/output_ports/input_ports/router.py Adds producer-side revoke endpoint and adjusts remove/deny logic to match new semantics.
backend/app/abstract_data_product/service.py Refactors request vs renew flows; implements revoke/cancel operations.
backend/app/abstract_data_product/input_ports/model.py Implements new recompute/current_request logic; adds revoke tracking fields/relations.
backend/app/abstract_data_product/input_ports/enums.py Adds REVOKED/CANCELLED link status and dedicated request decision enum.
Files not reviewed (9)
  • cli/golang/pkg/api/oas_client_gen.go: Generated file
  • cli/golang/pkg/api/oas_interfaces_gen.go: Generated file
  • cli/golang/pkg/api/oas_json_gen.go: Generated file
  • cli/golang/pkg/api/oas_operations_gen.go: Generated file
  • cli/golang/pkg/api/oas_parameters_gen.go: Generated file
  • cli/golang/pkg/api/oas_request_encoders_gen.go: Generated file
  • cli/golang/pkg/api/oas_response_decoders_gen.go: Generated file
  • cli/golang/pkg/api/oas_schemas_gen.go: Generated file
  • cli/golang/pkg/api/oas_validators_gen.go: Generated file
Comments suppressed due to low confidence (1)

backend/app/data_products/router.py:514

  • This notification guard uses input_port.status == APPROVED, which is true even when a renewal request is pending on top of an active grant. That can produce an "approved" notification even though the newly created request is still pending.
    for input_port, event_id in zip(input_ports, event_ids):
        if input_port.status == InputPortStatus.APPROVED:
            NotificationService(db).create_data_product_notifications(
                data_product_id=input_port.consuming_abstract_data_product_id,
                event_id=event_id,
            )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/app/data_products/router.py
Comment thread backend/app/data_products/router.py Outdated
Comment thread docs/adr/0021-input-ports-and-input-port-requests.md Outdated
HannesDS added 4 commits July 23, 2026 15:42
  The 'My Requests' history showed 'Available' for input port access
  that had already been revoked, because the request's decision stays
  APPROVED forever (a historical fact) while revocation is tracked
  separately via revoked_at. Status helpers now check revoked_at
  before falling back to the decision label.

  Also removes an unreachable status assignment in approve_request
  (immediately overwritten by recompute_status() in both callers), an
  orphaned pre-ADR-0021 schema pair still importing the shared
  DecisionStatus enum, and a input_port_count filter that compared
  against DecisionStatus instead of InputPortStatus.
@HannesDS
HannesDS marked this pull request as ready for review July 24, 2026 08:19
@HannesDS
HannesDS requested a review from nclaeys as a code owner July 24, 2026 08:19

@stijn-janssens stijn-janssens left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall really nice PR, i think the eventing is the only major remark I have. The rest are smaller comments

Comment thread frontend/src/utils/status.helper.ts Outdated
Comment thread backend/app/events/enums.py
Add DATA_PRODUCT_DATASET_LINK_REVOKED/CANCELLED to the frontend event
types and history helper (backend enum had them, frontend didn't).
Drop console.error calls from input port actions since nothing else in
the app logs to console. Replace the ad hoc 'cancelled' string literal
with a proper InputPortRequestDecision enum, mirroring the existing
DecisionStatus wrapper, and type the input port status helpers with
InputPortStatus instead of a loose string.
@HannesDS
HannesDS requested a review from stijn-janssens July 24, 2026 14:26
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.

4 participants