Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,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 []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -502,21 +503,24 @@ function AtlassianServiceAccountModal({
Add {serviceName} service account
Comment thread
waleedlatif1 marked this conversation as resolved.
</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
Expand All @@ -535,7 +539,6 @@ function AtlassianServiceAccountModal({
? 'Atlassian sites usually look like your-team.atlassian.net.'
: undefined
}
hint={ATLASSIAN_COVERAGE_HINT}
/>

<ChipModalField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
11 changes: 5 additions & 6 deletions apps/sim/lib/integrations/credential-display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 0 additions & 5 deletions apps/sim/lib/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
23 changes: 21 additions & 2 deletions packages/emcn/src/components/chip-modal/chip-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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
}
}

Expand Down
Loading