Repository: speee/jsx-slack
Pull request #136 (2020-03-20 07:05:18)

Add experimental filter props to <ConversationsSelect>
Added 3 filter props to `<ConversationsSelect>`, for assigning [filter composition object (beta)](https://api.slack.com/reference/block-kit/composition-objects#filter_conversations): `include`, `excludeExternalSharedChannels`, and `excludeBotUsers`.

jsx-slack should mark them as experimental during provided in beta.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89e28a4..7cd497f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,8 @@
 
 ### Added
 
-- Added `responseUrlEnabled` property for modal's input component to `<ConversationsSelect>` and `<ChannelsSelect>` ([#135](https://github.com/speee/jsx-slack/pull/135))
+- `responseUrlEnabled` property for modal's input component to `<ConversationsSelect>` and `<ChannelsSelect>` ([#134](https://github.com/speee/jsx-slack/issues/134), [#135](https://github.com/speee/jsx-slack/pull/135))
+- Experimental filter properties to `<ConversationsSelect>`: `include`, `excludeExternalSharedChannels`, and `excludeBotUsers` ([#133](https://github.com/speee/jsx-slack/issues/133), [#136](https://github.com/speee/jsx-slack/pull/136))
 
 ## v1.5.1 - 2020-03-16
 
diff --git a/README.md b/README.md
index 085765a..fd2a279 100644
--- a/README.md
+++ b/README.md
@@ -177,10 +177,13 @@ By using jsx-slack, you can build a template with piling up Block Kit blocks by
 
   <Input type="text" name="subject" label="Subject" required />
   <Textarea name="message" label="Message" maxLength={500} />
+
   <ConversationsSelect
     name="shareWith"
     label="Share with..."
     required
+    include={['public', 'im']}
+    excludeBotUsers
     responseUrlEnabled
   />
 
diff --git a/demo/example.js b/demo/example.js
index 4a37b3a..6912b47 100644
--- a/demo/example.js
+++ b/demo/example.js
@@ -37,10 +37,13 @@ export const modal = `
 
   <Input type="text" name="subject" label="Subject" required />
   <Textarea name="message" label="Message" maxLength="500" />
+
   <ConversationsSelect
     name="shareWith"
     label="Share with..."
     required
+    include="public im"
+    excludeBotUsers
     responseUrlEnabled
   />
 
diff --git a/demo/schema.js b/demo/schema.js
index 0bd5fea..f3cd079 100644
--- a/demo/schema.js
+++ b/demo/schema.js
@@ -281,6 +281,9 @@ const schema = {
     attrs: {
       placeholder: null,
       initialConversation: null,
+      include: ['im', 'mpim', 'private', 'public'],
+      excludeBotUsers: [],
+      excludeExternalSharedChannels: [],
       ...blockInteractiveCommonAttrs,
       ...multipleSelectAttrs,
       ...inputComponentAttrs,
diff --git a/docs/block-elements.md b/docs/block-elements.md
index a4081d3..0fdf720 100644
--- a/docs/block-elements.md
+++ b/docs/block-elements.md
@@ -277,6 +277,13 @@ A select menu with options consisted of any type of conversations in the current
 - `placeholder` (optional): A plain text to be shown at first.
 - `initialConversation` (optional): The initial conversation ID. It can pass multiple string values by array when `multiple` is enabled.
 - `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog.
+- _`include`_ (optional): An array of the kind or a string of space-separated kinds, to indicate which kind of conversation types are included in list. By default, all conversation types are included. _(experimental)_
+  - `public`: Public channel
+  - `private`: Private channel
+  - `im`: Direct message
+  - `mpim`: Group direct message
+- _`excludeExternalSharedChannels`_ (optional): A boolean value whether to exclude external [shared channels](https://api.slack.com/enterprise/shared-channels) from conversations list. _(experimental)_
+- _`excludeBotUsers`_ (optional): A boolean value whether to exclude bot users from conversations list. _(experimental)_
 
 ##### Props for [multiple select]
 
diff --git a/src/block-kit/elements/Select.tsx b/src/block-kit/elements/Select.tsx
index 38d2635..7130edf 100644
--- a/src/block-kit/elements/Select.tsx
+++ b/src/block-kit/elements/Select.tsx
@@ -108,7 +108,20 @@ type UsersSelectProps = WithInputProps<
 >
 
 // Conversations select
-interface SingleConversationsSelectProps extends SingleSelectPropsBase {
+interface ConversationsSelectPropsBase {
+  /** [Experimental] Properties to filter conversations list are beta. */
+  include?: string | ('im' | 'mpim' | 'private' | 'public')[]
+
+  /** [Experimental] Properties to filter conversations list are beta. */
+  excludeExternalSharedChannels?: boolean
+
+  /** [Experimental] Properties to filter conversations list are beta. */
+  excludeBotUsers?: boolean
+}
+
+interface SingleConversationsSelectProps
+  extends SingleSelectPropsBase,
+    ConversationsSelectPropsBase {
   initialConversation?: string
   children?: undefined
 }
@@ -117,7 +130,9 @@ interface SingleConversationsSelectInputProps {
   responseUrlEnabled?: boolean
 }
 
-interface MultiConversationsSelectProps extends MultiSelectPropsBase {
+interface MultiConversationsSelectProps
+  extends MultiSelectPropsBase,
+    ConversationsSelectPropsBase {
   initialConversation?: string | string[]
   children?: undefined
 }
@@ -355,6 +370,27 @@ export const UsersSelect: JSXSlack.FC<UsersSelectProps> = props => {
 }
 
 export const ConversationsSelect: JSXSlack.FC<ConversationsSelectProps> = props => {
+  let filterComposition: SlackConversationsSelect['filter'] = {}
+  let { include } = props
+
+  if (include) {
+    if (!Array.isArray(include)) include = include.split(' ') as any[]
+
+    include = [...new Set(include)].filter(o =>
+      ['im', 'mpim', 'private', 'public'].includes(o)
+    )
+
+    if (include.length > 0) filterComposition.include = include
+  }
+
+  if (props.excludeBotUsers !== undefined)
+    filterComposition.exclude_bot_users = !!props.excludeBotUsers
+
+  if (props.excludeExternalSharedChannels !== undefined)
+    filterComposition.exclude_external_shared_channels = !!props.excludeExternalSharedChannels
+
+  if (Object.keys(filterComposition).length === 0) filterComposition = undefined
+
   const element = props.multiple ? (
     <ObjectOutput<MultiConversationsSelect>
       type="multi_conversations_select"
@@ -362,12 +398,14 @@ export const ConversationsSelect: JSXSlack.FC<ConversationsSelectProps> = props
       initial_conversations={
         props.initialConversation ? wrap(props.initialConversation) : undefined
       }
+      filter={filterComposition}
     />
   ) : (
     <ObjectOutput<SlackConversationsSelect>
       type="conversations_select"
       {...baseProps(props)}
       initial_conversation={props.initialConversation}
+      filter={filterComposition}
       response_url_enabled={
         props.responseUrlEnabled !== undefined
           ? !!props.responseUrlEnabled


Test patch:
diff --git a/test/block-kit/block-elements/interactive-components.tsx b/test/block-kit/block-elements/interactive-components.tsx
index ef5fa05..c3c797b 100644
--- a/test/block-kit/block-elements/interactive-components.tsx
+++ b/test/block-kit/block-elements/interactive-components.tsx
@@ -494,6 +494,65 @@ describe('Interactive components', () => {
         )
       ).toStrictEqual([selectAction])
     })
+
+    it('adds filter composition object when specified filter props', () => {
+      const filterCmp = (element: JSXSlack.Node) =>
+        JSXSlack(
+          <Blocks>
+            <Section>test{element}</Section>
+          </Blocks>
+        )[0].accessory.filter
+
+      expect(filterCmp(<ConversationsSelect />)).toBeUndefined()
+      expect(filterCmp(<ConversationsSelect include={[]} />)).toBeUndefined()
+      expect(filterCmp(<ConversationsSelect include="" />)).toBeUndefined()
+      expect(filterCmp(<ConversationsSelect include=" " />)).toBeUndefined()
+      expect(filterCmp(<ConversationsSelect include="?" />)).toBeUndefined()
+
+      expect(
+        filterCmp(
+          <ConversationsSelect include={['public', 'private', 'im', 'mpim']} />
+        )
+      ).toStrictEqual({ include: ['public', 'private', 'im', 'mpim'] })
+      expect(
+        filterCmp(
+          <ConversationsSelect multiple include="public private im mpim" />
+        )
+      ).toStrictEqual({ include: ['public', 'private', 'im', 'mpim'] })
+      expect(
+        filterCmp(<ConversationsSelect include={['im', 'im']} />)
+      ).toStrictEqual({ include: ['im'] })
+      expect(
+        filterCmp(<ConversationsSelect include="unknown im" />)
+      ).toStrictEqual({ include: ['im'] })
+      expect(
+        filterCmp(<ConversationsSelect include="   public and  private " />)
+      ).toStrictEqual({ include: ['public', 'private'] })
+
+      expect(filterCmp(<ConversationsSelect excludeBotUsers />)).toStrictEqual({
+        exclude_bot_users: true,
+      })
+
+      expect(
+        filterCmp(<ConversationsSelect excludeExternalSharedChannels />)
+      ).toStrictEqual({
+        exclude_external_shared_channels: true,
+      })
+
+      expect(
+        filterCmp(
+          <ConversationsSelect
+            include="public im"
+            excludeBotUsers
+            excludeExternalSharedChannels
+          />
+        )
+      ).toStrictEqual({
+        include: ['public', 'im'],
+        exclude_bot_users: true,
+        exclude_external_shared_channels: true,
+      })
+    })
   })
 
   describe('<ChannelsSelect>', () => {