feat(linear): added Linear tool#430
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
@sriram2k4 is attempting to deploy a commit to the Sim Studio Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
PR Summary
Added Linear integration to enable issue management through Linear's GraphQL API, with OAuth authentication and dedicated UI components for team/project selection.
- Added
/api/tools/linear/teamsand/api/tools/linear/projectsendpoints with proper OAuth token refresh and error handling - Created
LinearBlockconfiguration with read/write operations and required field validation for issue management - Implemented
LinearProjectSelectorandLinearTeamSelectorcomponents with loading states and error handling - Added Linear OAuth provider configuration with read/write scopes and GraphQL-based user info retrieval
- Integrated Linear tools (
linearReadIssuesToolandlinearCreateIssueTool) with proper GraphQL queries and response transformation
💡 (2/5) Greptile learns from your feedback when you react with 👍/👎!
17 file(s) reviewed, 21 comment(s)
Edit PR Review Bot Settings | Greptile
|
|
||
| const linearClient = new LinearClient({ accessToken }) | ||
| const teamsResult = await linearClient.teams() | ||
| const teams = teamsResult.nodes.map((team: any) => ({ |
There was a problem hiding this comment.
style: Type any used for team object. Consider using proper Linear SDK types for better type safety
| const teams = teamsResult.nodes.map((team: any) => ({ | |
| const teams = teamsResult.nodes.map((team: Team) => ({ |
| teamId: string | ||
| label?: string | ||
| disabled?: boolean | ||
| showPreview?: boolean |
There was a problem hiding this comment.
style: showPreview prop is defined but never used in the component
| useEffect(() => { | ||
| if (!credential || !teamId) return | ||
| setLoading(true) | ||
| fetch('/api/tools/linear/projects', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ credential, teamId }), | ||
| }) | ||
| .then((res) => res.json()) | ||
| .then((data) => { | ||
| if (data.error) { | ||
| setError(data.error) | ||
| setProjects([]) | ||
| } else { | ||
| setProjects(data.projects) | ||
| } | ||
| }) | ||
| .catch((err) => { | ||
| setError(err.message) | ||
| setProjects([]) | ||
| }) | ||
| .finally(() => setLoading(false)) | ||
| }, [credential, teamId]) |
There was a problem hiding this comment.
style: consider using AbortController to cancel pending requests when component unmounts or dependencies change
| useEffect(() => { | |
| if (!credential || !teamId) return | |
| setLoading(true) | |
| fetch('/api/tools/linear/projects', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ credential, teamId }), | |
| }) | |
| .then((res) => res.json()) | |
| .then((data) => { | |
| if (data.error) { | |
| setError(data.error) | |
| setProjects([]) | |
| } else { | |
| setProjects(data.projects) | |
| } | |
| }) | |
| .catch((err) => { | |
| setError(err.message) | |
| setProjects([]) | |
| }) | |
| .finally(() => setLoading(false)) | |
| }, [credential, teamId]) | |
| 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 | |
| }) | |
| .then((res) => 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]) |
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ credential, teamId }), | ||
| }) | ||
| .then((res) => res.json()) |
There was a problem hiding this comment.
logic: should check res.ok before calling res.json() to handle non-200 responses
| .then((res) => res.json()) | |
| .then((res) => { | |
| if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`) | |
| return res.json() | |
| }) |
| projectId: '', | ||
| }, | ||
| }, | ||
| error: data.errors[0].message, |
There was a problem hiding this comment.
style: Only returning first error message may hide important context from multiple GraphQL errors
| method: 'POST', | ||
| headers: (params) => ({ | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${params.accessToken || ''}`, |
There was a problem hiding this comment.
logic: Empty string fallback for missing access token could cause silent auth failures. Should throw error if token missing.
| Authorization: `Bearer ${params.accessToken || ''}`, | |
| Authorization: `Bearer ${params.accessToken ?? throw new Error('Linear access token is required')}` |
| return { | ||
| success: true, | ||
| output: { | ||
| issues: data.data.issues.nodes.map((issue: any) => ({ |
There was a problem hiding this comment.
style: Using 'any' type for issue mapping loses type safety. Consider defining proper interface for Linear API response.
| transformResponse: async (response) => { | ||
| const data = await response.json() | ||
| if (data.errors) { | ||
| return { success: false, output: { issues: [] }, error: data.errors[0].message } |
There was a problem hiding this comment.
style: Only returns first error message when multiple GraphQL errors may be present. Consider aggregating all error messages.
|
merged as a part of #439 |
…5288) * fix(workflow-renderer): validate dropbox host in note embed renderer Replace the bare url.includes('dropbox.com') check with a parsed-hostname match so attacker-controlled hosts (dropbox.com.evil.com, evil.com/?dropbox.com) no longer get treated as direct dropbox videos. Resolves CodeQL js/incomplete-url-substring-sanitization (#430). * fix(workflow-renderer): rewrite dropbox embed via parsed URL, tolerate scheme-less links Derive the direct video URL from the parsed URL object (rewrite hostname to dl.dropboxusercontent.com for any dropbox.com/*.dropbox.com host) instead of a www-only string replace, and accept scheme-less links. Fixes broken embeds for m.dropbox.com / bare-host links flagged in review.
Description
Users can now read and write Linear issues when both a team and project are specified.
Fixes #104
Type of change
Please delete options that are not relevant.
How Has This Been Tested?
Tested by configuring the Linear integration, attempting to create and read issues, Verified that issue creation and retrieval work as expected when all required fields are provided.
Checklist:
bun run test)Security Considerations:
Additional Information:
Any additional information, configuration or data that might be necessary to reproduce the issue or use the feature.