Repository: hub4j/github-api
Pull request #1502 (2022-08-10 23:19:59)

Add ProjectsV2Item event payload
Frankly, this thing is underdocumented and IMHO not very usable but I
suppose it will improve over time.
Someone asked for it in my Quarkus GitHub App project so I thought I
might as well implement it.

@bitwiseman I think you relaxed the rules about documenting the simple getters but I'll add javadoc if required.


Patch:
diff --git a/src/main/java/org/kohsuke/github/GHEventPayload.java b/src/main/java/org/kohsuke/github/GHEventPayload.java
index e7af6e93d..371ed92f1 100644
--- a/src/main/java/org/kohsuke/github/GHEventPayload.java
+++ b/src/main/java/org/kohsuke/github/GHEventPayload.java
@@ -1630,4 +1630,26 @@ public abstract class GHEventPayload extends GitHubInteractiveObject {
             return GitHubClient.parseDate(starredAt);
         }
     }
+
+    /**
+     * A project v2 item was archived, converted, created, edited, restored, deleted, or reordered.
+     *
+     * @see <a href=
+     *      "https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#projects_v2_item">star
+     *      event</a>
+     */
+    public static class ProjectsV2Item extends GHEventPayload {
+
+        private GHProjectsV2Item projectsV2Item;
+        private GHProjectsV2ItemChanges changes;
+
+        @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected")
+        public GHProjectsV2Item getProjectsV2Item() {
+            return projectsV2Item;
+        }
+
+        public GHProjectsV2ItemChanges getChanges() {
+            return changes;
+        }
+    }
 }
diff --git a/src/main/java/org/kohsuke/github/GHProjectsV2Item.java b/src/main/java/org/kohsuke/github/GHProjectsV2Item.java
new file mode 100644
index 000000000..2841858f5
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHProjectsV2Item.java
@@ -0,0 +1,59 @@
+package org.kohsuke.github;
+
+import org.kohsuke.github.internal.EnumUtils;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Date;
+
+/**
+ * A Projects V2 item in the organization.
+ * <p>
+ * Projects V2 are not attached to a repository but to an organization, even if it is possible to create shortcuts at
+ * the repository level.
+ * <p>
+ * This event exposes the GraphQL object (more or less - the ids are handled differently for instance) directly. The new
+ * Projects V2 API is only available through GraphQL so for now you cannot execute any actions on this object.
+ *
+ * @author Guillaume Smet
+ * @see <a href=
+ *      "https://docs.github.com/en/issues/planning-and-tracking-with-projects/automating-your-project/using-the-api-to-manage-projects">The
+ *      GraphQL API for Projects V2</a>
+ */
+public class GHProjectsV2Item extends GHObject {
+
+    private String projectNodeId;
+    private String contentNodeId;
+    private String contentType;
+
+    private GHUser creator;
+    private String archivedAt;
+
+    public String getProjectNodeId() {
+        return projectNodeId;
+    }
+
+    public String getContentNodeId() {
+        return contentNodeId;
+    }
+
+    public ContentType getContentType() {
+        return EnumUtils.getEnumOrDefault(ContentType.class, contentType, ContentType.UNKNOWN);
+    }
+
+    public GHUser getCreator() throws IOException {
+        return root().intern(creator);
+    }
+
+    public Date getArchivedAt() {
+        return GitHubClient.parseDate(archivedAt);
+    }
+
+    public URL getHtmlUrl() {
+        throw new IllegalStateException(getClass().getName() + " does not offer a HTML URL.");
+    }
+
+    public enum ContentType {
+        ISSUE, DRAFTISSUE, PULLREQUEST, UNKNOWN;
+    }
+}
diff --git a/src/main/java/org/kohsuke/github/GHProjectsV2ItemChanges.java b/src/main/java/org/kohsuke/github/GHProjectsV2ItemChanges.java
new file mode 100644
index 000000000..6c74e8d70
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHProjectsV2ItemChanges.java
@@ -0,0 +1,80 @@
+package org.kohsuke.github;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.kohsuke.github.internal.EnumUtils;
+
+import java.util.Date;
+
+/**
+ * An object to track changes in projects_v2_item payloads.
+ * <p>
+ * Note that this is best effort only as nothing is documented in the GitHub documentation.
+ */
+@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD" }, justification = "JSON API")
+public class GHProjectsV2ItemChanges {
+
+    private FieldValue fieldValue;
+
+    private FromToDate archivedAt;
+
+    private FromTo previousProjectsV2ItemNodeId;
+
+    public FieldValue getFieldValue() {
+        return fieldValue;
+    }
+
+    public FromToDate getArchivedAt() {
+        return archivedAt;
+    }
+
+    public FromTo getPreviousProjectsV2ItemNodeId() {
+        return previousProjectsV2ItemNodeId;
+    }
+
+    public static class FieldValue {
+
+        private String fieldNodeId;
+        private String fieldType;
+
+        public String getFieldNodeId() {
+            return fieldNodeId;
+        }
+
+        public FieldType getFieldType() {
+            return EnumUtils.getEnumOrDefault(FieldType.class, fieldType, FieldType.UNKNOWN);
+        }
+    }
+
+    public static class FromTo {
+
+        private String from;
+        private String to;
+
+        public String getFrom() {
+            return from;
+        }
+
+        public String getTo() {
+            return to;
+        }
+    }
+
+    public static class FromToDate {
+
+        private String from;
+        private String to;
+
+        public Date getFrom() {
+            return GitHubClient.parseDate(from);
+        }
+
+        public Date getTo() {
+            return GitHubClient.parseDate(to);
+        }
+    }
+
+    public enum FieldType {
+
+        TEXT, NUMBER, DATE, SINGLE_SELECT, ITERATION, UNKNOWN;
+    }
+}


