Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion custom_components/integration_linear/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -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)",
Expand Down
33 changes: 32 additions & 1 deletion custom_components/integration_linear/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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!",
Expand All @@ -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")
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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)

Expand Down
5 changes: 4 additions & 1 deletion custom_components/integration_linear/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down Expand Up @@ -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",
}
Expand Down Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions custom_components/integration_linear/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading