Skip to content

Commit 1ca0851

Browse files
fix(slack): default custom bot setup to full permissions
1 parent e3d3625 commit 1ca0851

5 files changed

Lines changed: 31 additions & 60 deletions

File tree

‎apps/docs/content/docs/integrations/slack.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,15 +2184,15 @@ Set the purpose (description) for a Slack channel (max 250 characters).
21842184

21852185
### Lists
21862186

2187-
Lists actions require a custom Slack bot and a paid Slack plan. Enable **Manage Lists** during bot setup. For an existing app, add `lists:read` and `lists:write` in Slack, reinstall it, then reconnect in Sim.
2187+
Lists actions require a custom Slack bot and a paid Slack plan. **Manage Lists** is selected by default during bot setup. For an existing app, add `lists:read` and `lists:write` in Slack, reinstall it, then reconnect in Sim.
21882188

21892189
- Run **Read List Items** with **Include List Schema** enabled to get the column IDs used in `column_id`. Updates also need the row's `id` as `row_id`.
21902190
- **Initial Fields** and **Cells** accept JSON arrays of typed values. Text uses `rich_text`; checkboxes use booleans. See Slack's [field formats](https://docs.slack.dev/reference/methods/slackLists.items.create/) for examples.
21912191
- To read every row, pass `nextCursor` into **Cursor** until it is empty. **Archived Rows** reads archived rows instead of active rows.
21922192

21932193
### Canvases
21942194

2195-
For custom bots, enable **Manage canvases** during setup. Existing apps need `canvases:write` for changes, `canvases:read` for section lookup, and `files:read` for metadata. Reinstall the app after changing scopes, then reconnect in Sim.
2195+
For custom bots, **Manage canvases** is selected by default during setup. Existing apps need `canvases:write` for changes, `canvases:read` for section lookup, and `files:read` for metadata. Reinstall the app after changing scopes, then reconnect in Sim.
21962196

21972197
**Edit Canvas** can replace the whole document when **Section ID** is empty. **Get Canvas Info** and **List Canvases** return metadata, not document contents. See Slack's [Canvas guide](https://docs.slack.dev/surfaces/canvases/) for details.
21982198
{/* MANUAL-CONTENT-END */}

