Note: I previously wrote about using plain PHP to query GitHub Projects V2. In this post I offer some tips for querying using Laravel.
GitHub’s new Projects are not accessible via the older REST API. Working with them programmatically involves learning some GraphQL, which can be a headache, the first time you encounter it. Here’s my approach, using Laravel.
Authentication
Get a GitHub personal access token, limiting its permissions as appropriate to your app. Then add to your .env
:
GH_TOKEN=[your token]
HTTP requests
We can standardise GitHub GraphQL queries by using an HTTP facade macro. In app/Providers/AppServiceProvider.php
, add to the boot()
method:
/**
* Make request to GitHub using the GraphQL API.
*
* $query - a GraphQL query string
* $variables - an array of GraphQL variables
*
* Call like: $data = Http::githubGraphQL($query, $variables)->throw()->json()['data'];
*/
Http::macro('githubGraphQL', function (string $query, array $variables) {
return Http::withHeaders([
'Accept' => 'application/vnd.github+json',
'Authorization' => 'Bearer ' . env('GH_TOKEN')
])->post('https://api.github.com/graphql', [
'query' => $query,
'variables' => $variables,
]);
});
GitHubProjects model
This sample app/Models/GitHubProjects.php
shows you the sort of query you can run:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Http;
class GitHubProjects extends Model
{
use HasFactory;
/**
* getFirstN - get first N projects for the GitHub organisation, ordered by
* title
*
* @param integer $count - number of projects to return
* @return array
*/
public static function getFirstN(int $count = 20): array
{
$projects = Http::githubGraphQL(
<<<EOD
query getFirstN(\$count: Int) {
organization(login: "MyOrg") {
projectsV2(first: \$count, orderBy: {field: TITLE, direction: ASC}) {
nodes {
number
id
title
}
}
}
}
EOD,
[ "count" => $count ]
)->throw()->json()['data'];
return $projects['organization']['projectsV2']['nodes'];
}
/**
* getFirstNIssues
*
* @param integer $projectNumber
* @param integer $count
* @return array
*/
public static function getFirstNIssues(int $projectNumber = 1, int $count = 20): array
{
$projectId = GitHubProjects::getIdByNumber($projectNumber);
$issues = Http::githubGraphQL(
<<<EOD
# Exclamation mark since projectId is required
query getFirstNIssues(\$projectId: ID!, \$count: Int) {
node(id: \$projectId) {
... on ProjectV2 {
items(first: \$count) {
nodes {
id
fieldValues(first: 8) {
nodes {
... on ProjectV2ItemFieldTextValue {
text
field {
... on ProjectV2FieldCommon {
name
}
}
}
... on ProjectV2ItemFieldDateValue {
date
field {
... on ProjectV2FieldCommon {
name
}
}
}
... on ProjectV2ItemFieldSingleSelectValue {
name
field {
... on ProjectV2FieldCommon {
name
}
}
}
}
}
content {
... on DraftIssue {
title
body
}
... on Issue {
title
assignees(first: 10) {
nodes {
login
}
}
}
... on PullRequest {
title
assignees(first: 10) {
nodes {
login
}
}
}
}
}
}
}
}
}
EOD,
[
"projectId" => $projectId,
"count" => $count
]
)->throw()->json()['data'];
return $issues['node']['items']['nodes'];
}
/**
* getByTitle - search for projects by title
*
* @param string $title - search for
* @return array
*/
public static function getByTitle(string $title): array
{
$projects = Http::githubGraphQL(
<<<EOD
query getByName(\$title: String!) {
organization(login: "MyOrg") {
projectsV2(
first: 20
orderBy: { field: TITLE, direction: ASC }
query: \$title
) {
nodes {
number
id
title
}
}
}
}
EOD,
[ "title" => $title ]
)->throw()->json()['data'];
return $projects['organization']['projectsV2']['nodes'];
}
/**
* getIdByNumber
*
* @param integer $number - the integer identifier of the project
* @return string - the internal GitHub project ID (e.g. PVT_kwDOBWQiz84AH53W)
*/
private static function getIdByNumber(int $number = 1)
{
$project = Http::githubGraphQL(
<<<EOD
# Exclamation mark since number is required
query getIdByNumber(\$number: Int!) {
organization(login: "MyOrg") {
projectV2(number: \$number) {
id
}
}
}
EOD,
['number' => $number]
)->throw()->json()['data'];
return $project['organization']['projectV2']['id'];
}
/**
* getProjectFields - get all the (custom) fields associated to a project
*
* @param string $projectID - GitHub's reference for the project
* @return array
*/
public static function getProjectFields(string $projectID): array
{
$fields = Http::githubGraphQL(
<<<EOD
query getProjectFields(\$node: ID!) {
node(id: \$node) {
... on ProjectV2 {
fields(first: 20) {
nodes {
... on ProjectV2Field {
id
name
}
... on ProjectV2IterationField {
id
name
configuration {
iterations {
startDate
id
}
}
}
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}
EOD,
[ "node" => $projectID ]
)->throw()->json()['data'];
return $fields['node']['fields']['nodes'];
}
}
Controller
This is a very basic sample controller method to get you started. In practice you’ll use views:
public function index()
{
/*
Search for "MyProject" in projects
Project number is $projects[0]['number'];
Project name is $projects[0]['title'];
Project ID is $projects[0]['id'];
*/
define('BR', "<br />\n");
$projects = GitHubProjects::getByTitle('MyProject');
if (isset($projects[0])) {
$projectNumber = $projects[0]['number'];
$issues = GitHubProjects::getFirstNIssues($projectNumber, 50);
echo "<h1>First 50 issues in MyProject project</h1>\n";
foreach ($issues as $issue) {
echo '<b>ID:</b> ' . $issue['id'] . BR;
echo '<b>Title:</b> ' . $issue['content']['title'] . BR;
if (isset($issue['content']['assignees']['nodes'][0])) {
echo '<b>Assignee:</b> ' . $issue['content']['assignees']['nodes'][0]['login'] . BR;
}
// Field types
foreach ($issue['fieldValues']['nodes'] as $field) {
if (isset($field['text']) && $field['field']['name'] != "Title") {
echo '<b>' . $field['field']['name'] . ':</b> ' . $field['text'] . BR;
}
if (isset($field['date'])) {
echo '<b>' . $field['field']['name'] . ':</b> ' . $field['date'] . BR;
}
if (isset($field['name'])) {
echo '<b>' . $field['field']['name'] . ':</b> ' . $field['name'] . BR;
}
}
echo BR;
}
} else {
echo "No projects found";
}
}
Useful resources
The following are invaluable for working with GitHub’s GraphQL API:
- Official reference
- GtiHub GraphQL Explorer (run live queries on your account)
- GraphQL Formatter (you can also use the “Prettify” button in the GraphQL Explorer