mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(integrations): keep the Atlassian coverage hint visible, and trim dead surface (#6105)
* fix(integrations): keep the Atlassian coverage hint visible, and trim comments Cursor Bugbot: `ChipModalField` hides a `hint` whenever that field shows an `error`, so the multi-product coverage sentence vanished the moment the domain format check fired — exactly when someone mid-form most needs it. The sentence also describes the token, not the domain, so it read as domain guidance. Moves it to the API token field, which surfaces its errors through `ChipModalError` at the bottom rather than its own `error` prop, so the hint cannot be displaced. Also drops rationale comments that restated the code they sat above. * chore(integrations): trim the credential-display barrel to what consumers use * fix(emcn): let a custom modal field associate its hint with the control it wraps `ChipModalField` computes `aria-required`/`aria-invalid`/`aria-describedby` from its own state, but `type='custom'` returned its children untouched — so the `hint` and `error` text it renders was visible and never announced. The field cannot apply the ARIA itself here: a custom child may be a bare input or a wrapper several levels above one, which is the same reason `associatesLabel` already excludes custom from the label's `htmlFor`. Adds a function form for custom children that receives the ARIA, so the consumer — which knows where focus lands — attaches it. Existing `ReactNode` children are unaffected; all five current custom-field call sites keep working untouched. Uses it for the Atlassian API token field, whose coverage hint this branch had just relocated onto a custom field.
This commit is contained in:
+3
-6
@@ -68,12 +68,9 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration
|
||||
useScrollRestoration(scrollContainerRef, { ready: !credentialsLoading })
|
||||
|
||||
/**
|
||||
* Credentials that authenticate this integration. Matching goes through
|
||||
* `credentialProviderMatchesService` so a family service account lists on
|
||||
* every product it powers — one Atlassian token covers Jira, Jira Service
|
||||
* Management, and Confluence. Comparing resolved `providerId`s instead would
|
||||
* hide it from all three, since `atlassian-service-account` resolves to its
|
||||
* own pseudo-service rather than to any product.
|
||||
* Matches on the service's own id *or* its service-account id, so a family
|
||||
* credential lists on every product it powers. Comparing resolved
|
||||
* `providerId`s instead hides it from all of them.
|
||||
*/
|
||||
const connectedCredentials = useMemo(() => {
|
||||
if (!oauthService) return []
|
||||
|
||||
+23
-20
@@ -62,10 +62,11 @@ function openDocs(url: string): void {
|
||||
const ATLASSIAN_DOMAIN_HINT_REGEX = /^[a-z0-9-]+\.atlassian\.net$/i
|
||||
|
||||
/**
|
||||
* States the site-wide reach of the token up front. Users reaching this modal
|
||||
* from the Jira page were left unsure whether they had connected Jira or Jira
|
||||
* Service Management; the credential covers both, plus Confluence. Derived from
|
||||
* the catalog so it cannot drift as Atlassian integrations are added.
|
||||
* States the token's reach up front — the ambiguity this modal exists to remove.
|
||||
* Sits on the API token field, not Site domain: it describes the token, and
|
||||
* `ChipModalField` hides a `hint` whenever that field shows an `error`, which
|
||||
* would drop it exactly while the user is correcting a domain typo. Derived
|
||||
* from the catalog so it cannot drift as Atlassian integrations are added.
|
||||
*/
|
||||
const ATLASSIAN_COVERAGE_HINT = getServiceAccountCoverageSentence(
|
||||
ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID
|
||||
@@ -502,21 +503,24 @@ function AtlassianServiceAccountModal({
|
||||
Add {serviceName} service account
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<ChipModalField type='custom' title='API token' required>
|
||||
<SecretInput
|
||||
value={apiToken}
|
||||
onChange={(value) => {
|
||||
setApiToken(value)
|
||||
if (error) setError(null)
|
||||
}}
|
||||
placeholder='Paste API token'
|
||||
name='atlassian_service_account_api_token'
|
||||
autoComplete='new-password'
|
||||
autoCorrect='off'
|
||||
autoCapitalize='off'
|
||||
data-lpignore='true'
|
||||
data-form-type='other'
|
||||
/>
|
||||
<ChipModalField type='custom' title='API token' required hint={ATLASSIAN_COVERAGE_HINT}>
|
||||
{(aria) => (
|
||||
<SecretInput
|
||||
{...aria}
|
||||
value={apiToken}
|
||||
onChange={(value) => {
|
||||
setApiToken(value)
|
||||
if (error) setError(null)
|
||||
}}
|
||||
placeholder='Paste API token'
|
||||
name='atlassian_service_account_api_token'
|
||||
autoComplete='new-password'
|
||||
autoCorrect='off'
|
||||
autoCapitalize='off'
|
||||
data-lpignore='true'
|
||||
data-form-type='other'
|
||||
/>
|
||||
)}
|
||||
</ChipModalField>
|
||||
|
||||
<ChipModalField
|
||||
@@ -535,7 +539,6 @@ function AtlassianServiceAccountModal({
|
||||
? 'Atlassian sites usually look like your-team.atlassian.net.'
|
||||
: undefined
|
||||
}
|
||||
hint={ATLASSIAN_COVERAGE_HINT}
|
||||
/>
|
||||
|
||||
<ChipModalField
|
||||
|
||||
+2
-5
@@ -28,11 +28,8 @@ import { isHiddenUnder, overlayVisibility } from '@/blocks/visibility/context'
|
||||
export interface ServiceAccountConnectTarget {
|
||||
serviceAccountProviderId: ServiceAccountProviderId
|
||||
/**
|
||||
* Name the setup surface is titled with. For a family service account this is
|
||||
* the vendor ("Atlassian"), not the product page you came from — one Atlassian
|
||||
* token authenticates Jira, Jira Service Management, and Confluence alike, so
|
||||
* calling it a "Jira service account" is what made users think they had
|
||||
* connected the wrong product.
|
||||
* Name the setup surface is titled with — the vendor ("Atlassian") for a
|
||||
* family service account, not the product page you opened it from.
|
||||
*/
|
||||
serviceName: string
|
||||
serviceIcon: ComponentType<{ className?: string }>
|
||||
|
||||
-6
@@ -96,12 +96,6 @@ export function ConnectedCredentialDetail({
|
||||
[oauthServiceNameByProviderId]
|
||||
)
|
||||
|
||||
/**
|
||||
* Service, brand tile, and copy all come from the shared resolver so this
|
||||
* page, the integrations list, and the Cmd-K search agree on how a credential
|
||||
* is named and branded — a family service account reads as its family
|
||||
* ("Atlassian"), not as whichever product the provider walk happened to hit.
|
||||
*/
|
||||
const display = useMemo(
|
||||
() => (credential ? resolveCredentialDisplay(credential) : null),
|
||||
[credential]
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
* catalog integrations it powers, which mark and brand tile represent it, and
|
||||
* the sentence describing it.
|
||||
*
|
||||
* Before this module, three surfaces (the integrations list, the Cmd-K search
|
||||
* items, and the credential detail page) each re-derived this by looking the
|
||||
* catalog up by the OAuth service's *display name*. That silently failed for
|
||||
* family service accounts: `atlassian-service-account` resolves to a
|
||||
* pseudo-service named "Atlassian Service Account", which matches no catalog
|
||||
* integration, so the credential lost its brand tile and its category.
|
||||
* Replaces a display-name-keyed catalog lookup that three surfaces each
|
||||
* re-derived. That keying silently failed for family service accounts:
|
||||
* `atlassian-service-account` resolves to a pseudo-service named "Atlassian
|
||||
* Service Account", which matches no catalog entry, so the credential lost its
|
||||
* brand tile and its category filter.
|
||||
*/
|
||||
|
||||
import type { ComponentType } from 'react'
|
||||
|
||||
@@ -100,11 +100,6 @@ export function toIntegrationSummary(integration: Integration): IntegrationSumma
|
||||
|
||||
export {
|
||||
type CredentialDisplay,
|
||||
getIntegrationsForCredentialProvider,
|
||||
getServiceAccountCoverageSentence,
|
||||
getServiceAccountFamilyIcon,
|
||||
getServiceAccountFamilyName,
|
||||
isFamilyServiceAccount,
|
||||
resolveCredentialDisplay,
|
||||
} from '@/lib/integrations/credential-display'
|
||||
export { blockTypeToIconMap } from '@/lib/integrations/icon-mapping'
|
||||
|
||||
@@ -549,9 +549,28 @@ export interface ChipModalEmailsFieldProps extends ChipModalFieldBaseProps {
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* ARIA the field derives from its own state and renders elsewhere in the row —
|
||||
* the `hint`/`error` paragraph ids, plus `required`/`invalid` flags.
|
||||
*/
|
||||
export interface ChipModalFieldAria {
|
||||
'aria-required'?: boolean
|
||||
'aria-invalid'?: boolean
|
||||
'aria-describedby'?: string
|
||||
}
|
||||
|
||||
interface ChipModalCustomFieldProps extends ChipModalFieldBaseProps {
|
||||
type: 'custom'
|
||||
children: React.ReactNode
|
||||
/**
|
||||
* Arbitrary JSX, or a function receiving the field's {@link ChipModalFieldAria}.
|
||||
*
|
||||
* The owned control types wire this ARIA themselves, but a custom field can
|
||||
* hold anything — a bare input, or a wrapper several levels above one — so
|
||||
* the field cannot know which element should carry it. Use the function form
|
||||
* whenever the child renders a focusable control, or its `hint`/`error` text
|
||||
* is rendered but never announced.
|
||||
*/
|
||||
children: React.ReactNode | ((aria: ChipModalFieldAria) => React.ReactNode)
|
||||
}
|
||||
|
||||
export type ChipModalFieldProps =
|
||||
@@ -718,7 +737,7 @@ function renderChipModalControl(
|
||||
case 'emails':
|
||||
return <ChipModalEmailsControl {...props} id={id} errorId={errorId} />
|
||||
case 'custom':
|
||||
return props.children
|
||||
return typeof props.children === 'function' ? props.children(aria) : props.children
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user