Test patch:
diff --git a/src/test/java/org/kohsuke/github/GHEventPayloadTest.java b/src/test/java/org/kohsuke/github/GHEventPayloadTest.java
index 0c07d1efe..18c06db28 100644
--- a/src/test/java/org/kohsuke/github/GHEventPayloadTest.java
+++ b/src/test/java/org/kohsuke/github/GHEventPayloadTest.java
@@ -4,6 +4,8 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.kohsuke.github.GHCheckRun.Conclusion;
 import org.kohsuke.github.GHCheckRun.Status;
+import org.kohsuke.github.GHProjectsV2Item.ContentType;
+import org.kohsuke.github.GHProjectsV2ItemChanges.FieldType;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
@@ -1152,4 +1154,102 @@ public class GHEventPayloadTest extends AbstractGitHubWireMockTest {
         assertThat(starPayload.getSender().getLogin(), is("gsmet"));
         assertThat(starPayload.getStarredAt().getTime(), is(1654017876000L));
     }
+
+    @Test
+    public void projectsv2item_created() throws Exception {
+        final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline()
+                .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class);
+
+        assertThat(projectsV2ItemPayload.getAction(), is("created"));
+
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getNodeId(), is("PVTI_lADOBNft-M4AEjBWzgB7VzY"));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getProjectNodeId(), is("PVT_kwDOBNft-M4AEjBW"));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getContentNodeId(), is("I_kwDOFOkjw85Ozz26"));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getContentType(), is(ContentType.ISSUE));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreator().getLogin(), is("gsmet"));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreator().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk="));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532028000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1659532028000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue()));
+
+        assertThat(projectsV2ItemPayload.getOrganization().getLogin(), is("gsmet-bot-playground"));
+        assertThat(projectsV2ItemPayload.getOrganization().getId(), is(81260024L));
+        assertThat(projectsV2ItemPayload.getOrganization().getNodeId(), is("MDEyOk9yZ2FuaXphdGlvbjgxMjYwMDI0"));
+
+        assertThat(projectsV2ItemPayload.getSender().getLogin(), is("gsmet"));
+        assertThat(projectsV2ItemPayload.getSender().getId(), is(1279749L));
+        assertThat(projectsV2ItemPayload.getSender().getNodeId(), is("MDQ6VXNlcjEyNzk3NDk="));
+
+        assertThat(projectsV2ItemPayload.getInstallation().getId(), is(16779846L));
+        assertThat(projectsV2ItemPayload.getInstallation().getNodeId(),
+                is("MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY3Nzk4NDY="));
+    }
+
+    @Test
+    public void projectsv2item_edited() throws Exception {
+        final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline()
+                .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class);
+
+        assertThat(projectsV2ItemPayload.getAction(), is("edited"));
+
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532028000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1659532033000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue()));
+
+        assertThat(projectsV2ItemPayload.getChanges().getFieldValue().getFieldNodeId(),
+                is("PVTF_lADOBNft-M4AEjBWzgCnp5Q"));
+        assertThat(projectsV2ItemPayload.getChanges().getFieldValue().getFieldType(), is(FieldType.SINGLE_SELECT));
+    }
+
+    @Test
+    public void projectsv2item_archived() throws Exception {
+        final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline()
+                .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class);
+
+        assertThat(projectsV2ItemPayload.getAction(), is("archived"));
+
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083794L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532431000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1660086629000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt().getTime(), is(1660086629000L));
+
+        assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getFrom(), is(nullValue()));
+        assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getTo().getTime(), is(1660086629000L));
+    }
+
+    @Test
+    public void projectsv2item_restored() throws Exception {
+        final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline()
+                .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class);
+
+        assertThat(projectsV2ItemPayload.getAction(), is("restored"));
+
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083254L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532028000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1659532419000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue()));
+
+        assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getFrom().getTime(), is(1659532142000L));
+        assertThat(projectsV2ItemPayload.getChanges().getArchivedAt().getTo(), is(nullValue()));
+    }
+
+    @Test
+    public void projectsv2item_reordered() throws Exception {
+        final GHEventPayload.ProjectsV2Item projectsV2ItemPayload = GitHub.offline()
+                .parseEventPayload(payload.asReader(), GHEventPayload.ProjectsV2Item.class);
+
+        assertThat(projectsV2ItemPayload.getAction(), is("reordered"));
+
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getId(), is(8083794L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getCreatedAt().getTime(), is(1659532431000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getUpdatedAt().getTime(), is(1659532439000L));
+        assertThat(projectsV2ItemPayload.getProjectsV2Item().getArchivedAt(), is(nullValue()));
+
+        assertThat(projectsV2ItemPayload.getChanges().getPreviousProjectsV2ItemNodeId().getFrom(),
+                is("PVTI_lADOBNft-M4AEjBWzgB7VzY"));
+        assertThat(projectsV2ItemPayload.getChanges().getPreviousProjectsV2ItemNodeId().getTo(),
+                is("PVTI_lADOBNft-M4AEjBWzgB7VzY"));
+    }
 }
diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_archived.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_archived.json
new file mode 100644
index 000000000..d43d79bfa
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_archived.json
@@ -0,0 +1,77 @@
+{
+    "action": "archived",
+    "projects_v2_item": {
+        "id": 8083794,
+        "node_id": "PVTI_lADOBNft-M4AEjBWzgB7WVI",
+        "project_node_id": "PVT_kwDOBNft-M4AEjBW",
+        "content_node_id": "I_kwDOFOkjw85OzN4w",
+        "content_type": "Issue",
+        "creator": {
+            "login": "gsmet",
+            "id": 1279749,
+            "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+            "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+            "gravatar_id": "",
+            "url": "https://api.github.com/users/gsmet",
+            "html_url": "https://github.com/gsmet",
+            "followers_url": "https://api.github.com/users/gsmet/followers",
+            "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+            "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+            "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+            "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+            "organizations_url": "https://api.github.com/users/gsmet/orgs",
+            "repos_url": "https://api.github.com/users/gsmet/repos",
+            "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+            "received_events_url": "https://api.github.com/users/gsmet/received_events",
+            "type": "User",
+            "site_admin": false
+        },
+        "created_at": "2022-08-03T13:13:51Z",
+        "updated_at": "2022-08-09T23:10:29Z",
+        "archived_at": "2022-08-09T23:10:29Z"
+    },
+    "changes": {
+        "archived_at": {
+            "from": null,
+            "to": "2022-08-10T01:10:29+02:00"
+        }
+    },
+    "organization": {
+        "login": "gsmet-bot-playground",
+        "id": 81260024,
+        "node_id": "MDEyOk9yZ2FuaXphdGlvbjgxMjYwMDI0",
+        "url": "https://api.github.com/orgs/gsmet-bot-playground",
+        "repos_url": "https://api.github.com/orgs/gsmet-bot-playground/repos",
+        "events_url": "https://api.github.com/orgs/gsmet-bot-playground/events",
+        "hooks_url": "https://api.github.com/orgs/gsmet-bot-playground/hooks",
+        "issues_url": "https://api.github.com/orgs/gsmet-bot-playground/issues",
+        "members_url": "https://api.github.com/orgs/gsmet-bot-playground/members{/member}",
+        "public_members_url": "https://api.github.com/orgs/gsmet-bot-playground/public_members{/member}",
+        "avatar_url": "https://avatars.githubusercontent.com/u/81260024?v=4",
+        "description": null
+    },
+    "sender": {
+        "login": "gsmet",
+        "id": 1279749,
+        "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/gsmet",
+        "html_url": "https://github.com/gsmet",
+        "followers_url": "https://api.github.com/users/gsmet/followers",
+        "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+        "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+        "organizations_url": "https://api.github.com/users/gsmet/orgs",
+        "repos_url": "https://api.github.com/users/gsmet/repos",
+        "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/gsmet/received_events",
+        "type": "User",
+        "site_admin": false
+    },
+    "installation": {
+        "id": 16779846,
+        "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY3Nzk4NDY="
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_created.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_created.json
new file mode 100644
index 000000000..be27de4a5
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_created.json
@@ -0,0 +1,71 @@
+{
+    "action": "created",
+    "projects_v2_item": {
+        "id": 8083254,
+        "node_id": "PVTI_lADOBNft-M4AEjBWzgB7VzY",
+        "project_node_id": "PVT_kwDOBNft-M4AEjBW",
+        "content_node_id": "I_kwDOFOkjw85Ozz26",
+        "content_type": "Issue",
+        "creator": {
+            "login": "gsmet",
+            "id": 1279749,
+            "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+            "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+            "gravatar_id": "",
+            "url": "https://api.github.com/users/gsmet",
+            "html_url": "https://github.com/gsmet",
+            "followers_url": "https://api.github.com/users/gsmet/followers",
+            "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+            "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+            "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+            "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+            "organizations_url": "https://api.github.com/users/gsmet/orgs",
+            "repos_url": "https://api.github.com/users/gsmet/repos",
+            "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+            "received_events_url": "https://api.github.com/users/gsmet/received_events",
+            "type": "User",
+            "site_admin": false
+        },
+        "created_at": "2022-08-03T13:07:08Z",
+        "updated_at": "2022-08-03T13:07:08Z",
+        "archived_at": null
+    },
+    "organization": {
+        "login": "gsmet-bot-playground",
+        "id": 81260024,
+        "node_id": "MDEyOk9yZ2FuaXphdGlvbjgxMjYwMDI0",
+        "url": "https://api.github.com/orgs/gsmet-bot-playground",
+        "repos_url": "https://api.github.com/orgs/gsmet-bot-playground/repos",
+        "events_url": "https://api.github.com/orgs/gsmet-bot-playground/events",
+        "hooks_url": "https://api.github.com/orgs/gsmet-bot-playground/hooks",
+        "issues_url": "https://api.github.com/orgs/gsmet-bot-playground/issues",
+        "members_url": "https://api.github.com/orgs/gsmet-bot-playground/members{/member}",
+        "public_members_url": "https://api.github.com/orgs/gsmet-bot-playground/public_members{/member}",
+        "avatar_url": "https://avatars.githubusercontent.com/u/81260024?v=4",
+        "description": null
+    },
+    "sender": {
+        "login": "gsmet",
+        "id": 1279749,
+        "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/gsmet",
+        "html_url": "https://github.com/gsmet",
+        "followers_url": "https://api.github.com/users/gsmet/followers",
+        "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+        "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+        "organizations_url": "https://api.github.com/users/gsmet/orgs",
+        "repos_url": "https://api.github.com/users/gsmet/repos",
+        "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/gsmet/received_events",
+        "type": "User",
+        "site_admin": false
+    },
+    "installation": {
+        "id": 16779846,
+        "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY3Nzk4NDY="
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_edited.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_edited.json
new file mode 100644
index 000000000..1452de2af
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_edited.json
@@ -0,0 +1,77 @@
+{
+    "action": "edited",
+    "projects_v2_item": {
+        "id": 8083254,
+        "node_id": "PVTI_lADOBNft-M4AEjBWzgB7VzY",
+        "project_node_id": "PVT_kwDOBNft-M4AEjBW",
+        "content_node_id": "I_kwDOFOkjw85Ozz26",
+        "content_type": "Issue",
+        "creator": {
+            "login": "gsmet",
+            "id": 1279749,
+            "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+            "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+            "gravatar_id": "",
+            "url": "https://api.github.com/users/gsmet",
+            "html_url": "https://github.com/gsmet",
+            "followers_url": "https://api.github.com/users/gsmet/followers",
+            "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+            "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+            "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+            "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+            "organizations_url": "https://api.github.com/users/gsmet/orgs",
+            "repos_url": "https://api.github.com/users/gsmet/repos",
+            "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+            "received_events_url": "https://api.github.com/users/gsmet/received_events",
+            "type": "User",
+            "site_admin": false
+        },
+        "created_at": "2022-08-03T13:07:08Z",
+        "updated_at": "2022-08-03T13:07:13Z",
+        "archived_at": null
+    },
+    "changes": {
+        "field_value": {
+            "field_node_id": "PVTF_lADOBNft-M4AEjBWzgCnp5Q",
+            "field_type": "single_select"
+        }
+    },
+    "organization": {
+        "login": "gsmet-bot-playground",
+        "id": 81260024,
+        "node_id": "MDEyOk9yZ2FuaXphdGlvbjgxMjYwMDI0",
+        "url": "https://api.github.com/orgs/gsmet-bot-playground",
+        "repos_url": "https://api.github.com/orgs/gsmet-bot-playground/repos",
+        "events_url": "https://api.github.com/orgs/gsmet-bot-playground/events",
+        "hooks_url": "https://api.github.com/orgs/gsmet-bot-playground/hooks",
+        "issues_url": "https://api.github.com/orgs/gsmet-bot-playground/issues",
+        "members_url": "https://api.github.com/orgs/gsmet-bot-playground/members{/member}",
+        "public_members_url": "https://api.github.com/orgs/gsmet-bot-playground/public_members{/member}",
+        "avatar_url": "https://avatars.githubusercontent.com/u/81260024?v=4",
+        "description": null
+    },
+    "sender": {
+        "login": "gsmet",
+        "id": 1279749,
+        "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/gsmet",
+        "html_url": "https://github.com/gsmet",
+        "followers_url": "https://api.github.com/users/gsmet/followers",
+        "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+        "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+        "organizations_url": "https://api.github.com/users/gsmet/orgs",
+        "repos_url": "https://api.github.com/users/gsmet/repos",
+        "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/gsmet/received_events",
+        "type": "User",
+        "site_admin": false
+    },
+    "installation": {
+        "id": 16779846,
+        "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY3Nzk4NDY="
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_reordered.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_reordered.json
new file mode 100644
index 000000000..54d100e86
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_reordered.json
@@ -0,0 +1,77 @@
+{
+    "action": "reordered",
+    "projects_v2_item": {
+        "id": 8083794,
+        "node_id": "PVTI_lADOBNft-M4AEjBWzgB7WVI",
+        "project_node_id": "PVT_kwDOBNft-M4AEjBW",
+        "content_node_id": "I_kwDOFOkjw85OzN4w",
+        "content_type": "Issue",
+        "creator": {
+            "login": "gsmet",
+            "id": 1279749,
+            "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+            "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+            "gravatar_id": "",
+            "url": "https://api.github.com/users/gsmet",
+            "html_url": "https://github.com/gsmet",
+            "followers_url": "https://api.github.com/users/gsmet/followers",
+            "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+            "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+            "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+            "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+            "organizations_url": "https://api.github.com/users/gsmet/orgs",
+            "repos_url": "https://api.github.com/users/gsmet/repos",
+            "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+            "received_events_url": "https://api.github.com/users/gsmet/received_events",
+            "type": "User",
+            "site_admin": false
+        },
+        "created_at": "2022-08-03T13:13:51Z",
+        "updated_at": "2022-08-03T13:13:59Z",
+        "archived_at": null
+    },
+    "changes": {
+        "previous_projects_v2_item_node_id": {
+            "from": "PVTI_lADOBNft-M4AEjBWzgB7VzY",
+            "to": "PVTI_lADOBNft-M4AEjBWzgB7VzY"
+        }
+    },
+    "organization": {
+        "login": "gsmet-bot-playground",
+        "id": 81260024,
+        "node_id": "MDEyOk9yZ2FuaXphdGlvbjgxMjYwMDI0",
+        "url": "https://api.github.com/orgs/gsmet-bot-playground",
+        "repos_url": "https://api.github.com/orgs/gsmet-bot-playground/repos",
+        "events_url": "https://api.github.com/orgs/gsmet-bot-playground/events",
+        "hooks_url": "https://api.github.com/orgs/gsmet-bot-playground/hooks",
+        "issues_url": "https://api.github.com/orgs/gsmet-bot-playground/issues",
+        "members_url": "https://api.github.com/orgs/gsmet-bot-playground/members{/member}",
+        "public_members_url": "https://api.github.com/orgs/gsmet-bot-playground/public_members{/member}",
+        "avatar_url": "https://avatars.githubusercontent.com/u/81260024?v=4",
+        "description": null
+    },
+    "sender": {
+        "login": "gsmet",
+        "id": 1279749,
+        "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/gsmet",
+        "html_url": "https://github.com/gsmet",
+        "followers_url": "https://api.github.com/users/gsmet/followers",
+        "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+        "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+        "organizations_url": "https://api.github.com/users/gsmet/orgs",
+        "repos_url": "https://api.github.com/users/gsmet/repos",
+        "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/gsmet/received_events",
+        "type": "User",
+        "site_admin": false
+    },
+    "installation": {
+        "id": 16779846,
+        "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY3Nzk4NDY="
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_restored.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_restored.json
new file mode 100644
index 000000000..cce852514
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/projectsv2item_restored.json
@@ -0,0 +1,77 @@
+{
+    "action": "restored",
+    "projects_v2_item": {
+        "id": 8083254,
+        "node_id": "PVTI_lADOBNft-M4AEjBWzgB7VzY",
+        "project_node_id": "PVT_kwDOBNft-M4AEjBW",
+        "content_node_id": "I_kwDOFOkjw85Ozz26",
+        "content_type": "Issue",
+        "creator": {
+            "login": "gsmet",
+            "id": 1279749,
+            "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+            "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+            "gravatar_id": "",
+            "url": "https://api.github.com/users/gsmet",
+            "html_url": "https://github.com/gsmet",
+            "followers_url": "https://api.github.com/users/gsmet/followers",
+            "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+            "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+            "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+            "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+            "organizations_url": "https://api.github.com/users/gsmet/orgs",
+            "repos_url": "https://api.github.com/users/gsmet/repos",
+            "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+            "received_events_url": "https://api.github.com/users/gsmet/received_events",
+            "type": "User",
+            "site_admin": false
+        },
+        "created_at": "2022-08-03T13:07:08Z",
+        "updated_at": "2022-08-03T13:13:39Z",
+        "archived_at": null
+    },
+    "changes": {
+        "archived_at": {
+            "from": "2022-08-03T15:09:02+02:00",
+            "to": null
+        }
+    },
+    "organization": {
+        "login": "gsmet-bot-playground",
+        "id": 81260024,
+        "node_id": "MDEyOk9yZ2FuaXphdGlvbjgxMjYwMDI0",
+        "url": "https://api.github.com/orgs/gsmet-bot-playground",
+        "repos_url": "https://api.github.com/orgs/gsmet-bot-playground/repos",
+        "events_url": "https://api.github.com/orgs/gsmet-bot-playground/events",
+        "hooks_url": "https://api.github.com/orgs/gsmet-bot-playground/hooks",
+        "issues_url": "https://api.github.com/orgs/gsmet-bot-playground/issues",
+        "members_url": "https://api.github.com/orgs/gsmet-bot-playground/members{/member}",
+        "public_members_url": "https://api.github.com/orgs/gsmet-bot-playground/public_members{/member}",
+        "avatar_url": "https://avatars.githubusercontent.com/u/81260024?v=4",
+        "description": null
+    },
+    "sender": {
+        "login": "gsmet",
+        "id": 1279749,
+        "node_id": "MDQ6VXNlcjEyNzk3NDk=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/gsmet",
+        "html_url": "https://github.com/gsmet",
+        "followers_url": "https://api.github.com/users/gsmet/followers",
+        "following_url": "https://api.github.com/users/gsmet/following{/other_user}",
+        "gists_url": "https://api.github.com/users/gsmet/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/gsmet/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/gsmet/subscriptions",
+        "organizations_url": "https://api.github.com/users/gsmet/orgs",
+        "repos_url": "https://api.github.com/users/gsmet/repos",
+        "events_url": "https://api.github.com/users/gsmet/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/gsmet/received_events",
+        "type": "User",
+        "site_admin": false
+    },
+    "installation": {
+        "id": 16779846,
+        "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTY3Nzk4NDY="
+    }
+}
\ No newline at end of file