-
Notifications
You must be signed in to change notification settings - Fork 399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #894 Unable to build options request objects in TypeScript #900
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// tslint:disable:no-implicit-dependencies | ||
import { assert } from 'chai'; | ||
import { BlockSuggestion, DialogSuggestion, InteractiveMessageSuggestion } from './index'; | ||
|
||
describe('options types', () => { | ||
it('should be compatible with block_suggestion payloads', () => { | ||
const payload: BlockSuggestion = { | ||
type: 'block_suggestion', | ||
user: { | ||
id: 'W111', | ||
name: 'primary-owner', | ||
team_id: 'T111', | ||
}, | ||
container: { type: 'view', view_id: 'V111' }, | ||
api_app_id: 'A111', | ||
token: 'verification_token', | ||
block_id: 'block-id-value', | ||
action_id: 'action-id-value', | ||
value: 'search word', | ||
team: { | ||
id: 'T111', | ||
domain: 'workspace-domain', | ||
enterprise_id: 'E111', | ||
enterprise_name: 'Sandbox Org', | ||
}, | ||
view: { | ||
id: 'V111', | ||
team_id: 'T111', | ||
type: 'modal', | ||
blocks: [ | ||
{ | ||
type: 'input', | ||
block_id: '5ar+', | ||
label: { type: 'plain_text', text: 'Label' }, | ||
optional: false, | ||
element: { type: 'plain_text_input', action_id: 'i5IpR' }, | ||
}, | ||
{ | ||
type: 'input', | ||
block_id: 'block-id-value', | ||
label: { type: 'plain_text', text: 'Search' }, | ||
optional: false, | ||
element: { | ||
type: 'external_select', | ||
action_id: 'action-id-value', | ||
placeholder: { type: 'plain_text', text: 'Select an item' }, | ||
}, | ||
}, | ||
{ | ||
type: 'input', | ||
block_id: 'xxx', | ||
label: { type: 'plain_text', text: 'Search (multi)' }, | ||
optional: false, | ||
element: { | ||
type: 'multi_external_select', | ||
action_id: 'yyy', | ||
placeholder: { type: 'plain_text', text: 'Select an item' }, | ||
}, | ||
}, | ||
], | ||
private_metadata: '', | ||
callback_id: 'view-id', | ||
state: { values: {} }, | ||
hash: '111.xxx', | ||
title: { type: 'plain_text', text: 'My App' }, | ||
clear_on_close: false, | ||
notify_on_close: false, | ||
close: { type: 'plain_text', text: 'Cancel' }, | ||
submit: { type: 'plain_text', text: 'Submit' }, | ||
root_view_id: 'V111', | ||
previous_view_id: null, | ||
app_id: 'A111', | ||
external_id: '', | ||
app_installed_team_id: 'T111', | ||
bot_id: 'B111', | ||
}, | ||
}; | ||
assert.equal(payload.action_id, 'action-id-value'); | ||
assert.equal(payload.value, 'search word'); | ||
}); | ||
|
||
it('should be compatible with interactive_message payloads', () => { | ||
const payload: InteractiveMessageSuggestion = { | ||
name: 'bugs_list', | ||
value: 'bot', | ||
callback_id: 'select_remote_1234', | ||
type: 'interactive_message', | ||
team: { | ||
id: 'T012AB0A1', | ||
domain: 'pocket-calculator', | ||
}, | ||
channel: { | ||
id: 'C012AB3CD', | ||
name: 'general', | ||
}, | ||
user: { | ||
id: 'U012A1BCJ', | ||
name: 'bugcatcher', | ||
}, | ||
action_ts: '1481670445.010908', | ||
message_ts: '1481670439.000007', | ||
attachment_id: '1', | ||
token: 'verification_token_string', | ||
}; | ||
assert.equal(payload.callback_id, 'select_remote_1234'); | ||
assert.equal(payload.value, 'bot'); | ||
}); | ||
|
||
it('should be compatible with dialog_suggestion payloads', () => { | ||
const payload: DialogSuggestion = { | ||
type: 'dialog_suggestion', | ||
token: 'verification_token', | ||
action_ts: '1596603332.676855', | ||
team: { | ||
id: 'T111', | ||
domain: 'workspace-domain', | ||
enterprise_id: 'E111', | ||
enterprise_name: 'Sandbox Org', | ||
}, | ||
user: { id: 'W111', name: 'primary-owner', team_id: 'T111' }, | ||
channel: { id: 'C111', name: 'test-channel' }, | ||
name: 'types', | ||
value: 'search keyword', | ||
callback_id: 'dialog-callback-id', | ||
state: 'Limo', | ||
}; | ||
assert.equal(payload.callback_id, 'dialog-callback-id'); | ||
assert.equal(payload.value, 'search keyword'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,51 @@ | ||
import { Option } from '@slack/types'; | ||
import { StringIndexed, XOR } from '../helpers'; | ||
import { AckFn } from '../utilities'; | ||
import { ViewOutput } from '../view/index'; | ||
|
||
/** | ||
* Arguments which listeners and middleware receive to process an options request from Slack | ||
*/ | ||
export interface SlackOptionsMiddlewareArgs<Source extends OptionsSource = OptionsSource> { | ||
payload: OptionsRequest<Source>; | ||
payload: OptionsPayloadFromType<Source>; | ||
body: this['payload']; | ||
options: this['payload']; | ||
ack: OptionsAckFn<Source>; | ||
} | ||
|
||
/** | ||
* A request for options for a select menu with an external data source, wrapped in the standard metadata. The menu | ||
* can have a source of Slack's Block Kit external select elements, dialogs, or legacy interactive components. | ||
* | ||
* This describes the entire JSON-encoded body of a request. | ||
* All sources from which Slack sends options requests. | ||
*/ | ||
export interface OptionsRequest<Source extends OptionsSource = OptionsSource> extends StringIndexed { | ||
export type OptionsSource = 'interactive_message' | 'dialog_suggestion' | 'block_suggestion'; | ||
|
||
export type SlackOptions = BlockSuggestion | InteractiveMessageSuggestion | DialogSuggestion; | ||
|
||
export interface BasicOptionsPayload<Type extends string = string> { | ||
type: Type; | ||
value: string; | ||
type: Source; | ||
} | ||
|
||
type OptionsPayloadFromType<T extends string> = KnownOptionsPayloadFromType<T> extends never | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the same approach with Events API payload handling. |
||
? BasicOptionsPayload<T> | ||
: KnownOptionsPayloadFromType<T>; | ||
|
||
type KnownOptionsPayloadFromType<T extends string> = Extract<SlackOptions, { type: T }>; | ||
|
||
/** | ||
* external data source in blocks | ||
*/ | ||
export interface BlockSuggestion extends StringIndexed { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to remove StringIndexed but it's not backward compatible. |
||
type: 'block_suggestion'; | ||
block_id: string; | ||
action_id: string; | ||
value: string; | ||
|
||
api_app_id: string; | ||
team: { | ||
id: string; | ||
domain: string; | ||
enterprise_id?: string; // undocumented | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "undocumented " here could be outdated comments |
||
enterprise_name?: string; // undocumented | ||
enterprise_id?: string; | ||
enterprise_name?: string; | ||
} | null; | ||
channel?: { | ||
id: string; | ||
|
@@ -34,25 +54,48 @@ export interface OptionsRequest<Source extends OptionsSource = OptionsSource> ex | |
user: { | ||
id: string; | ||
name: string; | ||
team_id?: string; // undocumented | ||
team_id?: string; | ||
}; | ||
token: string; | ||
|
||
name: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; | ||
callback_id: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; | ||
action_ts: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; | ||
|
||
message_ts: Source extends 'interactive_message' ? string : never; | ||
attachment_id: Source extends 'interactive_message' ? string : never; | ||
|
||
api_app_id: Source extends 'block_suggestion' ? string : never; | ||
action_id: Source extends 'block_suggestion' ? string : never; | ||
block_id: Source extends 'block_suggestion' ? string : never; | ||
container: Source extends 'block_suggestion' ? StringIndexed : never; | ||
token: string; // legacy verification token | ||
container: StringIndexed; | ||
// exists for blocks in either a modal or a home tab | ||
view?: ViewOutput; | ||
// exists for enterprise installs | ||
is_enterprise_install?: boolean; | ||
enterprise?: { | ||
id: string; | ||
name: string; | ||
}; | ||
} | ||
|
||
// this appears in the block_suggestions schema, but we're not sure when its present or what its type would be | ||
app_unfurl?: any; | ||
/** | ||
* external data source in attachments | ||
*/ | ||
export interface InteractiveMessageSuggestion extends StringIndexed { | ||
type: 'interactive_message'; | ||
name: string; | ||
value: string; | ||
callback_id: string; | ||
action_ts: string; | ||
message_ts: string; | ||
attachment_id: string; | ||
|
||
team: { | ||
id: string; | ||
domain: string; | ||
enterprise_id?: string; | ||
enterprise_name?: string; | ||
} | null; | ||
channel?: { | ||
id: string; | ||
name: string; | ||
}; | ||
user: { | ||
id: string; | ||
name: string; | ||
team_id?: string; | ||
}; | ||
token: string; // legacy verification token | ||
// exists for enterprise installs | ||
is_enterprise_install?: boolean; | ||
enterprise?: { | ||
|
@@ -62,9 +105,38 @@ export interface OptionsRequest<Source extends OptionsSource = OptionsSource> ex | |
} | ||
|
||
/** | ||
* All sources from which Slack sends options requests. | ||
* external data source in dialogs | ||
*/ | ||
export type OptionsSource = 'interactive_message' | 'dialog_suggestion' | 'block_suggestion'; | ||
export interface DialogSuggestion extends StringIndexed { | ||
type: 'dialog_suggestion'; | ||
name: string; | ||
value: string; | ||
callback_id: string; | ||
action_ts: string; | ||
|
||
team: { | ||
id: string; | ||
domain: string; | ||
enterprise_id?: string; | ||
enterprise_name?: string; | ||
} | null; | ||
channel?: { | ||
id: string; | ||
name: string; | ||
}; | ||
user: { | ||
id: string; | ||
name: string; | ||
team_id?: string; | ||
}; | ||
token: string; // legacy verification token | ||
// exists for enterprise installs | ||
is_enterprise_install?: boolean; | ||
enterprise?: { | ||
id: string; | ||
name: string; | ||
}; | ||
} | ||
|
||
/** | ||
* Type function which given an options source `Source` returns a corresponding type for the `ack()` function. The | ||
|
@@ -96,3 +168,56 @@ export interface OptionGroups<Options> { | |
label: string; | ||
} & Options)[]; | ||
} | ||
|
||
// Don't delete the following interface for backward-compatibility | ||
// We may remove it in v4 or newer | ||
|
||
/** | ||
* A request for options for a select menu with an external data source, wrapped in the standard metadata. The menu | ||
* can have a source of Slack's Block Kit external select elements, dialogs, or legacy interactive components. | ||
* | ||
* This describes the entire JSON-encoded body of a request. | ||
* @deprecated You can use more specific types such as BlockSuggestionPayload | ||
*/ | ||
export interface OptionsRequest<Source extends OptionsSource = OptionsSource> extends StringIndexed { | ||
value: string; | ||
type: Source; | ||
team: { | ||
id: string; | ||
domain: string; | ||
enterprise_id?: string; // undocumented | ||
enterprise_name?: string; // undocumented | ||
} | null; | ||
channel?: { | ||
id: string; | ||
name: string; | ||
}; | ||
user: { | ||
id: string; | ||
name: string; | ||
team_id?: string; // undocumented | ||
}; | ||
token: string; | ||
|
||
name: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; | ||
callback_id: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; | ||
action_ts: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; | ||
|
||
message_ts: Source extends 'interactive_message' ? string : never; | ||
attachment_id: Source extends 'interactive_message' ? string : never; | ||
|
||
api_app_id: Source extends 'block_suggestion' ? string : never; | ||
action_id: Source extends 'block_suggestion' ? string : never; | ||
block_id: Source extends 'block_suggestion' ? string : never; | ||
container: Source extends 'block_suggestion' ? StringIndexed : never; | ||
|
||
// this appears in the block_suggestions schema, but we're not sure when its present or what its type would be | ||
app_unfurl?: any; | ||
|
||
// exists for enterprise installs | ||
is_enterprise_install?: boolean; | ||
enterprise?: { | ||
id: string; | ||
name: string; | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may sound a bit unnatural but this is consistent with other union types (e.g., SlackAction, SlackEvent, SlackShortcut, SlackViewAction)