Skip to content

add request access button to database, schema, and dashboard cards [WIP]#27014

Open
SaaiAravindhRaja wants to merge 6 commits intoopen-metadata:mainfrom
SaaiAravindhRaja:feat/issue-25575-request-access-button
Open

add request access button to database, schema, and dashboard cards [WIP]#27014
SaaiAravindhRaja wants to merge 6 commits intoopen-metadata:mainfrom
SaaiAravindhRaja:feat/issue-25575-request-access-button

Conversation

@SaaiAravindhRaja
Copy link
Copy Markdown

@SaaiAravindhRaja SaaiAravindhRaja commented Apr 3, 2026

Summary

  • add a shared request-access URL resolver and hook backed by service connectionOptions
  • show a Request Access button on explore cards and entity headers for supported assets
  • add focused unit tests for the resolver, hook, and UI surfaces

Notes

  • supported connection option keys: requestAccessUrl, requestAccessUrlTemplate
  • supported asset types: database, databaseSchema, dashboard

Test Plan

  • git diff --check
  • node -e "JSON.parse(require('fs').readFileSync('openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json','utf8')); console.log('json_ok')"
  • yarn test --runInBand src/utils/RequestAccessUtils.test.ts
  • yarn test --runInBand src/hooks/useRequestAccessUrl.test.ts
  • remaining frontend verification was not completed in this environment because repo-generated frontend artifacts and broader UI typecheck issues are present locally

Fixes #25575

Copilot AI review requested due to automatic review settings April 3, 2026 07:17
@SaaiAravindhRaja SaaiAravindhRaja requested a review from a team as a code owner April 3, 2026 07:17
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a reusable “Request Access” URL resolver + hook driven by service connectionOptions, and surfaces a Request Access button on Explore cards and Data Asset headers for supported entities (database, schema, dashboard), with accompanying unit tests.

Changes:

  • Added RequestAccessUtils to resolve a direct URL or a templated URL with encoded entity placeholders.
  • Added useRequestAccessUrl hook that loads service connectionOptions via getServiceByFQN and resolves a request-access URL.
  • Rendered “Request Access” CTA on Explore search cards and Data Asset headers; added unit tests and an i18n string.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.ts New resolver for request-access URLs from connection options.
openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.test.ts Unit tests for resolver behavior and precedence rules.
openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json Adds label.request-access translation string.
openmetadata-ui/src/main/resources/ui/src/hooks/useRequestAccessUrl.ts New hook to fetch service connection options and resolve request-access URL.
openmetadata-ui/src/main/resources/ui/src/hooks/useRequestAccessUrl.test.ts Unit tests for hook behavior across supported/unsupported entities.
openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx Displays “Request Access” action on Explore cards when URL is available.
openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.test.tsx Verifies the CTA renders when the hook returns a URL.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx Displays “Request Access” CTA in the entity header when URL is available.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.test.tsx Verifies the CTA renders when the hook returns a URL.

Comment on lines +41 to +45
const directUrl = connectionOptions?.[REQUEST_ACCESS_URL]?.trim();

