Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.8k
feat(tools): added linear tools/block#439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
c15ed30cf5498c481d0c6bd0079c15077ebfe058dc3b83c63e9a619423abcee40d964184c2d6b7dd3b35d744f7c8db78a1b3706097483381211d5b17c282b3File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,104 @@ | ||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||
| title: Linear | ||||||||||||||||||||||||||||||||||
| description: Read and create issues in Linear | ||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||
| import { BlockInfoCard } from "@/components/ui/block-info-card" | ||||||||||||||||||||||||||||||||||
| <BlockInfoCard | ||||||||||||||||||||||||||||||||||
| type="linear" | ||||||||||||||||||||||||||||||||||
| color="#5E6AD2" | ||||||||||||||||||||||||||||||||||
| icon={true} | ||||||||||||||||||||||||||||||||||
| iconSvg={`<svg className="block-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 100 100"><path fill="#fff" d="M1.22541 61.5228c-.2225-.9485.90748-1.5459 1.59638-.857L39.3342 97.1782c.6889.6889.0915 1.8189-.857 1.5964C20.0515 94.4522 5.54779 79.9485 1.22541 61.5228ZM.00189135 46.8891c-.01764375.2833.08887215.5599.28957165.7606L52.3503 99.7085c.2007.2007.4773.3075.7606.2896 2.3692-.1476 4.6938-.46 6.9624-.9259.7645-.157 1.0301-1.0963.4782-1.6481L2.57595 39.4485c-.55186-.5519-1.49117-.2863-1.648174.4782-.465915 2.2686-.77832 4.5932-.92588465 6.9624ZM4.21093 29.7054c-.16649.3738-.08169.8106.20765 1.1l64.77602 64.776c.2894.2894.7262.3742 1.1.2077 1.7861-.7956 3.5171-1.6927 5.1855-2.684.5521-.328.6373-1.0867.1832-1.5407L8.43566 24.3367c-.45409-.4541-1.21271-.3689-1.54074.1832-.99132 1.6684-1.88843 3.3994-2.68399 5.1855ZM12.6587 18.074c-.3701-.3701-.393-.9637-.0443-1.3541C21.7795 6.45931 35.1114 0 49.9519 0 77.5927 0 100 22.4073 100 50.0481c0 14.8405-6.4593 28.1724-16.7199 37.3375-.3903.3487-.984.3258-1.3542-.0443L12.6587 18.074Z"/></svg>`} | ||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| {/* MANUAL-CONTENT-START:intro */} | ||||||||||||||||||||||||||||||||||
| [Linear](https://linear.app) is a leading project management and issue tracking platform that helps teams plan, track, and manage their work effectively. As a modern project management tool, Linear has become increasingly popular among software development teams and project management professionals for its streamlined interface and powerful features. | ||||||||||||||||||||||||||||||||||
| Linear provides a comprehensive set of tools for managing complex projects through its flexible and customizable workflow system. With its robust API and integration capabilities, Linear enables teams to streamline their development processes and maintain clear visibility of project progress. | ||||||||||||||||||||||||||||||||||
| Key features of Linear include: | ||||||||||||||||||||||||||||||||||
| - Agile Project Management: Support for Scrum and Kanban methodologies with customizable boards and workflows | ||||||||||||||||||||||||||||||||||
| - Issue Tracking: Sophisticated tracking system for bugs, stories, epics, and tasks with detailed reporting | ||||||||||||||||||||||||||||||||||
| - Workflow Automation: Powerful automation rules to streamline repetitive tasks and processes | ||||||||||||||||||||||||||||||||||
| - Advanced Search: Complex filtering and reporting capabilities for efficient issue management | ||||||||||||||||||||||||||||||||||
| In Sim Studio, the Linear integration allows your agents to seamlessly interact with your project management workflow. This creates opportunities for automated issue creation, updates, and tracking as part of your AI workflows. The integration enables agents to read existing issues and create new ones programmatically, facilitating automated project management tasks and ensuring that important information is properly tracked and documented. By connecting Sim Studio with Linear, you can build intelligent agents that maintain project visibility while automating routine project management tasks, enhancing team productivity and ensuring consistent project tracking. | ||||||||||||||||||||||||||||||||||
| {/* MANUAL-CONTENT-END */} | ||||||||||||||||||||||||||||||||||
| ## Usage Instructions | ||||||||||||||||||||||||||||||||||
| Integrate with Linear to fetch, filter, and create issues directly from your workflow. | ||||||||||||||||||||||||||||||||||
| ## Tools | ||||||||||||||||||||||||||||||||||
| ### `linear_read_issues` | ||||||||||||||||||||||||||||||||||
| Fetch and filter issues from Linear | ||||||||||||||||||||||||||||||||||
| #### Input | ||||||||||||||||||||||||||||||||||
| | Parameter | Type | Required | Description | | ||||||||||||||||||||||||||||||||||
| | --------- | ---- | -------- | ----------- | | ||||||||||||||||||||||||||||||||||
| | `teamId` | string | Yes | Linear team ID | | ||||||||||||||||||||||||||||||||||
| | `projectId` | string | Yes | Linear project ID | | ||||||||||||||||||||||||||||||||||
| #### Output | ||||||||||||||||||||||||||||||||||
| | Parameter | Type | | ||||||||||||||||||||||||||||||||||
| | --------- | ---- | | ||||||||||||||||||||||||||||||||||
| | `issues` | string | | ||||||||||||||||||||||||||||||||||
| ### `linear_create_issue` | ||||||||||||||||||||||||||||||||||
| Create a new issue in Linear | ||||||||||||||||||||||||||||||||||
| #### Input | ||||||||||||||||||||||||||||||||||
| | Parameter | Type | Required | Description | | ||||||||||||||||||||||||||||||||||
| | --------- | ---- | -------- | ----------- | | ||||||||||||||||||||||||||||||||||
| | `teamId` | string | Yes | Linear team ID | | ||||||||||||||||||||||||||||||||||
| | `projectId` | string | Yes | Linear project ID | | ||||||||||||||||||||||||||||||||||
| | `title` | string | Yes | Issue title | | ||||||||||||||||||||||||||||||||||
| | `description` | string | No | Issue description | | ||||||||||||||||||||||||||||||||||
| #### Output | ||||||||||||||||||||||||||||||||||
| | Parameter | Type | | ||||||||||||||||||||||||||||||||||
| | --------- | ---- | | ||||||||||||||||||||||||||||||||||
| | `issue` | string | | ||||||||||||||||||||||||||||||||||
| | `title` | string | | ||||||||||||||||||||||||||||||||||
| | `description` | string | | ||||||||||||||||||||||||||||||||||
| | `state` | string | | ||||||||||||||||||||||||||||||||||
| | `teamId` | string | | ||||||||||||||||||||||||||||||||||
| | `projectId` | string | | ||||||||||||||||||||||||||||||||||
Comment on lines
+71
to
+78
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Output parameters should be properly typed as object properties rather than separate string fields
Suggested change
| ||||||||||||||||||||||||||||||||||
| ## Block Configuration | ||||||||||||||||||||||||||||||||||
| ### Input | ||||||||||||||||||||||||||||||||||
| | Parameter | Type | Required | Description | | ||||||||||||||||||||||||||||||||||
| | --------- | ---- | -------- | ----------- | | ||||||||||||||||||||||||||||||||||
| | `operation` | string | Yes | Operation | | ||||||||||||||||||||||||||||||||||
| ### Outputs | ||||||||||||||||||||||||||||||||||
| | Output | Type | Description | | ||||||||||||||||||||||||||||||||||
| | ------ | ---- | ----------- | | ||||||||||||||||||||||||||||||||||
| | `response` | object | Output from response | | ||||||||||||||||||||||||||||||||||
| | ↳ `issues` | json | issues of the response | | ||||||||||||||||||||||||||||||||||
| | ↳ `issue` | json | issue of the response | | ||||||||||||||||||||||||||||||||||
| ## Notes | ||||||||||||||||||||||||||||||||||
| - Category: `tools` | ||||||||||||||||||||||||||||||||||
| - Type: `linear` | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -21,6 +21,7 @@ | ||
| "image_generator", | ||
| "jina", | ||
| "jira", | ||
| "linear", | ||
| "linkup", | ||
| "mem0", | ||
| "memory", | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import type { Project } from '@linear/sdk' | ||
| import { LinearClient } from '@linear/sdk' | ||
| import { NextResponse } from 'next/server' | ||
| import { getSession } from '@/lib/auth' | ||
| import { createLogger } from '@/lib/logs/console-logger' | ||
| import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' | ||
| export const dynamic = 'force-dynamic' | ||
| const logger = createLogger('LinearProjects') | ||
| export async function POST(request: Request) { | ||
| try { | ||
| const session = await getSession() | ||
| const body = await request.json() | ||
| const { credential, teamId, workflowId } = body | ||
Comment on lines
+15
to
+16
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Add request body validation using zod or similar to ensure type safety | ||
| if (!credential || !teamId) { | ||
| logger.error('Missing credential or teamId in request') | ||
| return NextResponse.json({ error: 'Credential and teamId are required' }, { status: 400 }) | ||
| } | ||
| const userId = session?.user?.id || '' | ||
| if (!userId) { | ||
| logger.error('No user ID found in session') | ||
| return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) | ||
| } | ||
| const accessToken = await refreshAccessTokenIfNeeded(credential, userId, workflowId) | ||
| if (!accessToken) { | ||
| logger.error('Failed to get access token', { credentialId: credential, userId }) | ||
| return NextResponse.json( | ||
| { | ||
| error: 'Could not retrieve access token', | ||
| authRequired: true, | ||
| }, | ||
| { status: 401 } | ||
| ) | ||
| } | ||
| const linearClient = new LinearClient({ accessToken }) | ||
| let projects = [] | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Initialize projects as const with type annotation for better type safety | ||
| const team = await linearClient.team(teamId) | ||
| const projectsResult = await team.projects() | ||
| projects = projectsResult.nodes.map((project: Project) => ({ | ||
| id: project.id, | ||
| name: project.name, | ||
| })) | ||
| if (projects.length === 0) { | ||
| logger.info('No projects found for team', { teamId }) | ||
| } | ||
| return NextResponse.json({ projects }) | ||
| } catch (error) { | ||
| logger.error('Error processing Linear projects request:', error) | ||
| return NextResponse.json( | ||
| { error: 'Failed to retrieve Linear projects', details: (error as Error).message }, | ||
| { status: 500 } | ||
| ) | ||
Comment on lines
+56
to
+61
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider handling specific Linear API errors separately from generic errors | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import type { Team } from '@linear/sdk' | ||
| import { LinearClient } from '@linear/sdk' | ||
| import { NextResponse } from 'next/server' | ||
| import { getSession } from '@/lib/auth' | ||
| import { createLogger } from '@/lib/logs/console-logger' | ||
| import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' | ||
| export const dynamic = 'force-dynamic' | ||
| const logger = createLogger('LinearTeams') | ||
| export async function POST(request: Request) { | ||
| try { | ||
| const session = await getSession() | ||
| const body = await request.json() | ||
| const { credential, workflowId } = body | ||
| if (!credential) { | ||
| logger.error('Missing credential in request') | ||
| return NextResponse.json({ error: 'Credential is required' }, { status: 400 }) | ||
| } | ||
| const userId = session?.user?.id || '' | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (!userId) { | ||
| logger.error('No user ID found in session') | ||
| return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) | ||
| } | ||
| const accessToken = await refreshAccessTokenIfNeeded(credential, userId, workflowId) | ||
| if (!accessToken) { | ||
| logger.error('Failed to get access token', { credentialId: credential, userId }) | ||
| return NextResponse.json( | ||
| { | ||
| error: 'Could not retrieve access token', | ||
| authRequired: true, | ||
| }, | ||
| { status: 401 } | ||
| ) | ||
| } | ||
| const linearClient = new LinearClient({ accessToken }) | ||
| const teamsResult = await linearClient.teams() | ||
| const teams = teamsResult.nodes.map((team: Team) => ({ | ||
| id: team.id, | ||
| name: team.name, | ||
| })) | ||
| return NextResponse.json({ teams }) | ||
| } catch (error) { | ||
| logger.error('Error processing Linear teams request:', error) | ||
| return NextResponse.json( | ||
| { error: 'Failed to retrieve Linear teams', details: (error as Error).message }, | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| { status: 500 } | ||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { useEffect, useState } from 'react' | ||
| import { | ||
| Select, | ||
| SelectContent, | ||
| SelectItem, | ||
| SelectTrigger, | ||
| SelectValue, | ||
| } from '@/components/ui/select' | ||
| export interface LinearProjectInfo { | ||
| id: string | ||
| name: string | ||
| } | ||
| interface LinearProjectSelectorProps { | ||
| value: string | ||
| onChange: (projectId: string, projectInfo?: LinearProjectInfo) => void | ||
| credential: string | ||
| teamId: string | ||
| label?: string | ||
| disabled?: boolean | ||
| } | ||
| export function LinearProjectSelector({ | ||
| value, | ||
| onChange, | ||
| credential, | ||
| teamId, | ||
| label = 'Select Linear project', | ||
| disabled = false, | ||
| }: LinearProjectSelectorProps) { | ||
| const [projects, setProjects] = useState<LinearProjectInfo[]>([]) | ||
| const [loading, setLoading] = useState(false) | ||
| const [error, setError] = useState<string | null>(null) | ||
| useEffect(() => { | ||
| if (!credential || !teamId) return | ||
| const controller = new AbortController() | ||
| setLoading(true) | ||
| fetch('/api/tools/linear/projects', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ credential, teamId }), | ||
| signal: controller.signal, | ||
| }) | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| .then(async (res) => { | ||
| if (!res.ok) { | ||
| const errorText = await res.text() | ||
| throw new Error(`HTTP error! status: ${res.status} - ${errorText}`) | ||
| } | ||
| return res.json() | ||
| }) | ||
| .then((data) => { | ||
| if (data.error) { | ||
| setError(data.error) | ||
| setProjects([]) | ||
| } else { | ||
| setProjects(data.projects) | ||
| } | ||
| }) | ||
| .catch((err) => { | ||
| if (err.name === 'AbortError') return | ||
| setError(err.message) | ||
| setProjects([]) | ||
| }) | ||
| .finally(() => setLoading(false)) | ||
| return () => controller.abort() | ||
| }, [credential, teamId]) | ||
| return ( | ||
| <Select | ||
| value={value} | ||
| onValueChange={(projectId) => { | ||
| const projectInfo = projects.find((p) => p.id === projectId) | ||
| onChange(projectId, projectInfo) | ||
| }} | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| disabled={disabled || loading || !credential || !teamId} | ||
| > | ||
| <SelectTrigger className='w-full'> | ||
| <SelectValue placeholder={loading ? 'Loading projects...' : label} /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| {projects.map((project) => ( | ||
| <SelectItem key={project.id} value={project.id}> | ||
| {project.name} | ||
| </SelectItem> | ||
| ))} | ||
| {error && <div className='px-2 py-1 text-red-500'>{error}</div>} | ||
| </SelectContent> | ||
| </Select> | ||
| ) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Output type for 'issues' should be array or object, not string, since it returns multiple issues