def open_modal(trigger_id: str): try: view = View(type="modal", callback_id="modal-id", title=PlainTextObject(text="Awesome Modal"), submit=PlainTextObject(text="Submit"), close=PlainTextObject(text="Cancel"), blocks=[ InputBlock(block_id="b-id-1", label=PlainTextObject(text="Input label"), element=ConversationSelectElement( action_id="a", default_to_current_conversation=True, )), InputBlock(block_id="b-id-2", label=PlainTextObject(text="Input label"), element=ConversationMultiSelectElement( action_id="a", max_selected_items=2, default_to_current_conversation=True, )), ]) api_response = client.views_open(trigger_id=trigger_id, view=view) return make_response("", 200) except SlackApiError as e: code = e.response["error"] return make_response(f"Failed to open a modal due to {code}", 200)
def on_main_modal_submit(rqst: ViewRequest, input_values: Dict): params = session[SESSION_KEY]['params'] # The input_values is a dictionary of key=action_id and value=user-input # since all action_id are formulated with dots (.), just use the # last token as the variable name when form results = {k.rpartition('.')[-1]: v for k, v in input_values.items()} results.update(dict(clicks=params['clicks'])) modal = Modal( rqst, view=View(type="modal", title=PlainTextObject(text='Modal Results'), close=PlainTextObject(text='Done'), clear_on_close=True, blocks=[ SectionBlock(text=PlainTextObject( text="Input results in raw JSON")), SectionBlock(text=MarkdownTextObject( text="```" + json.dumps(results, indent=3) + "```")) ])) return modal.push()
def test_confirm_overrides(self): confirm = ConfirmObject( title=PlainTextObject(text="some title"), text=MarkdownTextObject(text="are you sure?"), confirm=PlainTextObject(text="I'm really sure"), deny=PlainTextObject(text="Nevermind"), ) expected = { "confirm": { "emoji": True, "text": "I'm really sure", "type": "plain_text" }, "deny": { "emoji": True, "text": "Nevermind", "type": "plain_text" }, "text": { "text": "are you sure?", "type": "mrkdwn", "verbatim": False }, "title": { "emoji": True, "text": "some title", "type": "plain_text" }, } self.assertDictEqual(confirm.to_dict(), expected)
def from_view(cls, view): new_view = cls(type=view['type'], title=PlainTextObject(text=view['title']['text']), callback_id=view['callback_id'], blocks=view['blocks'], external_id=view['external_id'] or None, clear_on_close=view['clear_on_close'] or None) if view['close']: new_view.close = PlainTextObject(text=view['close']['text']) if view['submit']: new_view.submit = PlainTextObject(text=view['submit']['text']) new_view.notify_on_close = view['notify_on_close'] new_view.view_id = view['id'] if view['private_metadata']: try: new_view.private_metadata = json.loads( view['private_metadata']) except json.JSONDecodeError: new_view.private_metadata = view['private_metadata'] new_view.state_values = view['state']['values'] new_view.view_hash = view['hash'] return new_view
def delayed_update_view(rqst: ViewRequest, view: View, delay: int): sleep(delay) modal = Modal(rqst=rqst, view=view, detached=True) # If the User clicks on Done, then the `done_booping` handler will be # invoked as a result of the view close. modal.notify_on_close = done_booping view = modal.view view.title = PlainTextObject(text='Booped!') view.close = PlainTextObject(text='Done') view.blocks[0] = SectionBlock(text=PlainTextObject( text=f'First boop after {delay} seconds')) button = view.add_block( SectionBlock(text=PlainTextObject(text='Click button to boop again.'), block_id=cmd.prog + ".boop")) button.accessory = ButtonElement(text='Boop', action_id=button.block_id, value='boop') rqst.app.ic.block_action.on(button.block_id, on_boop_button) res = modal.update() if not res.get('ok'): rqst.app.log.error(f'failed to boop: {res}')
def setUp(self) -> None: self.elements = [ ButtonElement(text=PlainTextObject(text="Click me"), action_id="reg_button", value="1"), LinkButtonElement(text=PlainTextObject(text="URL Button"), url="http://google.com"), ]
def test_basic_json(self): self.assertDictEqual( {"text": "some text", "type": "plain_text"}, PlainTextObject(text="some text").to_dict(), ) self.assertDictEqual( {"text": "some text", "emoji": False, "type": "plain_text"}, PlainTextObject(text="some text", emoji=False).to_dict(), )
def test_text_length_with_object(self): with self.assertRaises(SlackObjectFormationError): plaintext = PlainTextObject(text=STRING_301_CHARS) ConfirmObject(title=PlainTextObject(text="title"), text=plaintext).to_dict() with self.assertRaises(SlackObjectFormationError): markdown = MarkdownTextObject(text=STRING_301_CHARS) ConfirmObject(title=PlainTextObject(text="title"), text=markdown).to_dict()
def test_invalid_type_value(self): modal_view = View( type="modallll", callback_id="modal-id", title=PlainTextObject(text="Awesome Modal"), submit=PlainTextObject(text="Submit"), close=PlainTextObject(text="Cancel"), blocks=[ InputBlock(block_id="b-id", label=PlainTextObject(text="Input label"), element=PlainTextInputElement(action_id="a-id")), ]) with self.assertRaises(SlackObjectFormationError): modal_view.validate_json()
def on_view2_submit(rqst: ViewRequest): # same technique as on_main_modal_submit; in this case disable the submit # button only allow the User to click the "Done" (close) button. modal = Modal(rqst) view = modal.view view.title = PlainTextObject(text='Final Modal View') view.close = PlainTextObject(text='Done') view.submit = None view.add_block(SectionBlock(text=MarkdownTextObject(text='Final bit.'))) return modal.update()
def test_close_in_home_tab(self): modal_view = View(type="home", callback_id="home-tab-id", close=PlainTextObject(text="Cancel"), blocks=[DividerBlock()]) with self.assertRaises(SlackObjectFormationError): modal_view.validate_json()
def test_basic_json(self): self.elements = [ ImageElement( image_url= "https://api.slack.com/img/blocks/bkb_template_images/palmtree.png", alt_text="palmtree"), PlainTextObject(text="Just text"), ] e = { "elements": [ { "type": "image", "image_url": "https://api.slack.com/img/blocks/bkb_template_images/palmtree.png", "alt_text": "palmtree" }, { "type": "plain_text", "text": "Just text" }, ], "type": "context", } d = ContextBlock(elements=self.elements).to_dict() self.assertDictEqual(e, d) with self.assertRaises(SlackObjectFormationError): ContextBlock(elements=self.elements * 6).to_dict()
def test_basic_json(self): expected = { "confirm": { "emoji": True, "text": "Yes", "type": "plain_text" }, "deny": { "emoji": True, "text": "No", "type": "plain_text" }, "text": { "text": "are you sure?", "type": "mrkdwn", "verbatim": False }, "title": { "emoji": True, "text": "some title", "type": "plain_text" }, } simple_object = ConfirmObject( title=PlainTextObject(text="some title"), text=MarkdownTextObject(text="are you sure?")).to_dict() self.assertDictEqual(simple_object, expected)
def test_from_single_value(self): option = Option(text=PlainTextObject(text="option_1"), value="option_1") self.assertDictEqual( option.to_dict("text"), option.from_single_value("option_1").to_dict("text"), )
def test_options_length(self): with self.assertRaises(SlackObjectFormationError): StaticSelectElement( placeholder=PlainTextObject(text="select"), action_id="selector", options=[self.option_one] * 101, ).to_dict()
def test_home_tab_construction(self): home_tab_view = View( type="home", blocks=[ SectionBlock(text=MarkdownTextObject( text="*Here's what you can do with Project Tracker:*"), ), ActionsBlock(elements=[ ButtonElement( text=PlainTextObject(text="Create New Task", emoji=True), style="primary", value="create_task", ), ButtonElement( text=PlainTextObject(text="Create New Project", emoji=True), value="create_project", ), ButtonElement( text=PlainTextObject(text="Help", emoji=True), value="help", ), ], ), ContextBlock(elements=[ ImageElement( image_url= "https://api.slack.com/img/blocks/bkb_template_images/placeholder.png", alt_text="placeholder", ), ], ), SectionBlock( text=MarkdownTextObject(text="*Your Configurations*"), ), DividerBlock(), SectionBlock( text=MarkdownTextObject( text= "*#public-relations*\n<fakelink.toUrl.com|PR Strategy 2019> posts new tasks, comments, and project updates to <fakelink.toChannel.com|#public-relations>" ), accessory=ButtonElement( text=PlainTextObject(text="Edit", emoji=True), value="public-relations", ), ), ], ) home_tab_view.validate_json()
def test_json_simple(self): button = ButtonElement(text=PlainTextObject(text="button text"), action_id="some_button", value="button_123").to_dict() coded = { "text": {"emoji": True, "text": "button text", "type": "plain_text"}, "action_id": "some_button", "value": "button_123", "type": "button", } self.assertDictEqual(button, coded)
def test_passing_text_objects(self): direct_construction = ConfirmObject( title=PlainTextObject(text="title"), text=MarkdownTextObject(text="Are you sure?")) mrkdwn = MarkdownTextObject(text="Are you sure?") preconstructed = ConfirmObject(title=PlainTextObject(text="title"), text=mrkdwn) self.assertDictEqual(direct_construction.to_dict(), preconstructed.to_dict()) plaintext = PlainTextObject(text="Are you sure?", emoji=False) passed_plaintext = ConfirmObject(title=PlainTextObject(text="title"), text=plaintext) self.assertDictEqual( passed_plaintext.to_dict(), { "confirm": { "emoji": True, "text": "Yes", "type": "plain_text" }, "deny": { "emoji": True, "text": "No", "type": "plain_text" }, "text": { "emoji": False, "text": "Are you sure?", "type": "plain_text" }, "title": { "emoji": True, "text": "title", "type": "plain_text" }, }, )
def test_json_with_confirm(self): confirm = ConfirmObject(title=PlainTextObject(text="really?"), text=PlainTextObject(text="are you sure?")) button = ButtonElement( text=PlainTextObject(text="button text"), action_id="some_button", value="button_123", style="primary", confirm=confirm, ).to_dict() coded = { "text": {"emoji": True, "text": "button text", "type": "plain_text"}, "action_id": "some_button", "value": "button_123", "type": "button", "style": "primary", "confirm": confirm.to_dict(), } self.assertDictEqual(button, coded)
def test_from_list_of_json_objects(self): json_objects = [ PlainTextObject(text="foo"), MarkdownTextObject(text="bar"), ] output = extract_json(json_objects) expected = {"result": [ {"type": "plain_text", "text": "foo", "emoji": True}, {"type": "mrkdwn", "text": "bar", "verbatim": False}, ]} self.assertDictEqual(expected, {"result": output})
def test_json_with_confirm(self): confirm = ConfirmObject(title=PlainTextObject(text="title"), text=PlainTextObject(text="text")) select = StaticSelectElement( placeholder=PlainTextObject(text="selectedValue"), action_id="dropdown", options=self.options, confirm=confirm, ).to_dict() coded = { "placeholder": { "emoji": True, "text": "selectedValue", "type": "plain_text", }, "action_id": "dropdown", "options": [o.to_dict() for o in self.options], "confirm": confirm.to_dict(), "type": "static_select", } self.assertDictEqual(select, coded)
def test_input_blocks_in_home_tab(self): modal_view = View( type="home", callback_id="home-tab-id", blocks=[ InputBlock(block_id="b-id", label=PlainTextObject(text="Input label"), element=PlainTextInputElement(action_id="a-id")), ]) with self.assertRaises(SlackObjectFormationError): modal_view.validate_json()
def test_from_single_json_object(self): single_json_object = PlainTextObject.from_str("foo") output = extract_json(single_json_object) expected = { "result": { "type": "plain_text", "text": "foo", "emoji": True } } self.assertDictEqual(expected, {"result": output})
def slack_app(): if not signature_verifier.is_valid_request(request.get_data(), request.headers): return make_response("invalid request", 403) if "payload" in request.form: payload = json.loads(request.form["payload"]) if payload["type"] == "shortcut" \ and payload["callback_id"] == "open-modal-shortcut": # Open a new modal by a global shortcut try: view = View( type="modal", callback_id="modal-id", title=PlainTextObject(text="Awesome Modal"), submit=PlainTextObject(text="Submit"), close=PlainTextObject(text="Cancel"), blocks=[ InputBlock( block_id="b-id", label=PlainTextObject(text="Input label"), element=PlainTextInputElement(action_id="a-id") ) ] ) api_response = client.views_open( trigger_id=payload["trigger_id"], view=view, ) return make_response("", 200) except SlackApiError as e: code = e.response["error"] return make_response(f"Failed to open a modal due to {code}", 200) if payload["type"] == "view_submission" \ and payload["view"]["callback_id"] == "modal-id": # Handle a data submission request from the modal submitted_data = payload["view"]["state"]["values"] print(submitted_data) # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}} return make_response("", 200) return make_response("", 404)
def test_json(self): button = LinkButtonElement(text=PlainTextObject(text="button text"), url="http://google.com") self.assertDictEqual( button.to_dict(), { "text": {"emoji": True, "text": "button text", "type": "plain_text"}, "url": "http://google.com", "type": "button", "value": "", "action_id": button.action_id, }, )
def test_json(self): self.assertDictEqual( ExternalDataSelectElement( placeholder=PlainTextObject(text="selectedValue"), action_id="dropdown", min_query_length=5 ).to_dict(), { "placeholder": { "emoji": True, "text": "selectedValue", "type": "plain_text", }, "action_id": "dropdown", "min_query_length": 5, "type": "external_select", }, ) self.assertDictEqual( ExternalDataSelectElement( placeholder=PlainTextObject(text="selectedValue"), action_id="dropdown", confirm=ConfirmObject(title=PlainTextObject(text="title"), text=PlainTextObject(text="text")), ).to_dict(), { "placeholder": { "emoji": True, "text": "selectedValue", "type": "plain_text", }, "action_id": "dropdown", "confirm": ConfirmObject(title=PlainTextObject(text="title"), text=PlainTextObject(text="text")).to_dict("block"), "type": "external_select", }, )
async def test_with_blocks(self): url = os.environ[SLACK_SDK_TEST_INCOMING_WEBHOOK_URL] webhook = AsyncWebhookClient(url) response = await webhook.send( text="fallback", blocks=[ SectionBlock( block_id="sb-id", text=MarkdownTextObject( text="This is a mrkdwn text section block."), fields=[ PlainTextObject(text="*this is plain_text text*", emoji=True), MarkdownTextObject(text="*this is mrkdwn text*"), PlainTextObject(text="*this is plain_text text*", emoji=True), ]), DividerBlock(), ActionsBlock(elements=[ ButtonElement( text=PlainTextObject(text="Create New Task", emoji=True), style="primary", value="create_task", ), ButtonElement( text=PlainTextObject(text="Create New Project", emoji=True), value="create_project", ), ButtonElement( text=PlainTextObject(text="Help", emoji=True), value="help", ), ], ), ]) self.assertEqual(200, response.status_code) self.assertEqual("ok", response.body)
def test_json_with_accessory(self): button = LinkButtonElement(text=PlainTextObject(text="Click me!"), url="http://google.com") section = SectionBlock(text=MarkdownTextObject(text="some text"), accessory=button).to_dict() coded = { "text": { "text": "some text", "type": "mrkdwn", "verbatim": False }, "accessory": button.to_dict(), "type": "section", } self.assertDictEqual(section, coded)
def on_main_modal_submit(rqst): modal = Modal(rqst) view = modal.view view.callback_id = cmd.prog + ".view2" view.title = PlainTextObject(text='Awaiting Boop') params = session[SESSION_KEY]['params'] delay = params['delay'] view.blocks[0] = SectionBlock(text=PlainTextObject( text=f"Launching async task for {delay} sec update")) view.submit = None rqst.app.log.debug(modal.view.hash) Thread(target=delayed_update_view, kwargs={ 'rqst': rqst, 'view': modal.view, 'delay': delay }).start() return modal.update()
def on_boop_button(rqst): params = session[SESSION_KEY]['params'] params['boops'] += 1 boops = params['boops'] modal = Modal(rqst) view = modal.view view.blocks.pop(0) view.blocks.insert( 0, SectionBlock(text=PlainTextObject(text=f'Boop {boops}'))) res = modal.update() if not res.get('ok'): rqst.app.log.error(f'failed to boop: {res}')