diff --git a/independent-publisher-connectors/Linear/Readme.md b/independent-publisher-connectors/Linear/Readme.md new file mode 100644 index 0000000000..1d14e62144 --- /dev/null +++ b/independent-publisher-connectors/Linear/Readme.md @@ -0,0 +1,81 @@ +# Linear + +Linear is a modern issue tracking and project management tool designed for software teams. This connector enables Power Automate users to create, update, query, and search issues in their Linear workspace, enabling seamless integration between Linear and hundreds of other services. + +## Publisher + +### Aaron Mah + +## Prerequisites + +To use this connector, you need a Linear account with API access: + +1. Log into your Linear workspace at [https://linear.app](https://linear.app). +2. Navigate to **Settings → Account → Security**. +3. Click **Create new API key**. +4. Give it a label (e.g., "Power Automate"). +5. Copy the API key (it is shown only once — starts with `lin_api_`). +6. Paste the key into the Power Automate connection dialog when prompted. + +> **Note:** The API key provides full read/write access to your workspace. Keep it secure and do not share it. Linear API keys are free on all plans. + +## Obtaining Credentials + +1. Log into your Linear workspace at [https://linear.app](https://linear.app). +2. Navigate to **Settings → Account → Security**. +3. Click **Create new API key**. +4. Give it a label (e.g., "Power Automate"). +5. Copy the API key (it is shown only once — starts with `lin_api_`). +6. Paste the key into the Power Automate connection dialog when prompted. + +API keys are free on all Linear plans. + +## Supported Operations + +### List Teams +Retrieves all teams in the Linear workspace. Use this to get team IDs for creating issues and to power team dropdowns in your flows. + +### List Workflow States +Retrieves all workflow states (statuses) in the workspace, such as Backlog, In Progress, and Done. Use this to get state IDs for creating or updating issues. + +### List Users +Retrieves all users in the Linear workspace. Use this to get user IDs for assigning issues. + +### List Labels +Retrieves all issue labels in the workspace. Use this to get label IDs for tagging issues during creation or update. + +### Create Issue +Creates a new issue in a Linear team. Supports setting title, description, assignee, priority, due date, estimate, workflow state, and labels. + +### Get Issue +Retrieves a single issue by its UUID or human-readable identifier (e.g., "ENG-42"). Returns full issue details including state, assignee, labels, and timestamps. + +### List Issues +Lists issues sorted by last updated time with pagination support. Returns issue summaries including state, assignee, team, and priority. + +### Update Issue +Updates properties of an existing issue. Supports changing title, description, state, assignee, priority, due date, estimate, and labels. + +### Add Comment +Creates a comment on an existing issue. Supports markdown formatting. Useful for posting automated updates like "PR merged" or "Build failed". + +### Search Issues +Searches issues by text across titles and descriptions. Supports pagination. Use this to find existing issues before creating duplicates. + +## API Documentation + +- [Linear Developer Documentation](https://linear.app/developers) +- [Linear GraphQL API Reference](https://studio.apollographql.com/public/Linear-API/variant/current/home) + +## Known Issues and Limitations + +- **GraphQL-only API:** Linear uses a GraphQL API. All operations are POST requests to a single `/graphql` endpoint with query/mutation payloads. The connector abstracts this complexity. +- **Rate limits:** Linear enforces rate limits on API requests. Complex queries cost more against the rate limit budget. If you encounter rate limit errors (HTTP 429), add delays between actions in your flow. +- **Pagination:** List and search operations return up to 100 items per request. Use the `After Cursor` parameter with the `End Cursor` value from the response to retrieve additional pages. +- **Label replacement:** When updating labels on an issue via Update Issue, the `labelIds` array replaces all existing labels rather than appending to them. +- **API key scope:** The API key grants full access to the workspace. There is no way to limit it to specific teams or operations. +- **No filtering on list operations:** List Issues returns all issues in the workspace (paginated). Server-side filtering by team or state is not available in this version due to GraphQL optional parameter limitations. Use Power Automate's Filter Array action to filter results client-side. + +## License + +Distributed under the MIT License. diff --git a/independent-publisher-connectors/Linear/apiDefinition.swagger.json b/independent-publisher-connectors/Linear/apiDefinition.swagger.json new file mode 100644 index 0000000000..d41ca3af0e --- /dev/null +++ b/independent-publisher-connectors/Linear/apiDefinition.swagger.json @@ -0,0 +1,1628 @@ +{ + "swagger": "2.0", + "info": { + "title": "Linear", + "description": "Linear is a modern issue tracking and project management tool for software teams. Create, update, query, and search issues in your Linear workspace.", + "version": "1.0", + "contact": { + "name": "Aaron Mah", + "url": "https://linear.app" + } + }, + "host": "api.linear.app", + "basePath": "/", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "in": "header", + "name": "Authorization" + } + }, + "security": [ + { + "api_key": [] + } + ], + "x-ms-connector-metadata": [ + { + "propertyName": "Website", + "propertyValue": "https://linear.app" + }, + { + "propertyName": "Privacy policy", + "propertyValue": "https://linear.app/privacy" + }, + { + "propertyName": "Categories", + "propertyValue": "Productivity" + } + ], + "paths": { + "/graphql/listTeams": { + "post": { + "operationId": "listTeams", + "summary": "List Teams", + "description": "Retrieves all teams in the Linear workspace.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "query { teams { nodes { id name key description color } } }" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful response with list of teams.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "teams": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Team ID", + "description": "Unique team identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Team Name", + "description": "Team display name." + }, + "key": { + "type": "string", + "x-ms-summary": "Team Key", + "description": "Short key prefix for issue identifiers (e.g., ENG)." + }, + "description": { + "type": "string", + "x-ms-summary": "Description", + "description": "Team description." + }, + "color": { + "type": "string", + "x-ms-summary": "Color", + "description": "Team color as hex string." + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/listWorkflowStates": { + "post": { + "operationId": "listWorkflowStates", + "summary": "List Workflow States", + "description": "Retrieves workflow states (statuses) for the workspace.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "query { workflowStates { nodes { id name type color position team { id name } } } }" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful response with list of workflow states.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "workflowStates": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "State ID", + "description": "Unique workflow state identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "State Name", + "description": "State display name (e.g., Backlog, In Progress, Done)." + }, + "type": { + "type": "string", + "x-ms-summary": "State Type", + "description": "State category: backlog, unstarted, started, completed, or canceled." + }, + "color": { + "type": "string", + "x-ms-summary": "Color", + "description": "State color as hex string." + }, + "position": { + "type": "number", + "x-ms-summary": "Position", + "description": "Sort position within the team workflow." + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Team ID", + "description": "Parent team identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Team Name", + "description": "Parent team name." + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/listUsers": { + "post": { + "operationId": "listUsers", + "summary": "List Users", + "description": "Retrieves all users in the Linear workspace.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "query { users { nodes { id name displayName email active admin } } }" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful response with list of users.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "users": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "User ID", + "description": "Unique user identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "User Name", + "description": "Full name of the user." + }, + "displayName": { + "type": "string", + "x-ms-summary": "Display Name", + "description": "Display name of the user." + }, + "email": { + "type": "string", + "x-ms-summary": "Email", + "description": "Email address of the user." + }, + "active": { + "type": "boolean", + "x-ms-summary": "Active", + "description": "Whether the user account is active." + }, + "admin": { + "type": "boolean", + "x-ms-summary": "Admin", + "description": "Whether the user is a workspace admin." + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/listLabels": { + "post": { + "operationId": "listLabels", + "summary": "List Labels", + "description": "Retrieves all issue labels in the Linear workspace.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "query { issueLabels { nodes { id name description color parent { id name } } } }" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful response with list of labels.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "issueLabels": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Label ID", + "description": "Unique label identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Label Name", + "description": "Label display name." + }, + "description": { + "type": "string", + "x-ms-summary": "Label Description", + "description": "Description of the label." + }, + "color": { + "type": "string", + "x-ms-summary": "Color", + "description": "Label color as hex string." + }, + "parent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Parent Label ID", + "description": "Parent label identifier for grouped labels." + }, + "name": { + "type": "string", + "x-ms-summary": "Parent Label Name", + "description": "Parent label name." + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/createIssue": { + "post": { + "operationId": "createIssue", + "summary": "Create Issue", + "description": "Creates a new issue in a Linear team.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query", + "variables" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "mutation CreateIssue($teamId: String!, $title: String!, $description: String, $stateId: String, $assigneeId: String, $priority: Int, $dueDate: TimelessDate, $estimate: Int, $labelIds: [String!]) { issueCreate(input: { teamId: $teamId, title: $title, description: $description, stateId: $stateId, assigneeId: $assigneeId, priority: $priority, dueDate: $dueDate, estimate: $estimate, labelIds: $labelIds }) { success issue { id identifier title description url createdAt priority state { id name } assignee { id name } team { id name key } labels { nodes { id name } } } } }" + }, + "variables": { + "type": "object", + "required": [ + "teamId", + "title" + ], + "properties": { + "teamId": { + "type": "string", + "x-ms-summary": "Team", + "description": "Team where the issue will be created.", + "x-ms-visibility": "important", + "x-ms-dynamic-values": { + "operationId": "listTeams", + "value-path": "id", + "value-title": "name", + "value-collection": "data/teams/nodes", + "parameters": { + "body": {} + } + } + }, + "title": { + "type": "string", + "x-ms-summary": "Title", + "description": "Issue title.", + "x-ms-visibility": "important" + }, + "description": { + "type": "string", + "x-ms-summary": "Description", + "description": "Issue description. Supports markdown." + }, + "stateId": { + "type": "string", + "x-ms-summary": "Workflow State", + "description": "Workflow state. Defaults to the team Backlog state if omitted.", + "x-ms-dynamic-values": { + "operationId": "listWorkflowStates", + "value-path": "id", + "value-title": "name", + "value-collection": "data/workflowStates/nodes", + "parameters": { + "body": {} + } + } + }, + "assigneeId": { + "type": "string", + "x-ms-summary": "Assignee", + "description": "User to assign the issue to.", + "x-ms-dynamic-values": { + "operationId": "listUsers", + "value-path": "id", + "value-title": "name", + "value-collection": "data/users/nodes", + "parameters": { + "body": {} + } + } + }, + "priority": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Priority", + "description": "Priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low.", + "x-ms-visibility": "advanced" + }, + "dueDate": { + "type": "string", + "x-ms-summary": "Due Date", + "description": "Due date in YYYY-MM-DD format.", + "x-ms-visibility": "advanced" + }, + "estimate": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Estimate", + "description": "Story point or hour estimate.", + "x-ms-visibility": "advanced" + }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + }, + "x-ms-summary": "Label IDs", + "description": "Array of label IDs to apply. Get IDs from List Labels.", + "x-ms-visibility": "advanced" + } + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Issue created successfully.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "issueCreate": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "x-ms-summary": "Success", + "description": "Whether the issue was created successfully." + }, + "issue": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "Unique issue identifier." + }, + "identifier": { + "type": "string", + "x-ms-summary": "Identifier", + "description": "Human-readable identifier (e.g., ENG-42)." + }, + "title": { + "type": "string", + "x-ms-summary": "Title", + "description": "Issue title." + }, + "description": { + "type": "string", + "x-ms-summary": "Description", + "description": "Issue description." + }, + "url": { + "type": "string", + "x-ms-summary": "URL", + "description": "Direct URL to the issue in Linear." + }, + "createdAt": { + "type": "string", + "x-ms-summary": "Created At", + "description": "ISO 8601 creation timestamp." + }, + "priority": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Priority", + "description": "Priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low." + }, + "state": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "State ID", + "description": "Workflow state identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "State Name", + "description": "Workflow state name." + } + } + }, + "assignee": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Assignee ID", + "description": "Assigned user identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Assignee Name", + "description": "Assigned user name." + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Team ID", + "description": "Team identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Team Name", + "description": "Team name." + }, + "key": { + "type": "string", + "x-ms-summary": "Team Key", + "description": "Team key prefix." + } + } + }, + "labels": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Label ID", + "description": "Label identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Label Name", + "description": "Label name." + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/getIssue": { + "post": { + "operationId": "getIssue", + "summary": "Get Issue", + "description": "Retrieves a single issue by its UUID or human-readable identifier (e.g., ENG-42).", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query", + "variables" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "query GetIssue($id: String!) { issue(id: $id) { id identifier title description url createdAt updatedAt dueDate estimate priority state { id name type } assignee { id name email } creator { id name } team { id name key } labels { nodes { id name color } } } }" + }, + "variables": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "Issue UUID or human-readable identifier (e.g., ENG-42).", + "x-ms-visibility": "important" + } + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful response with issue details.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "issue": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "Unique issue identifier." + }, + "identifier": { + "type": "string", + "x-ms-summary": "Identifier", + "description": "Human-readable identifier (e.g., ENG-42)." + }, + "title": { + "type": "string", + "x-ms-summary": "Title", + "description": "Issue title." + }, + "url": { + "type": "string", + "x-ms-summary": "URL", + "description": "Direct URL to the issue in Linear." + }, + "createdAt": { + "type": "string", + "x-ms-summary": "Created At", + "description": "ISO 8601 creation timestamp." + }, + "updatedAt": { + "type": "string", + "x-ms-summary": "Updated At", + "description": "ISO 8601 last updated timestamp." + }, + "priority": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Priority", + "description": "Priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low." + }, + "state": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "State ID", + "description": "Current workflow state identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "State Name", + "description": "Current workflow state name." + }, + "type": { + "type": "string", + "x-ms-summary": "State Type", + "description": "State category." + } + } + }, + "assignee": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Assignee ID", + "description": "Assigned user identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Assignee Name", + "description": "Assigned user name." + }, + "email": { + "type": "string", + "x-ms-summary": "Assignee Email", + "description": "Assigned user email." + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Team ID", + "description": "Team identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Team Name", + "description": "Team name." + }, + "key": { + "type": "string", + "x-ms-summary": "Team Key", + "description": "Team key prefix." + } + } + }, + "description": { + "type": "string", + "x-ms-summary": "Description", + "description": "Issue description in markdown." + }, + "dueDate": { + "type": "string", + "x-ms-summary": "Due Date", + "description": "Due date in YYYY-MM-DD format." + }, + "estimate": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Estimate", + "description": "Point or hour estimate." + }, + "creator": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Creator ID", + "description": "Issue creator identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Creator Name", + "description": "Issue creator name." + } + } + }, + "labels": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Label ID", + "description": "Label identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Label Name", + "description": "Label name." + }, + "color": { + "type": "string", + "x-ms-summary": "Label Color", + "description": "Label color." + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/listIssues": { + "post": { + "operationId": "listIssues", + "summary": "List Issues", + "description": "Lists issues sorted by last updated time. Supports pagination.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "query ListIssues($first: Int, $after: String) { issues(first: $first, after: $after, orderBy: updatedAt) { nodes { id identifier title url createdAt updatedAt priority state { id name type } assignee { id name } team { id name key } } pageInfo { hasNextPage endCursor } } }" + }, + "variables": { + "type": "object", + "properties": { + "first": { + "type": "integer", + "format": "int32", + "default": 50, + "x-ms-summary": "Max Results", + "description": "Number of issues to return. Default 50, max 100.", + "x-ms-visibility": "advanced" + }, + "after": { + "type": "string", + "x-ms-summary": "After Cursor", + "description": "Pagination cursor. Use endCursor from a previous response.", + "x-ms-visibility": "advanced" + } + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful response with list of issues.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "issues": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "Unique issue identifier." + }, + "identifier": { + "type": "string", + "x-ms-summary": "Identifier", + "description": "Human-readable identifier (e.g., ENG-42)." + }, + "title": { + "type": "string", + "x-ms-summary": "Title", + "description": "Issue title." + }, + "url": { + "type": "string", + "x-ms-summary": "URL", + "description": "Direct URL to the issue in Linear." + }, + "createdAt": { + "type": "string", + "x-ms-summary": "Created At", + "description": "ISO 8601 creation timestamp." + }, + "updatedAt": { + "type": "string", + "x-ms-summary": "Updated At", + "description": "ISO 8601 last updated timestamp." + }, + "priority": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Priority", + "description": "Priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low." + }, + "state": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "State ID", + "description": "Current workflow state identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "State Name", + "description": "Current workflow state name." + }, + "type": { + "type": "string", + "x-ms-summary": "State Type", + "description": "State category." + } + } + }, + "assignee": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Assignee ID", + "description": "Assigned user identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Assignee Name", + "description": "Assigned user name." + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Team ID", + "description": "Team identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Team Name", + "description": "Team name." + }, + "key": { + "type": "string", + "x-ms-summary": "Team Key", + "description": "Team key prefix." + } + } + } + } + } + }, + "pageInfo": { + "type": "object", + "properties": { + "hasNextPage": { + "type": "boolean", + "x-ms-summary": "Has Next Page", + "description": "Whether more results exist beyond this page." + }, + "endCursor": { + "type": "string", + "x-ms-summary": "End Cursor", + "description": "Cursor to pass as after parameter for the next page." + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/updateIssue": { + "post": { + "operationId": "updateIssue", + "summary": "Update Issue", + "description": "Updates properties of an existing issue.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query", + "variables" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "mutation UpdateIssue($id: String!, $title: String, $description: String, $stateId: String, $assigneeId: String, $priority: Int, $dueDate: TimelessDate, $estimate: Int, $labelIds: [String!]) { issueUpdate(id: $id, input: { title: $title, description: $description, stateId: $stateId, assigneeId: $assigneeId, priority: $priority, dueDate: $dueDate, estimate: $estimate, labelIds: $labelIds }) { success issue { id identifier title description url updatedAt priority state { id name } assignee { id name } team { id name key } } } }" + }, + "variables": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "UUID of the issue to update.", + "x-ms-visibility": "important" + }, + "title": { + "type": "string", + "x-ms-summary": "Title", + "description": "New issue title." + }, + "description": { + "type": "string", + "x-ms-summary": "Description", + "description": "New issue description. Supports markdown." + }, + "stateId": { + "type": "string", + "x-ms-summary": "Workflow State", + "description": "New workflow state for the issue.", + "x-ms-dynamic-values": { + "operationId": "listWorkflowStates", + "value-path": "id", + "value-title": "name", + "value-collection": "data/workflowStates/nodes", + "parameters": { + "body": {} + } + } + }, + "assigneeId": { + "type": "string", + "x-ms-summary": "Assignee", + "description": "New assignee for the issue.", + "x-ms-dynamic-values": { + "operationId": "listUsers", + "value-path": "id", + "value-title": "name", + "value-collection": "data/users/nodes", + "parameters": { + "body": {} + } + } + }, + "priority": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Priority", + "description": "New priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low.", + "x-ms-visibility": "advanced" + }, + "dueDate": { + "type": "string", + "x-ms-summary": "Due Date", + "description": "New due date in YYYY-MM-DD format.", + "x-ms-visibility": "advanced" + }, + "estimate": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Estimate", + "description": "New estimate value.", + "x-ms-visibility": "advanced" + }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + }, + "x-ms-summary": "Label IDs", + "description": "New label IDs (replaces all existing labels).", + "x-ms-visibility": "advanced" + } + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Issue updated successfully.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "issueUpdate": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "x-ms-summary": "Success", + "description": "Whether the update succeeded." + }, + "issue": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "Unique issue identifier." + }, + "identifier": { + "type": "string", + "x-ms-summary": "Identifier", + "description": "Human-readable identifier (e.g., ENG-42)." + }, + "title": { + "type": "string", + "x-ms-summary": "Title", + "description": "Issue title." + }, + "description": { + "type": "string", + "x-ms-summary": "Description", + "description": "Issue description." + }, + "url": { + "type": "string", + "x-ms-summary": "URL", + "description": "Direct URL to the issue in Linear." + }, + "priority": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Priority", + "description": "Priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low." + }, + "state": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "State ID", + "description": "Workflow state identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "State Name", + "description": "Workflow state name." + } + } + }, + "assignee": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Assignee ID", + "description": "Assigned user identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Assignee Name", + "description": "Assigned user name." + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Team ID", + "description": "Team identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Team Name", + "description": "Team name." + }, + "key": { + "type": "string", + "x-ms-summary": "Team Key", + "description": "Team key prefix." + } + } + }, + "labels": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Label ID", + "description": "Label identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Label Name", + "description": "Label name." + } + } + } + } + } + }, + "updatedAt": { + "type": "string", + "x-ms-summary": "Updated At", + "description": "ISO 8601 timestamp of this update." + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/addComment": { + "post": { + "operationId": "addComment", + "summary": "Add Comment", + "description": "Creates a comment on an existing issue.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query", + "variables" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "mutation AddComment($issueId: String!, $body: String!) { commentCreate(input: { issueId: $issueId, body: $body }) { success comment { id body createdAt user { id name } issue { id identifier } } } }" + }, + "variables": { + "type": "object", + "required": [ + "issueId", + "body" + ], + "properties": { + "issueId": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "UUID of the issue to comment on.", + "x-ms-visibility": "important" + }, + "body": { + "type": "string", + "x-ms-summary": "Comment Body", + "description": "Comment text. Supports markdown.", + "x-ms-visibility": "important" + } + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Comment created successfully.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "commentCreate": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "x-ms-summary": "Success", + "description": "Whether the comment was created successfully." + }, + "comment": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Comment ID", + "description": "Unique comment identifier." + }, + "body": { + "type": "string", + "x-ms-summary": "Body", + "description": "Comment body text." + }, + "createdAt": { + "type": "string", + "x-ms-summary": "Created At", + "description": "ISO 8601 creation timestamp." + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "User ID", + "description": "Commenting user identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "User Name", + "description": "Commenting user name." + } + } + }, + "issue": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "Parent issue identifier." + }, + "identifier": { + "type": "string", + "x-ms-summary": "Issue Identifier", + "description": "Parent issue identifier (e.g., ENG-42)." + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/graphql/searchIssues": { + "post": { + "operationId": "searchIssues", + "summary": "Search Issues", + "description": "Searches issues by text across titles and descriptions.", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "required": [ + "query", + "variables" + ], + "properties": { + "query": { + "type": "string", + "x-ms-visibility": "internal", + "default": "query SearchIssues($searchText: String!, $first: Int, $after: String) { searchIssues(term: $searchText, first: $first, after: $after) { nodes { id identifier title description url createdAt updatedAt priority state { id name type } assignee { id name } team { id name key } } pageInfo { hasNextPage endCursor } } }" + }, + "variables": { + "type": "object", + "required": [ + "searchText" + ], + "properties": { + "searchText": { + "type": "string", + "x-ms-summary": "Search Text", + "description": "Text to search for across issue titles and descriptions.", + "x-ms-visibility": "important" + }, + "first": { + "type": "integer", + "format": "int32", + "default": 50, + "x-ms-summary": "Max Results", + "description": "Number of results to return. Default 50, max 100.", + "x-ms-visibility": "advanced" + }, + "after": { + "type": "string", + "x-ms-summary": "After Cursor", + "description": "Pagination cursor. Use endCursor from a previous response.", + "x-ms-visibility": "advanced" + } + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful response with matching issues.", + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "searchIssues": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Issue ID", + "description": "Unique issue identifier." + }, + "identifier": { + "type": "string", + "x-ms-summary": "Identifier", + "description": "Human-readable identifier (e.g., ENG-42)." + }, + "title": { + "type": "string", + "x-ms-summary": "Title", + "description": "Issue title." + }, + "url": { + "type": "string", + "x-ms-summary": "URL", + "description": "Direct URL to the issue in Linear." + }, + "createdAt": { + "type": "string", + "x-ms-summary": "Created At", + "description": "ISO 8601 creation timestamp." + }, + "updatedAt": { + "type": "string", + "x-ms-summary": "Updated At", + "description": "ISO 8601 last updated timestamp." + }, + "priority": { + "type": "integer", + "format": "int32", + "x-ms-summary": "Priority", + "description": "Priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low." + }, + "state": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "State ID", + "description": "Current workflow state identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "State Name", + "description": "Current workflow state name." + }, + "type": { + "type": "string", + "x-ms-summary": "State Type", + "description": "State category." + } + } + }, + "assignee": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Assignee ID", + "description": "Assigned user identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Assignee Name", + "description": "Assigned user name." + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string", + "x-ms-summary": "Team ID", + "description": "Team identifier." + }, + "name": { + "type": "string", + "x-ms-summary": "Team Name", + "description": "Team name." + }, + "key": { + "type": "string", + "x-ms-summary": "Team Key", + "description": "Team key prefix." + } + } + }, + "description": { + "type": "string", + "x-ms-summary": "Description", + "description": "Issue description." + } + } + } + }, + "pageInfo": { + "type": "object", + "properties": { + "hasNextPage": { + "type": "boolean", + "x-ms-summary": "Has Next Page", + "description": "Whether more results exist beyond this page." + }, + "endCursor": { + "type": "string", + "x-ms-summary": "End Cursor", + "description": "Cursor to pass as after parameter for the next page." + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "definitions": {}, + "parameters": {}, + "responses": {}, + "tags": [] +} \ No newline at end of file diff --git a/independent-publisher-connectors/Linear/apiProperties.json b/independent-publisher-connectors/Linear/apiProperties.json new file mode 100644 index 0000000000..cc1ae93f4b --- /dev/null +++ b/independent-publisher-connectors/Linear/apiProperties.json @@ -0,0 +1,23 @@ +{ + "properties": { + "connectionParameters": { + "api_key": { + "type": "securestring", + "uiDefinition": { + "displayName": "API Key", + "description": "Your Linear Personal API Key.", + "tooltip": "Go to Linear Settings > Account > Security to create an API key (starts with lin_api_).", + "constraints": { + "tabIndex": 2, + "clearText": false, + "required": "true" + } + } + } + }, + "iconBrandColor": "#da3b01", + "capabilities": [], + "publisher": "Aaron Mah", + "stackOwner": "Linear" + } +} diff --git a/independent-publisher-connectors/Linear/intro.md b/independent-publisher-connectors/Linear/intro.md new file mode 100644 index 0000000000..af2067a719 --- /dev/null +++ b/independent-publisher-connectors/Linear/intro.md @@ -0,0 +1,34 @@ +# Linear + +Linear is a modern issue tracking and project management tool for software teams. Create, update, query, and search issues in your Linear workspace. + +## Publisher + +Aaron Mah (aaronmah@microsoft.com) + +## API Documentation + +https://linear.app/developers + +## Authentication + +API Key (Personal API Key from Linear Settings → Account → Security) + +## Supported Operations (10) + +| Operation | +|---| +| List Teams | +| List Workflow States | +| List Users | +| List Labels | +| Create Issue | +| Get Issue | +| List Issues | +| Update Issue | +| Add Comment | +| Search Issues | + +## Contact + +For collaboration inquiries, reach out to aaronmah@microsoft.com diff --git a/independent-publisher-connectors/Linear/package.zip b/independent-publisher-connectors/Linear/package.zip new file mode 100644 index 0000000000..274e9904f1 Binary files /dev/null and b/independent-publisher-connectors/Linear/package.zip differ