Deprecate the dynamic package sensors in seventeentrack (#116102)

* Add deprecation comments for the dynamic package sensors

* Add deprecation comments for the dynamic package sensors

* Add deprecation comments for the dynamic package sensors

add more information when retrieving packages from service call

* Add deprecation comments for the dynamic package sensors

update deprecation comment

* 1. 17Track repair flow
2. update deprecation comment

* 1. remove description_placeholders
2. 2024.8 deprecated

* Update homeassistant/components/seventeentrack/repairs.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* 1. extract deprecated to constant
2. fix types
3. check for issue_id
4. add listener only when not deprecated
5. update which service to call

* Update homeassistant/components/seventeentrack/strings.json

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/seventeentrack/repairs.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* 1. move deprecate_sensor_issue to where needed
2. add entry_id to issue_id
3. use constant where needed

* update breaks in ha version

* Update homeassistant/components/seventeentrack/strings.json

* Remove obsolete tests

* Fix

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Shai Ungar 2024-07-07 17:55:38 +03:00 committed by GitHub
parent a66631f1ee
commit b6609fa77c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 221 additions and 7 deletions

View file

@ -29,10 +29,14 @@ from homeassistant.util import slugify
from .const import (
ATTR_CONFIG_ENTRY_ID,
ATTR_DESTINATION_COUNTRY,
ATTR_INFO_TEXT,
ATTR_ORIGIN_COUNTRY,
ATTR_PACKAGE_STATE,
ATTR_PACKAGE_TYPE,
ATTR_STATUS,
ATTR_TIMESTAMP,
ATTR_TRACKING_INFO_LANGUAGE,
ATTR_TRACKING_NUMBER,
DOMAIN,
SERVICE_GET_PACKAGES,
@ -104,6 +108,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return {
"packages": [
{
ATTR_DESTINATION_COUNTRY: package.destination_country,
ATTR_ORIGIN_COUNTRY: package.origin_country,
ATTR_PACKAGE_TYPE: package.package_type,
ATTR_TRACKING_INFO_LANGUAGE: package.tracking_info_language,
ATTR_TRACKING_NUMBER: package.tracking_number,
ATTR_LOCATION: package.location,
ATTR_STATUS: package.status,

View file

@ -45,3 +45,5 @@ SERVICE_GET_PACKAGES = "get_packages"
ATTR_PACKAGE_STATE = "package_state"
ATTR_CONFIG_ENTRY_ID = "config_entry_id"
DEPRECATED_KEY = "deprecated"

View file

@ -0,0 +1,49 @@
"""Repairs for the SeventeenTrack integration."""
import voluptuous as vol
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from .const import DEPRECATED_KEY
class SensorDeprecationRepairFlow(RepairsFlow):
"""Handler for an issue fixing flow."""
def __init__(self, entry: ConfigEntry) -> None:
"""Create flow."""
self.entry = entry
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the first step of a fix flow."""
return await self.async_step_confirm()
async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is not None:
data = {**self.entry.data, DEPRECATED_KEY: True}
self.hass.config_entries.async_update_entry(self.entry, data=data)
return self.async_create_entry(data={})
return self.async_show_form(
step_id="confirm",
data_schema=vol.Schema({}),
)
async def async_create_fix_flow(
hass: HomeAssistant, issue_id: str, data: dict
) -> RepairsFlow:
"""Create flow."""
if issue_id.startswith("deprecate_sensor_"):
entry = hass.config_entries.async_get_entry(data["entry_id"])
assert entry
return SensorDeprecationRepairFlow(entry)
return ConfirmRepairFlow()

View file

@ -20,7 +20,11 @@ from homeassistant.const import (
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers import (
config_validation as cv,
entity_registry as er,
issue_registry as ir,
)
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
@ -41,6 +45,7 @@ from .const import (
ATTRIBUTION,
CONF_SHOW_ARCHIVED,
CONF_SHOW_DELIVERED,
DEPRECATED_KEY,
DOMAIN,
LOGGER,
NOTIFICATION_DELIVERED_MESSAGE,
@ -113,8 +118,12 @@ async def async_setup_entry(
coordinator: SeventeenTrackCoordinator = hass.data[DOMAIN][config_entry.entry_id]
previous_tracking_numbers: set[str] = set()
# This has been deprecated in 2024.8, will be removed in 2025.2
@callback
def _async_create_remove_entities():
if config_entry.data.get(DEPRECATED_KEY):
remove_packages(hass, coordinator.account_id, previous_tracking_numbers)
return
live_tracking_numbers = set(coordinator.data.live_packages.keys())
new_tracking_numbers = live_tracking_numbers - previous_tracking_numbers
@ -157,11 +166,12 @@ async def async_setup_entry(
for status, summary_data in coordinator.data.summary.items()
)
_async_create_remove_entities()
config_entry.async_on_unload(
coordinator.async_add_listener(_async_create_remove_entities)
)
if not config_entry.data.get(DEPRECATED_KEY):
deprecate_sensor_issue(hass, config_entry.entry_id)
_async_create_remove_entities()
config_entry.async_on_unload(
coordinator.async_add_listener(_async_create_remove_entities)
)
class SeventeenTrackSensor(CoordinatorEntity[SeventeenTrackCoordinator], SensorEntity):
@ -206,6 +216,7 @@ class SeventeenTrackSummarySensor(SeventeenTrackSensor):
"""Return the state of the sensor."""
return self.coordinator.data.summary[self._status]["quantity"]
# This has been deprecated in 2024.8, will be removed in 2025.2
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes."""
@ -225,6 +236,7 @@ class SeventeenTrackSummarySensor(SeventeenTrackSensor):
}
# The dynamic package sensors have been replaced by the seventeentrack.get_packages service
class SeventeenTrackPackageSensor(SeventeenTrackSensor):
"""Define an individual package sensor."""
@ -298,3 +310,20 @@ def notify_delivered(hass: HomeAssistant, friendly_name: str, tracking_number: s
persistent_notification.create(
hass, message, title=title, notification_id=notification_id
)
@callback
def deprecate_sensor_issue(hass: HomeAssistant, entry_id: str) -> None:
"""Ensure an issue is registered."""
ir.async_create_issue(
hass,
DOMAIN,
f"deprecate_sensor_{entry_id}",
breaks_in_ha_version="2025.2.0",
issue_domain=DOMAIN,
is_fixable=True,
is_persistent=True,
translation_key="deprecate_sensor",
severity=ir.IssueSeverity.WARNING,
data={"entry_id": entry_id},
)

View file

@ -45,6 +45,17 @@
"deprecated_yaml_import_issue_invalid_auth": {
"title": "The 17Track YAML configuration import request failed due to invalid authentication",
"description": "Configuring 17Track using YAML is being removed but there were invalid credentials provided while importing your existing configuration.\nSetup will not proceed.\n\nVerify that your 17Track credentials are correct and restart Home Assistant to attempt the import again.\n\nAlternatively, you may remove the 17Track configuration from your YAML configuration entirely, restart Home Assistant, and add the 17Track integration manually."
},
"deprecate_sensor": {
"title": "17Track package sensors are being deprecated",
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::seventeentrack::issues::deprecate_sensor::title%]",
"description": "17Track package sensors are deprecated and will be removed.\nPlease update your automations and scripts to get data using the `seventeentrack.get_packages` service call."
}
}
}
}
},
"entity": {

View file

@ -3,27 +3,39 @@
dict({
'packages': list([
dict({
'destination_country': 'Belgium',
'friendly_name': 'friendly name 3',
'info_text': 'info text 1',
'location': 'location 1',
'origin_country': 'Belgium',
'package_type': 'Registered Parcel',
'status': 'Expired',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_info_language': 'Unknown',
'tracking_number': '123',
}),
dict({
'destination_country': 'Belgium',
'friendly_name': 'friendly name 1',
'info_text': 'info text 1',
'location': 'location 1',
'origin_country': 'Belgium',
'package_type': 'Registered Parcel',
'status': 'In Transit',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_info_language': 'Unknown',
'tracking_number': '456',
}),
dict({
'destination_country': 'Belgium',
'friendly_name': 'friendly name 2',
'info_text': 'info text 1',
'location': 'location 1',
'origin_country': 'Belgium',
'package_type': 'Registered Parcel',
'status': 'Delivered',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_info_language': 'Unknown',
'tracking_number': '789',
}),
]),
@ -33,19 +45,27 @@
dict({
'packages': list([
dict({
'destination_country': 'Belgium',
'friendly_name': 'friendly name 1',
'info_text': 'info text 1',
'location': 'location 1',
'origin_country': 'Belgium',
'package_type': 'Registered Parcel',
'status': 'In Transit',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_info_language': 'Unknown',
'tracking_number': '456',
}),
dict({
'destination_country': 'Belgium',
'friendly_name': 'friendly name 2',
'info_text': 'info text 1',
'location': 'location 1',
'origin_country': 'Belgium',
'package_type': 'Registered Parcel',
'status': 'Delivered',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_info_language': 'Unknown',
'tracking_number': '789',
}),
]),

View file

@ -0,0 +1,95 @@
"""Tests for the seventeentrack repair flow."""
from http import HTTPStatus
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
from homeassistant.components.repairs.websocket_api import RepairsFlowIndexView
from homeassistant.components.seventeentrack import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.setup import async_setup_component
from . import goto_future, init_integration
from .conftest import DEFAULT_SUMMARY_LENGTH, get_package
from tests.common import MockConfigEntry
from tests.typing import ClientSessionGenerator
async def test_repair(
hass: HomeAssistant,
mock_seventeentrack: AsyncMock,
issue_registry: ir.IssueRegistry,
hass_client: ClientSessionGenerator,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Ensure everything starts correctly."""
await init_integration(hass, mock_config_entry) # 2
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH
assert len(issue_registry.issues) == 1
package = get_package()
mock_seventeentrack.return_value.profile.packages.return_value = [package]
await goto_future(hass, freezer)
assert hass.states.get("sensor.17track_package_friendly_name_1")
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
assert "deprecated" not in mock_config_entry.data
repair_issue = issue_registry.async_get_issue(
domain=DOMAIN, issue_id=f"deprecate_sensor_{mock_config_entry.entry_id}"
)
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
client = await hass_client()
resp = await client.post(
RepairsFlowIndexView.url,
json={"handler": DOMAIN, "issue_id": repair_issue.issue_id},
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
flow_id = data["flow_id"]
assert data == {
"type": "form",
"flow_id": flow_id,
"handler": DOMAIN,
"step_id": "confirm",
"data_schema": [],
"errors": None,
"description_placeholders": None,
"last_step": None,
"preview": None,
}
resp = await client.post(RepairsFlowIndexView.url + f"/{flow_id}")
assert resp.status == HTTPStatus.OK
data = await resp.json()
flow_id = data["flow_id"]
assert data == {
"type": "create_entry",
"handler": DOMAIN,
"flow_id": flow_id,
"description": None,
"description_placeholders": None,
}
assert mock_config_entry.data["deprecated"]
repair_issue = issue_registry.async_get_issue(
domain=DOMAIN, issue_id="deprecate_sensor"
)
assert repair_issue is None
await goto_future(hass, freezer)
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH

View file

@ -317,4 +317,4 @@ async def test_full_valid_platform_config(
assert await async_setup_component(hass, "sensor", VALID_PLATFORM_CONFIG_FULL)
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids()) == len(DEFAULT_SUMMARY.keys())
assert len(issue_registry.issues) == 1
assert len(issue_registry.issues) == 2