Skip to content

Commit 20d1a0f

Browse files
authored
Add Options flow to YouTube (home-assistant#93667)
* Add Options flow to YouTube * Add strings for options flow * Add strings for options flow * Add strings for options flow
1 parent 795ef07 commit 20d1a0f

File tree

3 files changed

+112
-12
lines changed

3 files changed

+112
-12
lines changed

homeassistant/components/youtube/config_flow.py

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
from googleapiclient.http import HttpRequest
1212
import voluptuous as vol
1313

14-
from homeassistant.config_entries import ConfigEntry
14+
from homeassistant.config_entries import ConfigEntry, OptionsFlowWithConfigEntry
1515
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
16+
from homeassistant.core import HomeAssistant, callback
1617
from homeassistant.data_entry_flow import FlowResult
1718
from homeassistant.helpers import config_entry_oauth2_flow
1819
from homeassistant.helpers.selector import (
@@ -24,6 +25,19 @@
2425
from .const import CONF_CHANNELS, DEFAULT_ACCESS, DOMAIN, LOGGER
2526

2627

28+
async def get_resource(hass: HomeAssistant, token: str) -> Resource:
29+
"""Get Youtube resource async."""
30+
31+
def _build_resource() -> Resource:
32+
return build(
33+
"youtube",
34+
"v3",
35+
credentials=Credentials(token),
36+
)
37+
38+
return await hass.async_add_executor_job(_build_resource)
39+
40+
2741
class OAuth2FlowHandler(
2842
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
2943
):
@@ -36,6 +50,14 @@ class OAuth2FlowHandler(
3650

3751
reauth_entry: ConfigEntry | None = None
3852

53+
@staticmethod
54+
@callback
55+
def async_get_options_flow(
56+
config_entry: ConfigEntry,
57+
) -> YouTubeOptionsFlowHandler:
58+
"""Get the options flow for this handler."""
59+
return YouTubeOptionsFlowHandler(config_entry)
60+
3961
@property
4062
def logger(self) -> logging.Logger:
4163
"""Return logger."""
@@ -69,7 +91,7 @@ async def async_step_reauth_confirm(
6991
async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult:
7092
"""Create an entry for the flow, or update existing entry."""
7193
try:
72-
service = await self._get_resource(data[CONF_TOKEN][CONF_ACCESS_TOKEN])
94+
service = await get_resource(self.hass, data[CONF_TOKEN][CONF_ACCESS_TOKEN])
7395
# pylint: disable=no-member
7496
own_channel_request: HttpRequest = service.channels().list(
7597
part="snippet", mine=True
@@ -116,7 +138,9 @@ async def async_step_channels(
116138
data=self._data,
117139
options=user_input,
118140
)
119-
service = await self._get_resource(self._data[CONF_TOKEN][CONF_ACCESS_TOKEN])
141+
service = await get_resource(
142+
self.hass, self._data[CONF_TOKEN][CONF_ACCESS_TOKEN]
143+
)
120144
# pylint: disable=no-member
121145
subscription_request: HttpRequest = service.subscriptions().list(
122146
part="snippet", mine=True, maxResults=50
@@ -140,12 +164,46 @@ async def async_step_channels(
140164
),
141165
)
142166

143-
async def _get_resource(self, token: str) -> Resource:
144-
def _build_resource() -> Resource:
145-
return build(
146-
"youtube",
147-
"v3",
148-
credentials=Credentials(token),
149-
)
150167

151-
return await self.hass.async_add_executor_job(_build_resource)
168+
class YouTubeOptionsFlowHandler(OptionsFlowWithConfigEntry):
169+
"""YouTube Options flow handler."""
170+
171+
async def async_step_init(
172+
self, user_input: dict[str, Any] | None = None
173+
) -> FlowResult:
174+
"""Initialize form."""
175+
if user_input is not None:
176+
return self.async_create_entry(
177+
title=self.config_entry.title,
178+
data=user_input,
179+
)
180+
service = await get_resource(
181+
self.hass, self.config_entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN]
182+
)
183+
# pylint: disable=no-member
184+
subscription_request: HttpRequest = service.subscriptions().list(
185+
part="snippet", mine=True, maxResults=50
186+
)
187+
response = await self.hass.async_add_executor_job(subscription_request.execute)
188+
selectable_channels = [
189+
SelectOptionDict(
190+
value=subscription["snippet"]["resourceId"]["channelId"],
191+
label=subscription["snippet"]["title"],
192+
)
193+
for subscription in response["items"]
194+
]
195+
return self.async_show_form(
196+
step_id="init",
197+
data_schema=self.add_suggested_values_to_schema(
198+
vol.Schema(
199+
{
200+
vol.Required(CONF_CHANNELS): SelectSelector(
201+
SelectSelectorConfig(
202+
options=selectable_channels, multiple=True
203+
)
204+
),
205+
}
206+
),
207+
self.options,
208+
),
209+
)

homeassistant/components/youtube/strings.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@
1919
}
2020
}
2121
},
22+
"options": {
23+
"step": {
24+
"init": {
25+
"description": "Select the channels you want to add.",
26+
"data": {
27+
"channels": "YouTube channels"
28+
}
29+
}
30+
}
31+
},
2232
"entity": {
2333
"sensor": {
2434
"latest_upload": {

tests/components/youtube/test_config_flow.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@
1212
from homeassistant.helpers import config_entry_oauth2_flow
1313

1414
from . import MockService
15-
from .conftest import CLIENT_ID, GOOGLE_AUTH_URI, GOOGLE_TOKEN_URI, SCOPES, TITLE
15+
from .conftest import (
16+
CLIENT_ID,
17+
GOOGLE_AUTH_URI,
18+
GOOGLE_TOKEN_URI,
19+
SCOPES,
20+
TITLE,
21+
ComponentSetup,
22+
)
1623

1724
from tests.common import MockConfigEntry, load_fixture
1825
from tests.test_util.aiohttp import AiohttpClientMocker
@@ -267,3 +274,28 @@ async def test_flow_exception(
267274
result = await hass.config_entries.flow.async_configure(result["flow_id"])
268275
assert result["type"] == FlowResultType.ABORT
269276
assert result["reason"] == "unknown"
277+
278+
279+
async def test_options_flow(
280+
hass: HomeAssistant, setup_integration: ComponentSetup
281+
) -> None:
282+
"""Test the full options flow."""
283+
await setup_integration()
284+
with patch(
285+
"homeassistant.components.youtube.config_flow.build", return_value=MockService()
286+
):
287+
entry = hass.config_entries.async_entries(DOMAIN)[0]
288+
result = await hass.config_entries.options.async_init(entry.entry_id)
289+
await hass.async_block_till_done()
290+
291+
assert result["type"] == FlowResultType.FORM
292+
assert result["step_id"] == "init"
293+
294+
result = await hass.config_entries.options.async_configure(
295+
result["flow_id"],
296+
user_input={CONF_CHANNELS: ["UC_x5XG1OV2P6uZZ5FSM9Ttw"]},
297+
)
298+
await hass.async_block_till_done()
299+
300+
assert result["type"] == FlowResultType.CREATE_ENTRY
301+
assert result["data"] == {CONF_CHANNELS: ["UC_x5XG1OV2P6uZZ5FSM9Ttw"]}

0 commit comments

Comments
 (0)