if (directUrl) {
return directUrl;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

getRequestAccessUrl returns whatever is in connectionOptions (or template output) without validating that it’s a safe URL. Because this value is later used as an href, a misconfigured/malicious value like javascript:... could lead to an XSS/open-redirect style issue on click. Consider validating the resolved URL (e.g., allow only http/https) and returning null when invalid, similar to the isSafeUrl pattern used elsewhere in the UI.

Copilot uses AI. Check for mistakes.
Comment on lines +258 to +262
const { url: requestAccessUrl } = useRequestAccessUrl({
entityFqn: source.fullyQualifiedName,
entityType: source.entityType as EntityType,
serviceName: source.service?.name,
serviceType: source.service?.type,
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Calling useRequestAccessUrl from inside each Explore search card will trigger an extra getServiceByFQN request per rendered card for supported entity types. On Explore pages with many results, this can easily become an N+1 pattern and add significant latency/load. Consider caching request-access URL per (serviceCategory, serviceName) or moving the service lookup to a higher level and passing the resolved URL down to cards.

Copilot uses AI. Check for mistakes.
Comment on lines +428 to +432
<Typography.Link
href={requestAccessUrl}
rel="noreferrer"
target="_blank">
<Button data-testid="request-access-button" type="link">
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The request-access link uses target="_blank" with rel="noreferrer" only. For tabnabbing protection and consistency with other external links in the codebase, include noopener as well (i.e., rel="noopener noreferrer"). Also consider ensuring the URL is validated/sanitized before assigning it to href (see existing safe URL handling in CustomPropertyTable/PropertyValue.tsx).

Copilot uses AI. Check for mistakes.
Comment on lines +698 to +702
<Typography.Link
href={requestAccessUrl}
rel="noreferrer"
target="_blank">
<Button
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The request-access link opens in a new tab but sets rel="noreferrer" only. Please add noopener (rel="noopener noreferrer") to prevent tabnabbing. Also, since requestAccessUrl comes from service connection options, validate/sanitize it before using it as an href to avoid javascript:/invalid URL injection on click.

Copilot uses AI. Check for mistakes.
Comment on lines 1650 to 1654
"reply-lowercase-plural": "replies",
"reposition": "Reposition",
"request": "Request",
"request-access": "Request Access",
"request-method": "Request Method",
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

A new translation key was added only to en-us.json. The UI repo includes an i18n sync script (yarn i18n) that keeps all locale JSON files in sync; without running it, other locales will be missing label.request-access and may regress (show raw keys) when users switch languages. Please run the i18n sync and commit the updated locale files.

Copilot uses AI. Check for mistakes.
@SaaiAravindhRaja SaaiAravindhRaja changed the title Fixes #25575: add request access button to database, schema, and dashboard cards add request access button to database, schema, and dashboard cards Apr 3, 2026
@SaaiAravindhRaja SaaiAravindhRaja marked this pull request as draft April 4, 2026 05:35
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@SaaiAravindhRaja SaaiAravindhRaja changed the title add request access button to database, schema, and dashboard cards add request access button to database, schema, and dashboard cards [WIP] Apr 4, 2026
@SaaiAravindhRaja SaaiAravindhRaja marked this pull request as ready for review April 4, 2026 11:16
Copilot AI review requested due to automatic review settings April 4, 2026 11:16
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comment on lines +21 to +22
const connectionOptionsCache = new Map<string, Record<string, string> | undefined>();

Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

connectionOptionsCache is a module-level Map that is never cleared. In long-lived sessions (or in tests that mount many different services) this can grow without bound and retain stale configuration. Consider adding an eviction/TTL strategy, scoping the cache to a provider, or switching to a request cache with lifecycle (e.g., react-query).

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +91
const cacheKey = `${serviceCategory}::${serviceName}`;
let connectionOptions: Record<string, string> | undefined;

if (connectionOptionsCache.has(cacheKey)) {
connectionOptions = connectionOptionsCache.get(cacheKey);
} else {
const service = await getServiceByFQN(serviceCategory, serviceName);
connectionOptions = get(
service,
'connection.config.connectionOptions'
) as Record<string, string> | undefined;
connectionOptionsCache.set(cacheKey, connectionOptions);
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

The cache only stores resolved connectionOptions. If multiple cards/headers for the same service mount at once (common on Explore), they will all miss the cache and each trigger getServiceByFQN, causing duplicate network calls. Consider caching the in-flight Promise (or using a shared data-fetching layer) so concurrent consumers dedupe to a single request per service.

Copilot uses AI. Check for mistakes.
Comment on lines +428 to +435
<Typography.Link
href={requestAccessUrl}
rel="noopener noreferrer"
target="_blank">
<Button data-testid="request-access-button" type="link">
{t('label.request-access')}
</Button>
</Typography.Link>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

This renders a <Typography.Link> (anchor) that wraps an Ant <Button> (button). Nesting interactive elements (<a><button/></a>) is invalid HTML and can cause accessibility/keyboard issues. Prefer rendering a single interactive element (e.g., Button with href/target/rel, or a Link styled as a button).

Suggested change
<Typography.Link
href={requestAccessUrl}
rel="noopener noreferrer"
target="_blank">
<Button data-testid="request-access-button" type="link">
{t('label.request-access')}
</Button>
</Typography.Link>
<Button
data-testid="request-access-button"
href={requestAccessUrl}
rel="noopener noreferrer"
target="_blank"
type="link">
{t('label.request-access')}
</Button>

Copilot uses AI. Check for mistakes.
Comment on lines +698 to +707
<Typography.Link
href={requestAccessUrl}
rel="noopener noreferrer"
target="_blank">
<Button
className="font-semibold"
data-testid="request-access-button">
{t('label.request-access')}
</Button>
</Typography.Link>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

This also nests an Ant <Button> inside <Typography.Link> (<a><button/></a>), which is invalid markup and may confuse screen readers / keyboard navigation. Prefer a single interactive element (e.g., Button with href/target/rel or a link styled to look like a button).

Copilot uses AI. Check for mistakes.
Comment on lines +258 to +263
const { url: requestAccessUrl } = useRequestAccessUrl({
entityFqn: source.fullyQualifiedName,
entityType: source.entityType as EntityType,
serviceName: source.service?.name,
serviceType: source.service?.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.

⚠️ Performance: useRequestAccessUrl fires API call per explore card render

In ExploreSearchCard.tsx, useRequestAccessUrl is invoked for every card in the explore/search results list. Even though there's a per-service cache, on initial page load with N results from M services, this fires M concurrent getServiceByFQN API calls — one per unique service. For pages with many distinct services this could cause a burst of requests.

More importantly, the hook runs in an useEffect keyed on [entityFqn, entityType, serviceCategory, serviceName]. If the explore page re-renders (e.g., filter change), all cards remount and the effects re-fire. The cache mitigates repeated network calls, but the hook still sets state (triggering re-renders) for every card.

Consider lifting the service lookup to a shared context or the parent list component so each service is fetched exactly once, and results are passed down as props.

Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion


const ENTITY_FQN_ENCODED_TOKEN = '{{entityFqnEncoded}}';
const ENTITY_URL_ENCODED_TOKEN = '{{entityUrlEncoded}}';
const SAFE_URL_PATTERN = /^https?:\/\//i;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Security: isSafeUrl regex allows http:// — consider restricting to https

The SAFE_URL_PATTERN at line 16 of RequestAccessUtils.ts allows both http:// and https:// URLs. While this prevents javascript: injection (the primary concern), allowing plain HTTP means the request-access URL could point to an unencrypted endpoint, potentially leaking referrer headers or entity metadata in transit. For a feature that directs users to access-request portals (which typically handle auth), restricting to https:// would be more secure.

This is minor since the URL is configured by an admin in service connection options, not by end users.

Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@gitar-bot
Copy link
Copy Markdown

gitar-bot bot commented Apr 4, 2026

Code Review ⚠️ Changes requested 3 resolved / 6 findings

Adds request access buttons to database, schema, and dashboard cards, but the useRequestAccessUrl hook triggers excessive API calls per card render in ExploreSearchCard. Additionally, the isSafeUrl regex allows http:// protocol and lacks test coverage for javascript: and data: URL rejection.

⚠️ Performance: useRequestAccessUrl fires API call per explore card render

📄 openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx:258-263 📄 openmetadata-ui/src/main/resources/ui/src/hooks/useRequestAccessUrl.ts:58-72

In ExploreSearchCard.tsx, useRequestAccessUrl is invoked for every card in the explore/search results list. Even though there's a per-service cache, on initial page load with N results from M services, this fires M concurrent getServiceByFQN API calls — one per unique service. For pages with many distinct services this could cause a burst of requests.

More importantly, the hook runs in an useEffect keyed on [entityFqn, entityType, serviceCategory, serviceName]. If the explore page re-renders (e.g., filter change), all cards remount and the effects re-fire. The cache mitigates repeated network calls, but the hook still sets state (triggering re-renders) for every card.

Consider lifting the service lookup to a shared context or the parent list component so each service is fetched exactly once, and results are passed down as props.

💡 Security: isSafeUrl regex allows http:// — consider restricting to https

📄 openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.ts:16 📄 openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.ts:23

The SAFE_URL_PATTERN at line 16 of RequestAccessUtils.ts allows both http:// and https:// URLs. While this prevents javascript: injection (the primary concern), allowing plain HTTP means the request-access URL could point to an unencrypted endpoint, potentially leaking referrer headers or entity metadata in transit. For a feature that directs users to access-request portals (which typically handle auth), restricting to https:// would be more secure.

This is minor since the URL is configured by an admin in service connection options, not by end users.

💡 Edge Case: No test for javascript: or data: URL rejection

📄 openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.test.ts

The RequestAccessUtils.test.ts tests cover the happy paths (template resolution, direct URL preference, missing options) but don't verify that malicious URLs like javascript:alert(1) or data:text/html,... are rejected by isSafeUrl. Adding a test case ensures this security invariant is protected against future regressions.

Suggested fix
it('should reject non-http(s) URLs', () => {
  expect(getRequestAccessUrl({
    connectionOptions: { requestAccessUrl: 'javascript:alert(1)' },
    entityFqn: 'db',
    entityUrl: 'http://localhost/database/db',
  })).toBeNull();

  expect(getRequestAccessUrl({
    connectionOptions: { requestAccessUrlTemplate: 'data:text/html,<script>alert(1)</script>' },
    entityFqn: 'db',
    entityUrl: 'http://localhost/database/db',
  })).toBeNull();
});
✅ 3 resolved
Security: No URL protocol validation — potential XSS via javascript: href

📄 openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.ts:41-44 📄 openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx:429 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx:699
The requestAccessUrl value from service connectionOptions is passed directly to <Typography.Link href={...}> without any protocol validation. An admin (or anyone who can edit service connection options) could set requestAccessUrl to javascript:alert(document.cookie), which would execute arbitrary JavaScript when a user clicks the "Request Access" button.

Ant Design's Typography.Link does not sanitize the href attribute. The codebase already acknowledges this threat in block-editor CSS (blocking javascript: in [href]), but that protection doesn't apply here.

This affects both the explore search cards and entity detail headers.

Performance: N+1 API calls: useRequestAccessUrl fires per search card

📄 openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx:258-263 📄 openmetadata-ui/src/main/resources/ui/src/hooks/useRequestAccessUrl.ts:72-86
The useRequestAccessUrl hook calls getServiceByFQN in a useEffect for every rendered card. In a typical search/explore page with 10–25 results, this fires 10–25 parallel API requests on each page load. When multiple results belong to the same service (common in practice), these are redundant duplicate requests. There is no caching layer in the API client.

This creates unnecessary backend load and delays rendering of the request-access buttons.

Bug: Module-level cache never invalidated, stale URLs persist

📄 openmetadata-ui/src/main/resources/ui/src/hooks/useRequestAccessUrl.ts:21
The connectionOptionsCache is a module-level Map that is populated on first fetch but never cleared. In a single-page application, this means if an admin updates the requestAccessUrl or requestAccessUrlTemplate in a service's connection options, users will continue seeing the stale URL until they do a full page reload. This is particularly problematic because the cache key is per-service, so a single wrong entry persists for all entities under that service.

Consider either:

  1. Adding a TTL-based eviction (e.g., store {data, timestamp} and invalidate entries older than N minutes), or
  2. Exporting a clearConnectionOptionsCache() function that can be called when service config is updated, or
  3. At minimum, document the caching behavior so it's a conscious trade-off.
🤖 Prompt for agents
Code Review: Adds request access buttons to database, schema, and dashboard cards, but the useRequestAccessUrl hook triggers excessive API calls per card render in ExploreSearchCard. Additionally, the isSafeUrl regex allows http:// protocol and lacks test coverage for javascript: and data: URL rejection.

1. ⚠️ Performance: useRequestAccessUrl fires API call per explore card render
   Files: openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx:258-263, openmetadata-ui/src/main/resources/ui/src/hooks/useRequestAccessUrl.ts:58-72

   In `ExploreSearchCard.tsx`, `useRequestAccessUrl` is invoked for every card in the explore/search results list. Even though there's a per-service cache, on initial page load with N results from M services, this fires M concurrent `getServiceByFQN` API calls — one per unique service. For pages with many distinct services this could cause a burst of requests.
   
   More importantly, the hook runs in an `useEffect` keyed on `[entityFqn, entityType, serviceCategory, serviceName]`. If the explore page re-renders (e.g., filter change), all cards remount and the effects re-fire. The cache mitigates repeated network calls, but the hook still sets state (triggering re-renders) for every card.
   
   Consider lifting the service lookup to a shared context or the parent list component so each service is fetched exactly once, and results are passed down as props.

2. 💡 Security: isSafeUrl regex allows http:// — consider restricting to https
   Files: openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.ts:16, openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.ts:23

   The `SAFE_URL_PATTERN` at line 16 of `RequestAccessUtils.ts` allows both `http://` and `https://` URLs. While this prevents `javascript:` injection (the primary concern), allowing plain HTTP means the request-access URL could point to an unencrypted endpoint, potentially leaking referrer headers or entity metadata in transit. For a feature that directs users to access-request portals (which typically handle auth), restricting to `https://` would be more secure.
   
   This is minor since the URL is configured by an admin in service connection options, not by end users.

3. 💡 Edge Case: No test for javascript: or data: URL rejection
   Files: openmetadata-ui/src/main/resources/ui/src/utils/RequestAccessUtils.test.ts

   The `RequestAccessUtils.test.ts` tests cover the happy paths (template resolution, direct URL preference, missing options) but don't verify that malicious URLs like `javascript:alert(1)` or `data:text/html,...` are rejected by `isSafeUrl`. Adding a test case ensures this security invariant is protected against future regressions.

   Suggested fix:
   it('should reject non-http(s) URLs', () => {
     expect(getRequestAccessUrl({
       connectionOptions: { requestAccessUrl: 'javascript:alert(1)' },
       entityFqn: 'db',
       entityUrl: 'http://localhost/database/db',
     })).toBeNull();
   
     expect(getRequestAccessUrl({
       connectionOptions: { requestAccessUrlTemplate: 'data:text/html,<script>alert(1)</script>' },
       entityFqn: 'db',
       entityUrl: 'http://localhost/database/db',
     })).toBeNull();
   });

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

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.

Adding a "Request Access" button to database, diagram, and dashboard cards

2 participants