Skip to content
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

Adding View API's and fixing OAuth Headers Bug #517

Merged
merged 4 commits into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 70 additions & 0 deletions docs-src/basic_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,76 @@ See `chat.delete <https://api.slack.com/methods/chat.delete>`_ for more info.

--------

Opening a modal
----------------------------------
Modals allow you to collect data from users and display dynamic information in a focused surface.

Modals use the same blocks that compose messages with the addition of an `input` block.

.. code-block:: python

client.views_open(
trigger_id="3213746830.000023",
view={
type: "modal",
callback_id: "view_identifier",
title: {
type: "plain_text",
text: "Modal title"
},
blocks: [
{
type: "input",
label: {
type: "plain_text",
text: "Input label"
},
element: {
type: "plain_text_input",
action_id: "value_indentifier"
}
}
]
}
)

See `views.open <https://api.slack.com/methods/views.open>`_ more details and additional parameters.

--------

Updating and pushing modals
-------------------
You can dynamically update a view inside of a modal by calling `views.update` and passing the view ID returned in the previous `views.open` call.

.. code-block:: python

client.views_update(
view_id="V123401"
view={
type: "modal",
callback_id: "view_identifier",
title: {
type: "plain_text",
text: "Modal title"
},
blocks: [
{
type: "section",
text: {
type: "plain_text",
text: "An updated modal, indeed"
}
}
]
}
)

See `views.update <https://api.slack.com/methods/views.update>`_ for more info.

If you want to push a new view onto the modal instead of updating an existing view, reference the `views.push <https://api.slack.com/methods/views.push>`_ documentation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be worth mentioning the 3 modal limit here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a subsequent docs revamp that will be shipped as a fast follow. I'll be sure to update it there.


--------

Emoji reactions
---------------------------------------
You can quickly respond to any message on Slack with an emoji reaction. Reactions can be used for any purpose: voting, checking off to-do items, showing excitement -— or just for fun.
Expand Down
29 changes: 21 additions & 8 deletions slack/web/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BaseClient:

def __init__(
self,
token,
token=None,
base_url=BASE_URL,
timeout=30,
loop: Optional[asyncio.AbstractEventLoop] = None,
Expand Down Expand Up @@ -56,10 +56,13 @@ def _get_event_loop(self):
asyncio.set_event_loop(loop)
return loop

def _get_headers(self, has_json, has_files):
def _get_headers(self, has_json, has_files, request_specific_headers):
"""Contructs the headers need for a request.
Args:
has_json (bool): Whether or not the request has json.
has_files (bool): Whether or not the request has files.
request_specific_headers (dict): Additional headers specified by the user for a specific request.

Returns:
The headers dictionary.
e.g. {
Expand All @@ -68,19 +71,28 @@ def _get_headers(self, has_json, has_files):
'User-Agent': 'Python/3.6.8 slack/2.1.0 Darwin/17.7.0'
}
"""
headers = {
final_headers = {
"User-Agent": self._get_user_agent(),
"Authorization": "Bearer {}".format(self.token),
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
}

if self.token:
final_headers.update({"Authorization": "Bearer {}".format(self.token)})

# Merge headers specified at client initialization.
final_headers.update(self.headers)

# Merge headers specified for a specific request. i.e. oauth.access
final_headers.update(request_specific_headers)

if has_json:
headers.update({"Content-Type": "application/json;charset=utf-8"})
final_headers.update({"Content-Type": "application/json;charset=utf-8"})

if has_files:
# These are set automatically by the aiohttp library.
headers.pop("Content-Type", None)
final_headers.pop("Content-Type", None)

return headers
return final_headers

def api_call(
self,
Expand All @@ -91,6 +103,7 @@ def api_call(
data: Union[dict, FormData] = None,
params: dict = None,
json: dict = None,
headers: dict = {},
) -> Union[asyncio.Future, SlackResponse]:
"""Create a request and execute the API call to Slack.

Expand Down Expand Up @@ -131,7 +144,7 @@ def api_call(
api_url = self._get_url(api_method)

req_args = {
"headers": self._get_headers(has_json, has_files),
"headers": self._get_headers(has_json, has_files, headers),
"data": data,
"files": files,
"params": params,
Expand Down
73 changes: 69 additions & 4 deletions slack/web/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1124,10 +1124,8 @@ def oauth_access(
client_secret (str): Issued when you created your application. e.g. '33fea0113f5b1'
code (str): The code param returned via the OAuth callback. e.g. 'ccdaa72ad'
"""
kwargs.update(
{"client_id": client_id, "client_secret": client_secret, "code": code}
)
return self.api_call("oauth.access", data=kwargs)
headers = {"client_id": client_id, "client_secret": client_secret, "code": code}
return self.api_call("oauth.access", data=kwargs, headers=headers)

def pins_add(self, *, channel: str, **kwargs) -> Union[Future, SlackResponse]:
"""Pins an item to a channel.
Expand Down Expand Up @@ -1508,3 +1506,70 @@ def users_profile_set(self, **kwargs) -> Union[Future, SlackResponse]:
"""Set the profile information for a user."""
self._validate_xoxp_token()
return self.api_call("users.profile.set", json=kwargs)

def views_open(
self, *, trigger_id: str, view: dict, **kwargs
) -> Union[Future, SlackResponse]:
"""Open a view for a user.​

Open a modal with a user by exchanging a trigger_id received
from another interaction.
See the modals (https://api.slack.com/block-kit/surfaces/modals)
documentation to learn how to obtain triggers from interactive components.

Args:
trigger_id (str): Exchange a trigger to post to the user.
e.g. '12345.98765.abcd2358fdea'
view (dict): The view payload.
"""
kwargs.update({"trigger_id": trigger_id, "view": view})
return self.api_call("views.open", json=kwargs)

def views_push(
self, *, trigger_id: str, view: dict, **kwargs
) -> Union[Future, SlackResponse]:
"""Push a view onto the stack of a root view.

Push a new view onto the existing view stack by passing a view
payload and a valid trigger_id generated from an interaction
within the existing modal.

Read the modals documentation (https://api.slack.com/block-kit/surfaces/modals)
to learn more about the lifecycle and intricacies of views.

Args:
trigger_id (str): Exchange a trigger to post to the user.
e.g. '12345.98765.abcd2358fdea'
view (dict): The view payload.
"""
kwargs.update({"trigger_id": trigger_id, "view": view})
return self.api_call("views.push", json=kwargs)

def views_update(
self, *, external_id: str = None, view_id: str = None, **kwargs
) -> Union[Future, SlackResponse]:
"""Update an existing view.

Update a view by passing a new view definition along with the
view_id returned in views.open or the external_id.

See the modals documentation (https://api.slack.com/block-kit/surfaces/modals#updating_views)
to learn more about updating views and avoiding race conditions with the hash argument.

Args:
external_id (str): A unique identifier of the view set by the developer.
e.g. 'bmarley_view2'
view_id (str): A unique identifier of the view to be updated.
e.g. 'VMM512F2U'
Raises:
SlackRequestError: Either view_id or external_id is required.
"""
if external_id:
kwargs.update({"external_id": external_id})
elif view_id:
kwargs.update({"view_id": view_id})
else:
raise e.SlackRequestError("Either view_id or external_id is required.")

return self.api_call("views.update", json=kwargs)