Skip to main content
Glama
update_project_view.ts14.3 kB
import { GitHubConfig, ToolResponse } from '../../shared/types.js'; interface UpdateProjectViewArgs { project_number?: number; project_id?: string; view_id?: string; view_name?: string; new_name?: string; description?: string; filter?: string; // GitHub search syntax filter clear_filter?: boolean; // Remove existing filter sort_field?: string; // Field name to sort by sort_direction?: 'ASC' | 'DESC'; clear_sort?: boolean; // Remove existing sort group_by_field?: string; // Field name to group by clear_grouping?: boolean; // Remove existing grouping visible_fields?: string[]; // List of field names to show layout?: 'BOARD_LAYOUT' | 'TABLE_LAYOUT' | 'ROADMAP_LAYOUT'; visibility?: 'PUBLIC' | 'PRIVATE'; } /** * Update GitHub Project v2 view configurations and layouts * Uses GraphQL mutation updateProjectV2View */ export async function updateProjectView(config: GitHubConfig, args: UpdateProjectViewArgs): Promise<ToolResponse> { const { graphqlWithAuth, owner } = config; if (!owner) { throw new Error('GITHUB_OWNER environment variable is required for project operations'); } try { let projectId = args.project_id; let viewId = args.view_id; // If project_number is provided instead of project_id, get the project ID if (!projectId && args.project_number) { const projectQuery = ` query($owner: String!, $number: Int!) { user(login: $owner) { projectV2(number: $number) { id title } } organization(login: $owner) { projectV2(number: $number) { id title } } } `; const projectResult = await graphqlWithAuth(projectQuery, { owner, number: args.project_number }); const project = projectResult.user?.projectV2 || projectResult.organization?.projectV2; if (!project) { throw new Error(`Project #${args.project_number} not found`); } projectId = project.id; } // If view_name is provided instead of view_id, find the view ID if (!viewId && args.view_name && projectId) { const viewsQuery = ` query($projectId: ID!) { node(id: $projectId) { ... on ProjectV2 { views(first: 100) { nodes { id name layout } } } } } `; const viewsResult = await graphqlWithAuth(viewsQuery, { projectId }); const views = viewsResult.node?.views?.nodes || []; const view = views.find((v: any) => v.name === args.view_name); if (!view) { throw new Error(`View "${args.view_name}" not found in project`); } viewId = view.id; } if (!viewId) { throw new Error('Either view_id or view_name (with project context) must be provided'); } // Get current view details first const currentViewQuery = ` query($viewId: ID!) { node(id: $viewId) { ... on ProjectV2View { id name layout filter sortBy { field { ... on ProjectV2Field { id name } ... on ProjectV2SingleSelectField { id name } ... on ProjectV2IterationField { id name } } direction } groupBy { field { ... on ProjectV2Field { id name } ... on ProjectV2SingleSelectField { id name } ... on ProjectV2IterationField { id name } } } visibleFields { ... on ProjectV2Field { id name dataType } ... on ProjectV2SingleSelectField { id name dataType } ... on ProjectV2IterationField { id name dataType } } } } } `; const currentViewResult = await graphqlWithAuth(currentViewQuery, { viewId }); const currentView = currentViewResult.node; if (!currentView) { throw new Error('View not found or access denied'); } // Get project fields for validation let validFields: string[] = ['Title', 'Status', 'Assignees', 'Labels', 'Repository', 'Milestone']; let fieldIdMap: { [name: string]: string } = {}; if (projectId) { try { const fieldsQuery = ` query($projectId: ID!) { node(id: $projectId) { ... on ProjectV2 { fields(first: 100) { nodes { ... on ProjectV2Field { id name } ... on ProjectV2SingleSelectField { id name } ... on ProjectV2IterationField { id name } } } } } } `; const fieldsResult = await graphqlWithAuth(fieldsQuery, { projectId }); const customFields = fieldsResult.node?.fields?.nodes || []; customFields.forEach((field: any) => { validFields.push(field.name); fieldIdMap[field.name] = field.id; }); } catch (error) { console.warn('Could not fetch custom fields, using default field list'); } } let updateOperations: string[] = []; const updateInput: any = { viewId }; // Update view name if provided if (args.new_name && args.new_name !== currentView.name) { updateInput.name = args.new_name; updateOperations.push(`Updated name from "${currentView.name}" to "${args.new_name}"`); } // Update layout if provided if (args.layout && args.layout !== currentView.layout) { updateInput.layout = args.layout; updateOperations.push(`Changed layout from ${currentView.layout} to ${args.layout}`); } // Update filter if (args.filter !== undefined) { updateInput.filter = args.filter; updateOperations.push(`Updated filter to: "${args.filter}"`); } else if (args.clear_filter) { updateInput.filter = null; updateOperations.push('Cleared filter'); } // Update sorting if (args.clear_sort) { updateInput.sortBy = []; updateOperations.push('Cleared sorting'); } else if (args.sort_field) { if (!validFields.includes(args.sort_field)) { throw new Error(`Sort field "${args.sort_field}" not found. Available fields: ${validFields.join(', ')}`); } const fieldId = fieldIdMap[args.sort_field]; if (fieldId) { updateInput.sortBy = [{ fieldId, direction: args.sort_direction || 'ASC' }]; updateOperations.push(`Updated sort to: ${args.sort_field} (${args.sort_direction || 'ASC'})`); } } // Update grouping if (args.clear_grouping) { updateInput.groupBy = []; updateOperations.push('Cleared grouping'); } else if (args.group_by_field) { if (!validFields.includes(args.group_by_field)) { throw new Error(`Group by field "${args.group_by_field}" not found. Available fields: ${validFields.join(', ')}`); } const fieldId = fieldIdMap[args.group_by_field]; if (fieldId) { updateInput.groupBy = [{ fieldId }]; updateOperations.push(`Updated grouping to: ${args.group_by_field}`); } } // Update visible fields if (args.visible_fields && args.visible_fields.length > 0) { const invalidFields = args.visible_fields.filter(field => !validFields.includes(field)); if (invalidFields.length > 0) { throw new Error(`Invalid fields: ${invalidFields.join(', ')}. Available fields: ${validFields.join(', ')}`); } const visibleFieldIds = args.visible_fields .map(fieldName => fieldIdMap[fieldName]) .filter(id => id); // Filter out undefined IDs if (visibleFieldIds.length > 0) { updateInput.visibleFields = visibleFieldIds; updateOperations.push(`Updated visible fields: ${args.visible_fields.join(', ')}`); } } if (updateOperations.length === 0) { return { content: [{ type: "text", text: `ℹ️ **No updates made to view "${currentView.name}"**\n\n**Reason:** No valid update parameters provided.\n\n**Available updates:**\n• new_name - Change view name\n• layout - Change view layout (BOARD_LAYOUT, TABLE_LAYOUT, ROADMAP_LAYOUT)\n• filter - Set search filter\n• clear_filter - Remove existing filter\n• sort_field & sort_direction - Set sorting\n• clear_sort - Remove sorting\n• group_by_field - Set grouping\n• clear_grouping - Remove grouping\n• visible_fields - Configure visible fields` }] }; } // Execute the update const updateViewMutation = ` mutation($input: UpdateProjectV2ViewInput!) { updateProjectV2View(input: $input) { projectV2View { id name layout filter updatedAt sortBy { field { ... on ProjectV2Field { name } ... on ProjectV2SingleSelectField { name } ... on ProjectV2IterationField { name } } direction } groupBy { field { ... on ProjectV2Field { name } ... on ProjectV2SingleSelectField { name } ... on ProjectV2IterationField { name } } } visibleFields { ... on ProjectV2Field { name } ... on ProjectV2SingleSelectField { name } ... on ProjectV2IterationField { name } } } } } `; const result = await graphqlWithAuth(updateViewMutation, { input: updateInput }); const updatedView = result.updateProjectV2View.projectV2View; let response = `✅ **Project view updated successfully!**\n\n`; response += `**View:** ${updatedView.name}\n`; response += `**ID:** ${viewId}\n`; response += `**Layout:** ${updatedView.layout.replace('_LAYOUT', '').toLowerCase()}\n`; response += `**Updated:** ${new Date(updatedView.updatedAt).toLocaleDateString()}\n\n`; response += `**Changes made:**\n`; updateOperations.forEach((operation, index) => { response += ` ${index + 1}. ${operation}\n`; }); // Show current view configuration response += `\n**Current Configuration:**\n`; if (updatedView.filter) { response += `• **Filter:** \`${updatedView.filter}\`\n`; } if (updatedView.sortBy && updatedView.sortBy.length > 0) { const sorts = updatedView.sortBy.map((sort: any) => `${sort.field.name} (${sort.direction})` ).join(', '); response += `• **Sort:** ${sorts}\n`; } if (updatedView.groupBy && updatedView.groupBy.length > 0) { const groups = updatedView.groupBy.map((group: any) => group.field.name).join(', '); response += `• **Group by:** ${groups}\n`; } if (updatedView.visibleFields && updatedView.visibleFields.length > 0) { const fieldNames = updatedView.visibleFields.map((field: any) => field.name).join(', '); response += `• **Visible fields:** ${fieldNames}\n`; } // Add layout-specific tips const layout = updatedView.layout; if (layout === 'BOARD_LAYOUT') { response += `\n**💡 Board View Tips:**\n`; response += `• Group by Status or custom select fields for best results\n`; response += `• Use filters to focus on specific work streams\n`; response += `• Configure swimlanes for better organization\n`; } else if (layout === 'TABLE_LAYOUT') { response += `\n**💡 Table View Tips:**\n`; response += `• Show relevant fields for spreadsheet-style editing\n`; response += `• Use sorting for data analysis\n`; response += `• Enable bulk editing for efficiency\n`; } else if (layout === 'ROADMAP_LAYOUT') { response += `\n**💡 Roadmap View Tips:**\n`; response += `• Set date fields for timeline visualization\n`; response += `• Group by milestones or iterations\n`; response += `• Use filters to show specific time periods\n`; } response += `\n**Next Steps:**\n`; response += `• Use 'list_project_views' to see all project views\n`; response += `• Further customize the view in the GitHub project interface\n`; response += `• Share this view with team members\n`; response += `• Set up automation based on view criteria`; return { content: [{ type: "text", text: response }] }; } catch (error: any) { if (error.message?.includes('insufficient permission')) { throw new Error('Insufficient permissions to update project views. Ensure your GitHub token has "project" scope.'); } if (error.message?.includes('View is system')) { throw new Error('Cannot modify system views. Create a custom view instead.'); } if (error.message?.includes('already exists')) { throw new Error('A view with that name already exists in this project.'); } throw new Error(`Failed to update project view: ${error.message}`); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Faresabdelghany/github-project-manager-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server