Skip to content

Commit fadff0e

Browse files
authored
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.
1 parent c9547dc commit fadff0e

7 files changed

Lines changed: 54 additions & 50 deletions

File tree

apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,9 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration
6868
useScrollRestoration(scrollContainerRef, { ready: !credentialsLoading })
6969

7070
/**
71-
* Credentials that authenticate this integration. Matching goes through
72-
* `credentialProviderMatchesService` so a family service account lists on
73-
* every product it powers — one Atlassian token covers Jira, Jira Service
74-
* Management, and Confluence. Comparing resolved `providerId`s instead would
75-
* hide it from all three, since `atlassian-service-account` resolves to its
76-
* own pseudo-service rather than to any product.
71+
* Matches on the service's own id *or* its service-account id, so a family
72+
* credential lists on every product it powers. Comparing resolved
73+
* `providerId`s instead hides it from all of them.
7774
*/
7875
const connectedCredentials = useMemo(() => {
7976
if (!oauthService) return []

apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/connect-service-account-modal.tsx

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@ function openDocs(url: string): void {
6262
const ATLASSIAN_DOMAIN_HINT_REGEX = /^[a-z0-9-]+\.atlassian\.net$/i
6363

6464
/**
65-
* States the site-wide reach of the token up front. Users reaching this modal
66-
* from the Jira page were left unsure whether they had connected Jira or Jira
67-
* Service Management; the credential covers both, plus Confluence. Derived from
68-
* the catalog so it cannot drift as Atlassian integrations are added.
65+
* States the token's reach up front — the ambiguity this modal exists to remove.
66+
* Sits on the API token field, not Site domain: it describes the token, and
67+
* `ChipModalField` hides a `hint` whenever that field shows an `error`, which
68+
* would drop it exactly while the user is correcting a domain typo. Derived
69+
* from the catalog so it cannot drift as Atlassian integrations are added.
6970
*/
7071
const ATLASSIAN_COVERAGE_HINT = getServiceAccountCoverageSentence(
7172
ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID
@@ -502,21 +503,24 @@ function AtlassianServiceAccountModal({
502503
Add {serviceName} service account
503504
</ChipModalHeader>
504505
<ChipModalBody>
505-
<ChipModalField type='custom' title='API token' required>
506-
<SecretInput
507-
value={apiToken}
508-
onChange={(value) => {
509-
setApiToken(value)
510-
if (error) setError(null)
511-
}}
512-
placeholder='Paste API token'
513-
name='atlassian_service_account_api_token'
514-
autoComplete='new-password'
515-
autoCorrect='off'
516-
autoCapitalize='off'
517-
data-lpignore='true'
518-
data-form-type='other'
519-
/>
506+
<ChipModalField type='custom' title='API token' required hint={ATLASSIAN_COVERAGE_HINT}>
507+
{(aria) => (
508+
<SecretInput
509+
{...aria}
510+
value={apiToken}
511+
onChange={(value) => {
512+
setApiToken(value)
513+
if (error) setError(null)
514+
}}
515+
placeholder='Paste API token'
516+
name='atlassian_service_account_api_token'
517+
autoComplete='new-password'
518+
autoCorrect='off'
519+
autoCapitalize='off'
520+
data-lpignore='true'
521+
data-form-type='other'
522+
/>
523+
)}
520524
</ChipModalField>
521525

522526
<ChipModalField
@@ -535,7 +539,6 @@ function AtlassianServiceAccountModal({
535539
? 'Atlassian sites usually look like your-team.atlassian.net.'
536540
: undefined
537541
}
538-
hint={ATLASSIAN_COVERAGE_HINT}
539542
/>
540543

541544
<ChipModalField

apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/use-service-account-connect.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,8 @@ import { isHiddenUnder, overlayVisibility } from '@/blocks/visibility/context'
2828
export interface ServiceAccountConnectTarget {
2929
serviceAccountProviderId: ServiceAccountProviderId
3030
/**
31-
* Name the setup surface is titled with. For a family service account this is
32-
* the vendor ("Atlassian"), not the product page you came from — one Atlassian
33-
* token authenticates Jira, Jira Service Management, and Confluence alike, so
34-
* calling it a "Jira service account" is what made users think they had
35-
* connected the wrong product.
31+
* Name the setup surface is titled with — the vendor ("Atlassian") for a
32+
* family service account, not the product page you opened it from.
3633
*/
3734
serviceName: string
3835
serviceIcon: ComponentType<{ className?: string }>

apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,6 @@ export function ConnectedCredentialDetail({
9696
[oauthServiceNameByProviderId]
9797
)
9898

99-
/**
100-
* Service, brand tile, and copy all come from the shared resolver so this
101-
* page, the integrations list, and the Cmd-K search agree on how a credential
102-
* is named and branded — a family service account reads as its family
103-
* ("Atlassian"), not as whichever product the provider walk happened to hit.
104-
*/
10599
const display = useMemo(
106100
() => (credential ? resolveCredentialDisplay(credential) : null),
107101
[credential]

apps/sim/lib/integrations/credential-display.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
* catalog integrations it powers, which mark and brand tile represent it, and
44
* the sentence describing it.
55
*
6-
* Before this module, three surfaces (the integrations list, the Cmd-K search
7-
* items, and the credential detail page) each re-derived this by looking the
8-
* catalog up by the OAuth service's *display name*. That silently failed for
9-
* family service accounts: `atlassian-service-account` resolves to a
10-
* pseudo-service named "Atlassian Service Account", which matches no catalog
11-
* integration, so the credential lost its brand tile and its category.
6+
* Replaces a display-name-keyed catalog lookup that three surfaces each
7+
* re-derived. That keying silently failed for family service accounts:
8+
* `atlassian-service-account` resolves to a pseudo-service named "Atlassian
9+
* Service Account", which matches no catalog entry, so the credential lost its
10+
* brand tile and its category filter.
1211
*/
1312

1413
import type { ComponentType } from 'react'

apps/sim/lib/integrations/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,6 @@ export function toIntegrationSummary(integration: Integration): IntegrationSumma
100100

101101
export {
102102
type CredentialDisplay,
103-
getIntegrationsForCredentialProvider,
104-
getServiceAccountCoverageSentence,
105-
getServiceAccountFamilyIcon,
106-
getServiceAccountFamilyName,
107-
isFamilyServiceAccount,
108103
resolveCredentialDisplay,
109104
} from '@/lib/integrations/credential-display'
110105
export { blockTypeToIconMap } from '@/lib/integrations/icon-mapping'

packages/emcn/src/components/chip-modal/chip-modal.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -549,9 +549,28 @@ export interface ChipModalEmailsFieldProps extends ChipModalFieldBaseProps {
549549
placeholder?: string
550550
}
551551

552+
/**
553+
* ARIA the field derives from its own state and renders elsewhere in the row —
554+
* the `hint`/`error` paragraph ids, plus `required`/`invalid` flags.
555+
*/
556+
export interface ChipModalFieldAria {
557+
'aria-required'?: boolean
558+
'aria-invalid'?: boolean
559+
'aria-describedby'?: string
560+
}
561+
552562
interface ChipModalCustomFieldProps extends ChipModalFieldBaseProps {
553563
type: 'custom'
554-
children: React.ReactNode
564+
/**
565+
* Arbitrary JSX, or a function receiving the field's {@link ChipModalFieldAria}.
566+
*
567+
* The owned control types wire this ARIA themselves, but a custom field can
568+
* hold anything — a bare input, or a wrapper several levels above one — so
569+
* the field cannot know which element should carry it. Use the function form
570+
* whenever the child renders a focusable control, or its `hint`/`error` text
571+
* is rendered but never announced.
572+
*/
573+
children: React.ReactNode | ((aria: ChipModalFieldAria) => React.ReactNode)
555574
}
556575

557576
export type ChipModalFieldProps =
@@ -718,7 +737,7 @@ function renderChipModalControl(
718737
case 'emails':
719738
return <ChipModalEmailsControl {...props} id={id} errorId={errorId} />
720739
case 'custom':
721-
return props.children
740+
return typeof props.children === 'function' ? props.children(aria) : props.children
722741
}
723742
}
724743

0 commit comments

Comments
 (0)