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 @@ -109,15 +109,27 @@ export function SlackManagedUsersModal({
availableApps.find((app) => app.appId === appId) ??
(availableApps.length === 1 && !appId ? availableApps[0] : undefined)
const sharedAppInstalled = organizationSetup && selectedApp?.appKind === 'shared'
const noInstalledApp = organizationSetup && apps.isSuccess && availableApps.length === 0
/** The shared app connects member accounts without a Search bot installation. */
const sharedAppWithoutBot = noInstalledApp && Boolean(apps.data?.sharedAppAvailable)
const sharedSetup = sharedAppInstalled || sharedAppWithoutBot
const searchPolicies = useSearchIntegrations(organizationId ?? '', {
enabled: open && sharedAppInstalled,
enabled: open && sharedSetup,
})
const searchApproved = searchPolicies.data?.some(
(policy) => policy.connectorType === 'slack' && policy.approved
)
const accounts = useOrganizationAccounts(open && sharedAppInstalled ? organizationId : undefined)
const accounts = useOrganizationAccounts(open && sharedSetup ? organizationId : undefined)
const memberGroup = accounts.data?.credentialGroup
const memberOption = memberGroup?.options.find((option) => option.provider === 'slack')
const memberSetupSettled =
accounts.isSuccess &&
!accounts.isFetching &&
!accounts.error &&
searchPolicies.isSuccess &&
!searchPolicies.isFetching &&
!searchPolicies.error &&
memberGroup?.id === credentialGroupId
const sharedAppCanAuthorize = Boolean(
sharedAppInstalled &&
apps.isSuccess &&
Expand All @@ -126,19 +138,18 @@ export function SlackManagedUsersModal({
apps.data?.sharedAppAvailable &&
selectedApp.enabled &&
!selectedApp.needsValidation &&
accounts.isSuccess &&
!accounts.isFetching &&
!accounts.error &&
searchPolicies.isSuccess &&
!searchPolicies.isFetching &&
!searchPolicies.error &&
memberGroup?.id === credentialGroupId &&
memberSetupSettled &&
memberOption?.status === 'active'
)
const memberOnlyCanAuthorize =
sharedAppWithoutBot &&
!apps.isFetching &&
memberSetupSettled &&
(!memberOption || memberOption.status === 'active')
const searchPermissionsMissing =
searchApproved && !hasSlackSearchUserScopes(memberOption?.requiredScopes)
const sharedAppReady =
sharedAppCanAuthorize &&
(sharedAppCanAuthorize || memberOnlyCanAuthorize) &&
memberOption?.configurationStatus === 'ready' &&
!searchPermissionsMissing
const sharedAppNeedsUpdate =
Expand Down Expand Up @@ -176,7 +187,7 @@ export function SlackManagedUsersModal({
: 'search')
const requiredScopes =
scope.kind === 'organization'
? selectedApp
? selectedApp || sharedAppWithoutBot
? [...SLACK_SEARCH_USER_SCOPES]
: []
: access === null
Expand Down Expand Up @@ -300,15 +311,24 @@ export function SlackManagedUsersModal({
if (
pending ||
(sharedAppInstalled && !sharedAppNeedsUpdate) ||
(sharedAppWithoutBot && !memberOnlyCanAuthorize) ||
(!organizationSetup && !selectedBot)
)
return
if (
organizationSetup
? !selectedApp || !requiredScopes.length
: !clientId.trim() || !clientSecret.trim()
)
if (organizationSetup ? !requiredScopes.length : !clientId.trim() || !clientSecret.trim())
return
const body = {
...(organizationSetup
? selectedApp
? { appId: selectedApp.appId, teamId: selectedApp.teamId }
: {}
: {
slackBotCredentialId: selectedBot?.id,
clientId: clientId.trim(),
clientSecret: clientSecret.trim(),
}),
requiredScopes,
}

if (isDesktopApp()) {
const controller = new AbortController()
Expand All @@ -320,16 +340,7 @@ export function SlackManagedUsersModal({
kind: 'slack-managed-users',
owner: resourceScopeFields(scope),
credentialGroupId,
body: {
...(organizationSetup
? { appId: selectedApp?.appId, teamId: selectedApp?.teamId }
: {
slackBotCredentialId: selectedBot?.id,
clientId: clientId.trim(),
clientSecret: clientSecret.trim(),
}),
requiredScopes,
},
body,
},
controller.signal
)
Expand Down Expand Up @@ -365,16 +376,7 @@ export function SlackManagedUsersModal({
const result = await startAuthorization.mutateAsync({
...resourceScopeFields(scope),
credentialGroupId,
body: {
...(organizationSetup
? { appId: selectedApp?.appId, teamId: selectedApp?.teamId }
: {
slackBotCredentialId: selectedBot?.id,
clientId: clientId.trim(),
clientSecret: clientSecret.trim(),
}),
requiredScopes,
},
body,
})
if (popup.current !== opened) return
expectedState.current = result.state
Expand Down Expand Up @@ -403,28 +405,28 @@ export function SlackManagedUsersModal({
if (sharedAppReady && !appSetupOpen) return null

const noBots = !organizationSetup && !isLoading && bots.length === 0
const needsApp = organizationSetup && apps.isSuccess && availableApps.length === 0
const needsApp = noInstalledApp && !sharedAppWithoutBot
const checkingSetup =
apps.isPending ||
(sharedAppInstalled &&
(sharedSetup &&
(apps.isFetching ||
accounts.isPending ||
accounts.isFetching ||
searchPolicies.isPending ||
searchPolicies.isFetching))
const failedSetup = apps.error
? apps
: sharedAppInstalled && searchPolicies.error
: sharedSetup && searchPolicies.error
? searchPolicies
: sharedAppInstalled && accounts.error
: sharedSetup && accounts.error
? accounts
: null
const title = organizationSetup ? 'Set up Slack app' : 'Set up Slack'
const primaryLabel = isLoading
? 'Loading...'
: pending
? 'Waiting for Slack...'
: sharedAppNeedsUpdate
: sharedAppNeedsUpdate || (sharedAppWithoutBot && memberOption)
? 'Update member access'
: 'Verify and add'
const primaryDisabled =
Expand All @@ -433,7 +435,10 @@ export function SlackManagedUsersModal({
(!organizationSetup && !selectedBot) ||
pending ||
(organizationSetup
? checkingSetup || Boolean(failedSetup) || !selectedApp || !requiredScopes.length
? checkingSetup ||
Boolean(failedSetup) ||
(sharedAppWithoutBot && !memberOnlyCanAuthorize) ||
!requiredScopes.length
: !clientId.trim() || !clientSecret.trim())

return (
Expand Down Expand Up @@ -465,6 +470,14 @@ export function SlackManagedUsersModal({
Retry
</Chip>
</ChipModalField>
) : sharedAppWithoutBot ? (
<ChipModalField type='custom' title='Member accounts'>
<p className='text-[var(--text-secondary)] text-sm'>
{memberOption
? 'Verify member permissions. Members will need to reconnect if their app or permissions change.'
: 'Verify member permissions in your Slack workspace.'}
</p>
</ChipModalField>
) : needsApp ? (
<ChipModalField type='custom' title='Install Sim Search first'>
<p className='text-[var(--text-secondary)] text-sm'>
Expand Down
2 changes: 0 additions & 2 deletions apps/sim/hooks/queries/credential-groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ export function useStartSlackCredentialGroupConfiguration() {
)) => {
const scope = resourceScopeFromOwner({ workspaceId, organizationId })
if (scope.kind === 'organization') {
if (!body.appId || !body.teamId)
throw new Error('Slack App ID and workspace ID are required')
return requestJson(startOrganizationSlackConfigurationContract, {
params: { id: scope.organizationId, groupId: credentialGroupId },
body: {
Expand Down
6 changes: 5 additions & 1 deletion apps/sim/lib/api/contracts/organization-accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,13 @@ export const startOrganizationSlackConfigurationContract = defineRouteContract({
method: 'POST',
path: '/api/organizations/[id]/connected-accounts/[groupId]/slack-managed-users',
params: organizationAccountsParamsSchema.extend({ groupId: z.string().min(1).max(128) }),
/** Omitting `appId` selects the shared Slack app for an organization with no installed bot. */
body: startSlackCredentialGroupConfigurationBodySchema
.omit({ slackBotCredentialId: true, clientId: true, clientSecret: true })
.required({ appId: true, teamId: true }),
.refine((body) => (body.appId === undefined) === (body.teamId === undefined), {
message: 'Select the Slack app and its workspace together',
path: ['appId'],
}),
response: startSlackCredentialGroupConfigurationContract.response,
})
export type OrganizationAccountsSettings = z.output<
Expand Down
Loading
Loading