diff --git a/custom_components/integration_linear/__init__.py b/custom_components/integration_linear/__init__.py index 9671249..36542f1 100644 --- a/custom_components/integration_linear/__init__.py +++ b/custom_components/integration_linear/__init__.py @@ -86,7 +86,7 @@ async def _async_handle_create_issue( raise ValueError(msg) client = config_entry.runtime_data.client - LOGGER.info("Fetching Team ID") + LOGGER.debug("Fetching Team ID") # If team_identifier is provided, look up the team if team_identifier: team = await client.async_get_team_by_identifier(team_identifier) @@ -95,6 +95,13 @@ async def _async_handle_create_issue( raise HomeAssistantError(msg) team_id = team["id"] + # Get the current user from the service call context + created_by_user = None + if call.context.user_id: + user = await hass.auth.async_get_user(call.context.user_id) + if user: + created_by_user = user.name + try: issue = await client.async_create_issue_advanced( title=title, @@ -104,6 +111,7 @@ async def _async_handle_create_issue( state_name_or_id=state_name_or_id, description=description, due_date=due_date, + created_by_user=created_by_user, ) LOGGER.info( "Created Linear issue: %s (ID: %s, URL: %s)", diff --git a/custom_components/integration_linear/api.py b/custom_components/integration_linear/api.py index 53514b0..c540f88 100644 --- a/custom_components/integration_linear/api.py +++ b/custom_components/integration_linear/api.py @@ -335,8 +335,21 @@ async def async_create_issue( state_id: str, description: str | None = None, due_date: str | None = None, + created_by_user: str | None = None, + created_by_user_avatar_url: str | None = None, ) -> dict[str, Any]: - """Create a new issue.""" + """ + Create a new issue. + + Args: + title: The issue title + team_id: The team ID + state_id: The workflow state ID + description: Optional description + due_date: Optional due date (ISO 8601 format) + created_by_user: Name/identifier of the user creating the issue + created_by_user_avatar_url: URL of the user's avatar + """ # Build variable declarations and input fields dynamically variable_declarations: list[str] = [ "$title: String!", @@ -354,6 +367,17 @@ async def async_create_issue( "stateId": state_id, } + # Add createAsUser if provided + if created_by_user: + variable_declarations.append("$createAsUser: String") + input_fields.append("createAsUser: $createAsUser") + variables["createAsUser"] = created_by_user + + if created_by_user_avatar_url: + variable_declarations.append("$displayIconUrl: String") + input_fields.append("displayIconUrl: $displayIconUrl") + variables["displayIconUrl"] = created_by_user_avatar_url + if description: variable_declarations.append("$description: String") input_fields.append("description: $description") @@ -410,6 +434,7 @@ async def async_create_issue_advanced( state_name_or_id: str | None = None, description: str | None = None, due_date: str | None = None, + created_by_user: str | None = None, ) -> dict[str, Any]: """ Create a new issue with advanced features. @@ -422,6 +447,7 @@ async def async_create_issue_advanced( state_name_or_id: State name or ID to set description: Optional description due_date: Optional due date (ISO 8601 format) + created_by_user: Name/identifier of the user creating the issue Raises: IntegrationBlueprintApiClientError: If user doesn't exist, @@ -495,6 +521,11 @@ async def async_create_issue_advanced( input_fields.append("dueDate: $dueDate") variables["dueDate"] = due_date + if created_by_user: + variable_declarations.append("$createAsUser: String") + input_fields.append("createAsUser: $createAsUser") + variables["createAsUser"] = created_by_user + variable_decls_str = ",\n ".join(variable_declarations) input_fields_str = ",\n ".join(input_fields) diff --git a/custom_components/integration_linear/config_flow.py b/custom_components/integration_linear/config_flow.py index a7f8a96..7115d8f 100644 --- a/custom_components/integration_linear/config_flow.py +++ b/custom_components/integration_linear/config_flow.py @@ -30,6 +30,7 @@ LINEAR_TOKEN_URL = "https://api.linear.app/oauth/token" # noqa: S105 OAUTH_SCOPES = ["write"] + class BlueprintFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): """Config flow for Linear Integration.""" @@ -65,6 +66,7 @@ def async_get_options_flow( def extra_authorize_data(self) -> dict[str, Any]: """Extra data that needs to be appended to the authorize url.""" return { + "actor": "app", "scope": " ".join(OAUTH_SCOPES), "prompt": "consent", } @@ -579,7 +581,8 @@ async def async_step_options_team_states( return await self._build_options_team_states_form({}) # All teams configured, update config entry - # Start with a copy of the existing entry data to preserve all other fields (including refreshed OAuth tokens) + # Start with a copy of the existing entry data to preserve all other fields, + # including refreshed OAuth tokens entry_data = dict(entry.data) # Update only the specific fields needed entry_data[CONF_TEAMS] = self._selected_teams diff --git a/custom_components/integration_linear/todo.py b/custom_components/integration_linear/todo.py index 7aa35b0..0cebe72 100644 --- a/custom_components/integration_linear/todo.py +++ b/custom_components/integration_linear/todo.py @@ -176,12 +176,44 @@ async def async_create_todo_item(self, item: TodoItem) -> None: # Use the first todo_state for new issues state_id = todo_states[0] + # Get the current user from Home Assistant + created_by_user = None + if self.hass.auth and hasattr(self, "_context") and self._context: + user_id = self._context.user_id + if user_id: + user = await self.hass.auth.async_get_user(user_id) + if user: + created_by_user = user + + created_by_person = None + if created_by_user: + all_persons = self.hass.states.async_all("person") + + for person in all_persons: + if person.attributes.get("user_id") == created_by_user.id: + created_by_person = person + break + LOGGER.debug("Created by person: %s", created_by_person) + + # Get avatar URL with public base URL if available + avatar_url = None + if ( + created_by_person + and (entity_picture := created_by_person.attributes.get("entity_picture")) + and self.hass.config.external_url + ): + avatar_url = f"{self.hass.config.external_url}{entity_picture}" + + LOGGER.debug("Avatar URL: %s", avatar_url) + await client.async_create_issue( title=item.summary or "", team_id=self._team_id, state_id=state_id, description=item.description, due_date=self._format_due_date(item.due), + created_by_user=created_by_user.name if created_by_user else None, + created_by_user_avatar_url=avatar_url, ) # Refresh coordinator to sync UI