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

Migrate dialogflow over to the new webhook component #17804

Merged
merged 5 commits into from Oct 28, 2018
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
18 changes: 18 additions & 0 deletions homeassistant/components/dialogflow/.translations/en.json
@@ -0,0 +1,18 @@
{
"config": {
"title": "Dialogflow",
"step": {
"user": {
"title": "Set up the Dialogflow Webhook",
"description": "Are you sure you want to set up Dialogflow?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details."
}
}
}
Expand Up @@ -8,23 +8,16 @@

import voluptuous as vol

from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent, template
from homeassistant.helpers import intent, template, config_entry_flow
from homeassistant.components.http import HomeAssistantView

_LOGGER = logging.getLogger(__name__)

CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech'
CONF_ACTION = 'action'
CONF_ASYNC_ACTION = 'async_action'

DEFAULT_CONF_ASYNC_ACTION = False
DEPENDENCIES = ['http']
DEPENDENCIES = ['webhook', 'http']
DOMAIN = 'dialogflow'

INTENTS_API_ENDPOINT = '/api/dialogflow'

SOURCE = "Home Assistant Dialogflow"

CONFIG_SCHEMA = vol.Schema({
Expand All @@ -38,52 +31,72 @@ class DialogFlowError(HomeAssistantError):

async def async_setup(hass, config):
"""Set up Dialogflow component."""
hass.http.register_view(DialogflowIntentsView)

return True


class DialogflowIntentsView(HomeAssistantView):
"""Handle Dialogflow requests."""

url = INTENTS_API_ENDPOINT
name = 'api:dialogflow'

async def post(self, request):
"""Handle Dialogflow."""
hass = request.app['hass']
message = await request.json()

_LOGGER.debug("Received Dialogflow request: %s", message)

try:
response = await async_handle_message(hass, message)
return b'' if response is None else self.json(response)

except DialogFlowError as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message, str(err)))
async def handle_webhook(hass, webhook_id, request):
"""Handle incoming webhook with Dialogflow requests."""
message = await request.json()

_LOGGER.debug("Received Dialogflow request: %s", message)

try:
response = await async_handle_message(hass, message)
return b'' if response is None else HomeAssistantView.json(response)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think that it's a webhook when the caller expects a response.

Copy link
Member

Choose a reason for hiding this comment

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

Forget about the comment above.

However, we should not use HomeAssistantView but instead use aiohttp directly.

from aiohttp import web

return web.json_response(…)

Docs


except DialogFlowError as err:
_LOGGER.warning(str(err))
return HomeAssistantView.json(
dialogflow_error_response(message, str(err))
)

except intent.UnknownIntent as err:
_LOGGER.warning(str(err))
return HomeAssistantView.json(
dialogflow_error_response(
message,
"This intent is not yet configured within Home Assistant."
)
)

except intent.InvalidSlotInfo as err:
_LOGGER.warning(str(err))
return HomeAssistantView.json(
dialogflow_error_response(
message,
"Invalid slot information received for this intent."
)
)

except intent.IntentError as err:
_LOGGER.warning(str(err))
return HomeAssistantView.json(
dialogflow_error_response(message, "Error handling intent."))


async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
hass.components.webhook.async_register(
entry.data[CONF_WEBHOOK_ID], handle_webhook)
return True

except intent.UnknownIntent as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message,
"This intent is not yet configured within Home Assistant."))

except intent.InvalidSlotInfo as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message,
"Invalid slot information received for this intent."))
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
return True

except intent.IntentError as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message, "Error handling intent."))
config_entry_flow.register_webhook_flow(
DOMAIN,
'Dialogflow Webhook',
{
'dialogflow_url': 'https://dialogflow.com/docs/fulfillment#webhook',
'docs_url': 'https://www.home-assistant.io/components/dialogflow/'
}
)


def dialogflow_error_response(hass, message, error):
def dialogflow_error_response(message, error):
"""Return a response saying the error message."""
dialogflow_response = DialogflowResponse(message['result']['parameters'])
dialogflow_response.add_speech(error)
Expand Down
18 changes: 18 additions & 0 deletions homeassistant/components/dialogflow/strings.json
@@ -0,0 +1,18 @@
{
"config": {
"title": "Dialogflow",
"step": {
"user": {
"title": "Set up the Dialogflow Webhook",
"description": "Are you sure you want to set up Dialogflow?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details."
}
}
}
8 changes: 5 additions & 3 deletions homeassistant/components/http/view.py
Expand Up @@ -40,7 +40,8 @@ def context(self, request):

return Context(user_id=user.id)

def json(self, result, status_code=200, headers=None):
@staticmethod
def json(result, status_code=200, headers=None):
Copy link
Member Author

Choose a reason for hiding this comment

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

I had to expose this and the json_message as static methods so that they could be accessed by things that don't implement HomeAssistantView. Not sure if that is acceptable or if it would be preferred to move them out of the class (within the same module)?

Copy link
Member

Choose a reason for hiding this comment

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

We should not do this. This is conflation of concerns.

Webhooks should work without any http component related things.

"""Return a JSON response."""
try:
msg = json.dumps(
Expand All @@ -54,13 +55,14 @@ def json(self, result, status_code=200, headers=None):
response.enable_compression()
return response

def json_message(self, message, status_code=200, message_code=None,
@staticmethod
def json_message(message, status_code=200, message_code=None,
headers=None):
"""Return a JSON message response."""
data = {'message': message}
if message_code is not None:
data['code'] = message_code
return self.json(data, status_code, headers=headers)
return HomeAssistantView.json(data, status_code, headers=headers)

def register(self, app, router):
"""Register the view with a router."""
Expand Down
1 change: 1 addition & 0 deletions homeassistant/config_entries.py
Expand Up @@ -137,6 +137,7 @@ async def async_step_discovery(info):
FLOWS = [
'cast',
'deconz',
'dialogflow',
'hangouts',
'homematicip_cloud',
'hue',
Expand Down
1 change: 1 addition & 0 deletions tests/components/dialogflow/__init__.py
@@ -0,0 +1 @@
"""Tests for the Dialogflow component."""