Skip to content

Commit

Permalink
Add Options flow to YouTube (#93667)
Browse files Browse the repository at this point in the history
* Add Options flow to YouTube

* Add strings for options flow

* Add strings for options flow

* Add strings for options flow
  • Loading branch information
joostlek committed May 29, 2023
1 parent 795ef07 commit 20d1a0f
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 12 deletions.
80 changes: 69 additions & 11 deletions homeassistant/components/youtube/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from googleapiclient.http import HttpRequest
import voluptuous as vol

from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigEntry, OptionsFlowWithConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.selector import (
Expand All @@ -24,6 +25,19 @@
from .const import CONF_CHANNELS, DEFAULT_ACCESS, DOMAIN, LOGGER


async def get_resource(hass: HomeAssistant, token: str) -> Resource:
"""Get Youtube resource async."""

def _build_resource() -> Resource:
return build(
"youtube",
"v3",
credentials=Credentials(token),
)

return await hass.async_add_executor_job(_build_resource)


class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
Expand All @@ -36,6 +50,14 @@ class OAuth2FlowHandler(

reauth_entry: ConfigEntry | None = None

@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> YouTubeOptionsFlowHandler:
"""Get the options flow for this handler."""
return YouTubeOptionsFlowHandler(config_entry)

@property
def logger(self) -> logging.Logger:
"""Return logger."""
Expand Down Expand Up @@ -69,7 +91,7 @@ async def async_step_reauth_confirm(
async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult:
"""Create an entry for the flow, or update existing entry."""
try:
service = await self._get_resource(data[CONF_TOKEN][CONF_ACCESS_TOKEN])
service = await get_resource(self.hass, data[CONF_TOKEN][CONF_ACCESS_TOKEN])
# pylint: disable=no-member
own_channel_request: HttpRequest = service.channels().list(
part="snippet", mine=True
Expand Down Expand Up @@ -116,7 +138,9 @@ async def async_step_channels(
data=self._data,
options=user_input,
)
service = await self._get_resource(self._data[CONF_TOKEN][CONF_ACCESS_TOKEN])
service = await get_resource(
self.hass, self._data[CONF_TOKEN][CONF_ACCESS_TOKEN]
)
# pylint: disable=no-member
subscription_request: HttpRequest = service.subscriptions().list(
part="snippet", mine=True, maxResults=50
Expand All @@ -140,12 +164,46 @@ async def async_step_channels(
),
)

async def _get_resource(self, token: str) -> Resource:
def _build_resource() -> Resource:
return build(
"youtube",
"v3",
credentials=Credentials(token),
)

return await self.hass.async_add_executor_job(_build_resource)
class YouTubeOptionsFlowHandler(OptionsFlowWithConfigEntry):
"""YouTube Options flow handler."""

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Initialize form."""
if user_input is not None:
return self.async_create_entry(
title=self.config_entry.title,
data=user_input,
)
service = await get_resource(
self.hass, self.config_entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN]
)
# pylint: disable=no-member
subscription_request: HttpRequest = service.subscriptions().list(
part="snippet", mine=True, maxResults=50
)
response = await self.hass.async_add_executor_job(subscription_request.execute)
selectable_channels = [
SelectOptionDict(
value=subscription["snippet"]["resourceId"]["channelId"],
label=subscription["snippet"]["title"],
)
for subscription in response["items"]
]
return self.async_show_form(
step_id="init",
data_schema=self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(CONF_CHANNELS): SelectSelector(
SelectSelectorConfig(
options=selectable_channels, multiple=True
)
),
}
),
self.options,
),
)
10 changes: 10 additions & 0 deletions homeassistant/components/youtube/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
}
}
},
"options": {
"step": {
"init": {
"description": "Select the channels you want to add.",
"data": {
"channels": "YouTube channels"
}
}
}
},
"entity": {
"sensor": {
"latest_upload": {
Expand Down
34 changes: 33 additions & 1 deletion tests/components/youtube/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@
from homeassistant.helpers import config_entry_oauth2_flow

from . import MockService
from .conftest import CLIENT_ID, GOOGLE_AUTH_URI, GOOGLE_TOKEN_URI, SCOPES, TITLE
from .conftest import (
CLIENT_ID,
GOOGLE_AUTH_URI,
GOOGLE_TOKEN_URI,
SCOPES,
TITLE,
ComponentSetup,
)

from tests.common import MockConfigEntry, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker
Expand Down Expand Up @@ -267,3 +274,28 @@ async def test_flow_exception(
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "unknown"


async def test_options_flow(
hass: HomeAssistant, setup_integration: ComponentSetup
) -> None:
"""Test the full options flow."""
await setup_integration()
with patch(
"homeassistant.components.youtube.config_flow.build", return_value=MockService()
):
entry = hass.config_entries.async_entries(DOMAIN)[0]
result = await hass.config_entries.options.async_init(entry.entry_id)
await hass.async_block_till_done()

assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "init"

result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={CONF_CHANNELS: ["UC_x5XG1OV2P6uZZ5FSM9Ttw"]},
)
await hass.async_block_till_done()

assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {CONF_CHANNELS: ["UC_x5XG1OV2P6uZZ5FSM9Ttw"]}

0 comments on commit 20d1a0f

Please sign in to comment.