add request access button to database, schema, and dashboard cards [WIP]#27014
add request access button to database, schema, and dashboard cards [WIP]#27014SaaiAravindhRaja wants to merge 6 commits intoopen-metadata:mainfrom
Conversation
|
Hi there 👋 Thanks for your contribution! The OpenMetadata team will review the PR shortly! Once it has been labeled as Let us know if you need any help! |
...ta-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
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
RequestAccessUtilsto resolve a direct URL or a templated URL with encoded entity placeholders. - Added
useRequestAccessUrlhook that loads serviceconnectionOptionsviagetServiceByFQNand 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. |
| const directUrl = connectionOptions?.[REQUEST_ACCESS_URL]?.trim(); | ||
|
|
||
| if (directUrl) { | ||
| return directUrl; | ||
| } |
There was a problem hiding this comment.
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.
| const { url: requestAccessUrl } = useRequestAccessUrl({ | ||
| entityFqn: source.fullyQualifiedName, | ||
| entityType: source.entityType as EntityType, | ||
| serviceName: source.service?.name, | ||
| serviceType: source.service?.type, |
There was a problem hiding this comment.
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.
| <Typography.Link | ||
| href={requestAccessUrl} | ||
| rel="noreferrer" | ||
| target="_blank"> | ||
| <Button data-testid="request-access-button" type="link"> |
There was a problem hiding this comment.
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).
| <Typography.Link | ||
| href={requestAccessUrl} | ||
| rel="noreferrer" | ||
| target="_blank"> | ||
| <Button |
There was a problem hiding this comment.
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.
| "reply-lowercase-plural": "replies", | ||
| "reposition": "Reposition", | ||
| "request": "Request", | ||
| "request-access": "Request Access", | ||
| "request-method": "Request Method", |
There was a problem hiding this comment.
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.
|
Hi there 👋 Thanks for your contribution! The OpenMetadata team will review the PR shortly! Once it has been labeled as Let us know if you need any help! |
|
Hi there 👋 Thanks for your contribution! The OpenMetadata team will review the PR shortly! Once it has been labeled as Let us know if you need any help! |
| const connectionOptionsCache = new Map<string, Record<string, string> | undefined>(); | ||
|
|
There was a problem hiding this comment.
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).
| 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); | ||
| } |
There was a problem hiding this comment.
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.
| <Typography.Link | ||
| href={requestAccessUrl} | ||
| rel="noopener noreferrer" | ||
| target="_blank"> | ||
| <Button data-testid="request-access-button" type="link"> | ||
| {t('label.request-access')} | ||
| </Button> | ||
| </Typography.Link> |
There was a problem hiding this comment.
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).
| <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> |
| <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> |
There was a problem hiding this comment.
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).
openmetadata-ui/src/main/resources/ui/src/hooks/useRequestAccessUrl.ts
Outdated
Show resolved
Hide resolved
| const { url: requestAccessUrl } = useRequestAccessUrl({ | ||
| entityFqn: source.fullyQualifiedName, | ||
| entityType: source.entityType as EntityType, | ||
| serviceName: source.service?.name, | ||
| serviceType: source.service?.type, | ||
| }); |
There was a problem hiding this comment.
⚠️ 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; |
There was a problem hiding this comment.
💡 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
|
Hi there 👋 Thanks for your contribution! The OpenMetadata team will review the PR shortly! Once it has been labeled as Let us know if you need any help! |
Code Review
|
| Compact |
|
Was this helpful? React with 👍 / 👎 | Gitar
Summary
connectionOptionsRequest Accessbutton on explore cards and entity headers for supported assetsNotes
requestAccessUrl,requestAccessUrlTemplatedatabase,databaseSchema,dashboardTest Plan
git diff --checknode -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.tsyarn test --runInBand src/hooks/useRequestAccessUrl.test.tsFixes #25575