‎apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.test.tsx‎

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ vi.mock('@sim/emcn', () => ({
77
Wizard: Object.assign(({ children }: { children: ReactNode }) => <div>{children}</div>, {
88
Step: ({ children }: { children: ReactNode }) => <section>{children}</section>,
99
}),
10-
ChipModalField: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
10+
ChipModalField: ({ title, children }: { title: string; children?: ReactNode }) => (
11+
<div>
12+
<span>{title}</span>
13+
{children}
14+
</div>
15+
),
1116
ChipDropdown: ({
1217
value,
1318
onChange,
@@ -50,6 +55,7 @@ vi.mock('@/triggers/webhook-url', () => ({
5055
buildSlackCustomBotRequestUrl: () => 'https://sim.test/api/webhooks/slack/custom/test-bot',
5156
}))
5257

58+
import { SLACK_MANAGED_USER_SCOPES } from '@/lib/credential-groups/slack-managed-user-scopes'
5359
import { ConnectSlackBotModal } from '@/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal'
5460

5561
describe('custom Slack bot permission selection', () => {
@@ -74,17 +80,20 @@ describe('custom Slack bot permission selection', () => {
7480
}
7581

7682
it.each([{ workspaceId: 'workspace-test' }, { organizationId: 'organization-test' }])(
77-
'omits Lists and Canvas scopes by default for %j',
83+
'includes Lists, Canvas, and full member scopes by default for %j',
7884
(owner) => {
7985
act(() => root.render(<ConnectSlackBotModal {...owner} open onOpenChange={vi.fn()} />))
8086
expect(botScopes()).toContain('chat:write')
8187
for (const scope of ['lists:read', 'lists:write', 'canvases:read', 'canvases:write']) {
82-
expect(botScopes()).not.toContain(scope)
88+
expect(botScopes()).toContain(scope)
8389
}
90+
const manifest = JSON.parse(container.querySelector('pre')!.textContent!)
91+
expect(manifest.oauth_config.scopes.user).toEqual([...SLACK_MANAGED_USER_SCOPES].sort())
92+
expect(container.textContent).not.toContain('Member access')
8493
}
8594
)
8695

87-
it('adds scopes only after selection and resets opt-in permissions when reopened', () => {
96+
it('selects all permissions by default, allows deselection, and restores defaults when reopened', () => {
8897
const onOpenChange = vi.fn()
8998
const render = (open: boolean) => {
9099
act(() =>
@@ -99,25 +108,23 @@ describe('custom Slack bot permission selection', () => {
99108
}
100109
render(true)
101110
const permissions = container.querySelector<HTMLSelectElement>('select')!
102-
for (const capability of ['action_lists', 'action_canvases']) {
103-
expect(
104-
permissions.querySelector<HTMLOptionElement>(`option[value="${capability}"]`)!.selected
105-
).toBe(false)
111+
for (const option of permissions.options) {
112+
expect(option.selected, option.value).toBe(true)
106113
}
107114
act(() => {
108115
for (const capability of ['action_lists', 'action_canvases']) {
109116
permissions.querySelector<HTMLOptionElement>(`option[value="${capability}"]`)!.selected =
110-
true
117+
false
111118
}
112119
permissions.dispatchEvent(new Event('change', { bubbles: true }))
113120
})
114-
expect(botScopes()).toEqual(
115-
expect.arrayContaining(['lists:read', 'lists:write', 'canvases:read', 'canvases:write'])
116-
)
121+
for (const scope of ['lists:read', 'lists:write', 'canvases:read', 'canvases:write']) {
122+
expect(botScopes()).not.toContain(scope)
123+
}
117124
render(false)
118125
render(true)
119126
for (const scope of ['lists:read', 'lists:write', 'canvases:read', 'canvases:write']) {
120-
expect(botScopes()).not.toContain(scope)
127+
expect(botScopes()).toContain(scope)
121128
}
122129
})
123130
it.each([{ workspaceId: 'workspace-test' }, { organizationId: 'organization-test' }])(

‎apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx‎

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ import { SlackIcon } from '@/components/icons'
1919
import { SlackAppManifest } from '@/components/integrations/slack-app-manifest'
2020
import { resourceScopeFields, resourceScopeFromOwner } from '@/lib/core/resource-scope'
2121
import { getBaseUrl } from '@/lib/core/utils/urls'
22-
import {
23-
SLACK_MANAGED_USER_SCOPES,
24-
SLACK_SEARCH_USER_SCOPES,
25-
} from '@/lib/credential-groups/slack-managed-user-scopes'
2622
import { SLACK_CUSTOM_BOT_PROVIDER_ID } from '@/lib/oauth/types'
2723
import {
2824
useCreateScopedCredential,
@@ -136,9 +132,6 @@ export function ConnectSlackBotModal({
136132
const [appName, setAppName] = useState(initialDisplayName ?? '')
137133
const [appDescription, setAppDescription] = useState(initialDescription ?? '')
138134
const [selected, setSelected] = useState<Set<string>>(() => new Set(DEFAULT_CAPABILITIES))
139-
const [memberAccess, setMemberAccess] = useState<'search' | 'workflow'>(
140-
isReconnect ? 'workflow' : 'search'
141-
)
142135
const [slashCommands, setSlashCommands] = useState<SlackSlashCommandDraft[]>([])
143136
const [signingSecret, setSigningSecret] = useState('')
144137
const [botToken, setBotToken] = useState('')
@@ -154,7 +147,6 @@ export function ConnectSlackBotModal({
154147
setAppName(initialDisplayName ?? '')
155148
setAppDescription(initialDescription ?? '')
156149
setSelected(new Set(DEFAULT_CAPABILITIES))
157-
setMemberAccess(isReconnect ? 'workflow' : 'search')
158150
setSlashCommands([])
159151
setSigningSecret('')
160152
setBotToken('')
@@ -185,12 +177,7 @@ export function ConnectSlackBotModal({
185177
const managedUserAuthorization = capabilities.has(
186178
SLACK_MANAGED_USER_AUTHORIZATION_CAPABILITY.id
187179
)
188-
? getSlackManagedUserAuthorizationManifestConfig(
189-
getBaseUrl(),
190-
searchOnly || memberAccess === 'search'
191-
? SLACK_SEARCH_USER_SCOPES
192-
: SLACK_MANAGED_USER_SCOPES
193-
)
180+
? getSlackManagedUserAuthorizationManifestConfig(getBaseUrl())
194181
: undefined
195182
const manifest = buildSlackManifest(capabilities, {
196183
appName: appName.trim() || DEFAULT_APP_NAME,
@@ -214,7 +201,6 @@ export function ConnectSlackBotModal({
214201
appDescription,
215202
slashCommands,
216203
requestUrl,
217-
memberAccess,
218204
searchOnly,
219205
])
220206

@@ -299,8 +285,6 @@ export function ConnectSlackBotModal({
299285
slashCommandsError={slashCommandsError}
300286
capabilityIds={capabilityIds}
301287
onCapabilityIdsChange={setCapabilityIds}
302-
memberAccess={memberAccess}
303-
onMemberAccessChange={setMemberAccess}
304288
/>
305289
</Wizard.Step>
306290
<Wizard.Step title={isReconnect ? 'Open your app in Slack' : 'Create the app in Slack'}>
@@ -362,8 +346,6 @@ interface StepConfigureProps {
362346
slashCommandsError: string | null
363347
capabilityIds: string[]
364348
onCapabilityIdsChange: (next: string[]) => void
365-
memberAccess: 'search' | 'workflow'
366-
onMemberAccessChange: (access: 'search' | 'workflow') => void
367349
}
368350
function StepConfigure({
369351
searchOnly,
@@ -378,8 +360,6 @@ function StepConfigure({
378360
slashCommandsError,
379361
capabilityIds,
380362
onCapabilityIdsChange,
381-
memberAccess,
382-
onMemberAccessChange,
383363
}: StepConfigureProps) {
384364
const canConfigureApp = !searchOnly && !reconnect
385365
const allSelected = capabilityIds.length === CUSTOM_BOT_CAPABILITIES.length
@@ -425,22 +405,6 @@ function StepConfigure({
425405
/>
426406
</ChipModalField>
427407
)}
428-
{canConfigureApp &&
429-
capabilityIds.includes(SLACK_MANAGED_USER_AUTHORIZATION_CAPABILITY.id) && (
430-
<ChipModalField
431-
type='dropdown'
432-
title='Member access'
433-
value={memberAccess}
434-
onChange={(value) => {
435-
if (value === 'search' || value === 'workflow') onMemberAccessChange(value)
436-
}}
437-
options={[
438-
{ value: 'search', label: 'Search documents' },
439-
{ value: 'workflow', label: 'Workflow tools' },
440-
]}
441-
hint='Choose the same access when configuring this app for member accounts.'
442-
/>
443-
)}
444408
{canConfigureApp && (
445409
<SlashCommandsEditor
446410
commands={slashCommands}

‎apps/sim/blocks/blocks/slack-resources.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ describe('Slack List and Canvas operations', () => {
213213
})
214214
})
215215

216-
it('keeps Lists scopes out of native connections and includes them in opt-in custom bot setup', () => {
216+
it('keeps Lists scopes out of native connections and includes them in custom bot setup', () => {
217217
for (const scope of ['lists:read', 'lists:write']) {
218218
expect(getScopesForService('slack')).not.toContain(scope)
219219
expect(SLACK_MANAGED_USER_SCOPES).not.toContain(scope)

‎apps/sim/triggers/slack/capabilities.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [
8686
id: 'trigger_file_shared',
8787
label: 'File shared',
8888
description: 'Trigger when a file is shared in a channel your bot can see.',
89-
defaultChecked: false,
89+
defaultChecked: true,
9090
group: 'trigger',
9191
scopes: ['files:read'],
9292
events: ['file_shared'],
@@ -95,7 +95,7 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [
9595
id: 'trigger_member_channel',
9696
label: 'Member joined / left channel',
9797
description: 'Trigger when a member joins or leaves a channel your bot is in.',
98-
defaultChecked: false,
98+
defaultChecked: true,
9999
group: 'trigger',
100100
scopes: ['channels:read', 'groups:read'],
101101
events: ['member_joined_channel', 'member_left_channel'],
@@ -104,7 +104,7 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [
104104
id: 'trigger_channel_lifecycle',
105105
label: 'Channel created / archived / renamed',
106106
description: 'Trigger when a channel is created, archived, or renamed.',
107-
defaultChecked: false,
107+
defaultChecked: true,
108108
group: 'trigger',
109109
scopes: ['channels:read', 'groups:read'],
110110
events: ['channel_created', 'channel_archive', 'channel_rename'],
@@ -113,7 +113,7 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [
113113
id: 'trigger_pin',
114114
label: 'Pin added / removed',
115115
description: 'Trigger when a message is pinned or unpinned in a channel.',
116-
defaultChecked: false,
116+
defaultChecked: true,
117117
group: 'trigger',
118118
scopes: ['pins:read'],
119119
events: ['pin_added', 'pin_removed'],
@@ -122,7 +122,7 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [
122122
id: 'trigger_team_join',
123123
label: 'Member joined workspace',
124124
description: 'Trigger when a new member joins the workspace.',
125-
defaultChecked: false,
125+
defaultChecked: true,
126126
group: 'trigger',
127127
scopes: ['users:read'],
128128
events: ['team_join'],
@@ -132,7 +132,7 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [
132132
label: 'Manage Lists',
133133
description:
134134
'Read Lists and column schemas; create, update, and delete rows. Requires a paid Slack plan.',
135-
defaultChecked: false,
135+
defaultChecked: true,
136136
group: 'action',
137137
scopes: ['lists:read', 'lists:write'],
138138
events: [],
@@ -141,7 +141,7 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [
141141
id: 'action_canvases',
142142
label: 'Manage canvases',
143143
description: 'Create and edit canvases, read metadata, find sections, and delete canvases.',
144-
defaultChecked: false,
144+
defaultChecked: true,
145145
group: 'action',
146146
scopes: ['canvases:read', 'canvases:write', 'files:read'],
147147
events: [],

0 commit comments

Comments
 (0)