Linear Demo Video Tickets Creator
Creates recurring demo video tickets in Linear for all team members on specified days. Automatically finds the keyboard team, gets all users, and creates tickets with detailed instructions for creating demo videos. Configurable parameters include team name, project name, demo days, and priority levels.

Created by
Stephen Roland - Keyboard Team
Requirements
KEYBOARD_LINEAR_API_KEY
Script
Copy script
Copied
Copy script
Copied
const axios = require('axios'); // Linear API configuration const LINEAR_API_URL = 'https://api.linear.app/graphql'; const LINEAR_API_KEY = process.env.KEYBOARD_LINEAR_API_KEY; if (!LINEAR_API_KEY) { console.error('ā Linear API key not found in environment variables'); process.exit(1); } // Linear expects just the API key, not "Bearer" prefix const headers = { 'Content-Type': 'application/json', 'Authorization': LINEAR_API_KEY }; console.log(`š Starting Linear ticket creation for ${projectName} demo videos...`); async function makeLinearRequest(query) { try { const response = await axios.post(LINEAR_API_URL, { query }, { headers }); if (response.data.errors) { console.error('GraphQL Errors:', JSON.stringify(response.data.errors, null, 2)); return null; } return response.data.data; } catch (error) { console.error('API Error:', error.response?.data || error.message); return null; } } // Get teams and users async function getTeamsAndUsers() { const query = ` query { teams { nodes { id name key } } users { nodes { id name email displayName } } }`; console.log('š Fetching teams and users...'); return await makeLinearRequest(query); } // Get teams and users first const data = await getTeamsAndUsers(); if (!data) { console.error('ā Failed to fetch teams and users'); process.exit(1); } console.log('š Available teams:'); data.teams.nodes.forEach(team => { console.log(` ⢠${team.name} (${team.key}) - ID: ${team.id}`); }); console.log('\nš„ Available users:'); data.users.nodes.forEach(user => { console.log(` ⢠${user.displayName || user.name} (${user.email}) - ID: ${user.id}`); }); // Find the target team let targetTeam = data.teams.nodes.find(team => team.name.toLowerCase().includes(teamName.toLowerCase()) || team.key.toLowerCase().includes(teamName.toLowerCase()) ); // If no specific team found, use the first available team if (!targetTeam && data.teams.nodes.length > 0) { targetTeam = data.teams.nodes[0]; console.log(`ā ļø No specific '${teamName}' team found, using first available team: ${targetTeam.name}`); } else if (targetTeam) { console.log(`ā Found ${teamName} team: ${targetTeam.name} (${targetTeam.key})`); } if (!targetTeam) { console.error('ā No teams available'); process.exit(1); } // Create tickets for each user const users = data.users.nodes; const createdTickets = []; console.log(`\nš¬ Creating demo video tickets for ${users.length} team members...`); for (const user of users) { // Create first day ticket const day1Mutation = ` mutation { issueCreate( input: { title: "Create Demo Video for ${projectName} - ${day1}" description: "## Demo Video Task\\n\\n**Assigned to:** ${user.displayName || user.name}\\n\\n**Task:** Create a demo video showcasing the ${projectName} project features and functionality.\\n\\n### Requirements:\\n- Record a comprehensive demo of current features\\n- Highlight key functionality and user workflows\\n- Keep video concise but informative (5-10 minutes recommended)\\n- Include any recent updates or improvements\\n\\n**Due:** Every ${day1}\\n\\n**Note:** This is a recurring task. Please complete and update this ticket every ${day1} with your demo video." teamId: "${targetTeam.id}" assigneeId: "${user.id}" priority: ${priority} } ) { success issue { id title assignee { name } } } }`; const day1Result = await makeLinearRequest(day1Mutation); if (day1Result?.issueCreate?.success) { console.log(`ā ${day1} ticket created for ${user.displayName || user.name} - ID: ${day1Result.issueCreate.issue.id}`); createdTickets.push({ day: day1, user: user.displayName || user.name, ticketId: day1Result.issueCreate.issue.id }); } else { console.log(`ā Failed to create ${day1} ticket for ${user.displayName || user.name}`); } // Create second day ticket const day2Mutation = ` mutation { issueCreate( input: { title: "Create Demo Video for ${projectName} - ${day2}" description: "## Demo Video Task\\n\\n**Assigned to:** ${user.displayName || user.name}\\n\\n**Task:** Create a demo video showcasing the ${projectName} project features and functionality.\\n\\n### Requirements:\\n- Record a comprehensive demo of current features\\n- Highlight key functionality and user workflows\\n- Keep video concise but informative (5-10 minutes recommended)\\n- Include any recent updates or improvements\\n\\n**Due:** Every ${day2}\\n\\n**Note:** This is a recurring task. Please complete and update this ticket every ${day2} with your demo video." teamId: "${targetTeam.id}" assigneeId: "${user.id}" priority: ${priority} } ) { success issue { id title assignee { name } } } }`; const day2Result = await makeLinearRequest(day2Mutation); if (day2Result?.issueCreate?.success) { console.log(`ā ${day2} ticket created for ${user.displayName || user.name} - ID: ${day2Result.issueCreate.issue.id}`); createdTickets.push({ day: day2, user: user.displayName || user.name, ticketId: day2Result.issueCreate.issue.id }); } else { console.log(`ā Failed to create ${day2} ticket for ${user.displayName || user.name}`); } // Small delay to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 500)); } console.log(`\nš Summary of created tickets:`); console.log(`Total tickets created: ${createdTickets.length}`); console.log(`Team: ${targetTeam.name} (${targetTeam.key})`); createdTickets.forEach(ticket => { console.log(` ⢠${ticket.day} - ${ticket.user} - Ticket ID: ${ticket.ticketId}`); }); // Verify tickets by querying them console.log('\nš Verifying created tickets...'); const verifyQuery = ` query { issues(filter: { team: { id: { eq: "${targetTeam.id}" } } }) { nodes { id title assignee { name } createdAt } } }`; const verificationResult = await makeLinearRequest(verifyQuery); if (verificationResult?.issues?.nodes) { const recentTickets = verificationResult.issues.nodes.filter(issue => issue.title.includes(`Demo Video for ${projectName}`) ); console.log(`ā Verification complete: ${recentTickets.length} demo video tickets found in ${targetTeam.name} team`); if (recentTickets.length > 0) { console.log('\nšÆ Successfully created tickets:'); recentTickets.forEach(ticket => { console.log(` ⢠${ticket.title} - Assigned to: ${ticket.assignee?.name || 'Unassigned'}`); }); } } else { console.log('ā ļø Could not verify tickets - but creation calls appeared successful'); } console.log('\nš Demo video ticket creation process completed!');