diff --git a/README.md b/README.md
index 4612c99a..a4e8501c 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,7 @@ A project always belongs to a user or organization account and has a number. For
`fields` is map of internal field names to the project's column labels. The comparison is case-insensitive. `"Priority"` will match both a field with the label `"Priority"` and one with the label `"priority"`. An error will be thrown if a project field isn't found, unless the field is set to `optional: true`.
```js
-const project = new GitHubProject({
+const options = {
owner: "my-org",
number: 1,
token: "ghp_s3cR3t",
@@ -55,7 +55,16 @@ const project = new GitHubProject({
dueAt: "Due",
lastUpdate: { name: "Last Update", optional: true },
},
-});
+};
+
+const project = new GitHubProject(options);
+
+// Alternatively, you can call the factory method to get a project instance
+// const project = await GithubProject.getInstance(options)
+
+// get project data
+const projectData = await project.get();
+console.log(projectData.description);
// log out all items
const items = await project.items.list();
@@ -98,6 +107,14 @@ if (item) {
const project = new GitHubProject(options);
```
+### Factory method
+
+The factory method is useful when you want immediate access to the project's data, for example to get the project's title. Will throw an error if the project doesn't exist.
+
+```js
+const project = GitHubProject.getInstance(options);
+```
+
@@ -236,6 +253,14 @@ function (fieldOptionValue, userValue) {
+### `project.getProperties()`
+
+```js
+const projectData = await project.getProperties();
+```
+
+Returns project level data `url`, `title`, `description` and `databaseId`
+
### `project.items.list()`
```js
diff --git a/api/lib/get-state-with-project-fields.js b/api/lib/get-state-with-project-fields.js
index 5a059062..6b87579a 100644
--- a/api/lib/get-state-with-project-fields.js
+++ b/api/lib/get-state-with-project-fields.js
@@ -17,20 +17,31 @@ export async function getStateWithProjectFields(project, state) {
return state;
}
- const {
- userOrOrganization: { projectV2 },
- } = await project.octokit.graphql(getProjectCoreDataQuery, {
+ const response = await project.octokit.graphql(getProjectCoreDataQuery, {
owner: project.owner,
number: project.number,
});
+ if (
+ response.userOrOrganization === null ||
+ response.userOrOrganization.projectV2 === null
+ ) {
+ throw new Error(
+ `[github-project] Cannot find project with number: ${project.number} and owner: ${project.owner}`
+ );
+ }
+
+ const {
+ userOrOrganization: { projectV2 },
+ } = response;
+
const fields = projectFieldsNodesToFieldsMap(
state,
project,
projectV2.fields.nodes
);
- const { id, title, url } = projectV2;
+ const { id, title, url, databaseId } = projectV2;
// mutate current state and return it
// @ts-expect-error - TS can't handle Object.assign
@@ -39,6 +50,7 @@ export async function getStateWithProjectFields(project, state) {
id,
title,
url,
+ databaseId,
fields,
});
}
diff --git a/api/lib/project-node-to-properties.js b/api/lib/project-node-to-properties.js
new file mode 100644
index 00000000..4bf95816
--- /dev/null
+++ b/api/lib/project-node-to-properties.js
@@ -0,0 +1,18 @@
+// @ts-check
+
+/**
+ * Takes a GraphQL `projectItem` node and returns a `ProjectItem` object
+ * in the format we return it from the GitHubProject API.
+ *
+ * @param {import("../..").GitHubProjectStateWithFields} state
+ * *
+ * @returns {import("../..").GitHubProjectProperties}
+ */
+export function projectNodeToProperties(state) {
+ return {
+ databaseId: state.databaseId,
+ id: state.id,
+ title: state.title,
+ url: state.url,
+ };
+}
diff --git a/api/lib/queries.js b/api/lib/queries.js
index 68d1c94c..91d62eec 100644
--- a/api/lib/queries.js
+++ b/api/lib/queries.js
@@ -35,6 +35,7 @@ const queryProjectNodes = `
id
title
url
+ databaseId
fields(first: 50) {
nodes {
... on ProjectV2FieldCommon {
diff --git a/api/project.getProperties.js b/api/project.getProperties.js
new file mode 100644
index 00000000..d97c8e98
--- /dev/null
+++ b/api/project.getProperties.js
@@ -0,0 +1,18 @@
+// @ts-check
+
+import { projectNodeToProperties } from "./lib/project-node-to-properties.js";
+import { getStateWithProjectFields } from "./lib/get-state-with-project-fields.js";
+
+/**
+ * Attempts to get a project's properties based on the owner and number.
+ *
+ * @param {import("..").default} project
+ * @param {import("..").GitHubProjectState} state
+ *
+ * @returns {Promise}
+ */
+export async function getProperties(project, state) {
+ const stateWithFields = await getStateWithProjectFields(project, state);
+
+ return projectNodeToProperties(stateWithFields);
+}
diff --git a/index.d.ts b/index.d.ts
index 460bbb14..7c9bd56b 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -19,6 +19,13 @@ export type DraftItemContent = {
assigneeIds?: string[];
};
+export type GitHubProjectProperties = {
+ databaseId: string;
+ id: string;
+ title: string;
+ url: string;
+};
+
export default class GitHubProject<
TCustomFields extends Record = {},
TFields extends BUILT_IN_FIELDS = TCustomFields & BUILT_IN_FIELDS,
@@ -40,6 +47,23 @@ export default class GitHubProject<
/** Map of fields */
get fields(): TFields;
+ /** Project properties */
+ getProperties(): Promise;
+
+ static getInstance<
+ TCustomFields extends Record = {},
+ TFields extends BUILT_IN_FIELDS = TCustomFields & BUILT_IN_FIELDS,
+ TItemFields extends {} = Record<
+ Exclude>,
+ string | null
+ > &
+ Partial<
+ Record, string | null>
+ >
+ >(
+ options: GitHubProjectOptions
+ ): Promise>;
+
constructor(options: GitHubProjectOptions);
items: {
@@ -274,4 +298,5 @@ export type GitHubProjectStateWithFields = GitHubProjectStateCommon & {
title: string;
url: string;
fields: ProjectFieldMap;
+ databaseId: string;
};
diff --git a/index.js b/index.js
index 04ad64eb..8486b1dd 100644
--- a/index.js
+++ b/index.js
@@ -17,6 +17,7 @@ import { archiveItemByContentRepositoryAndNumber } from "./api/items.archive-by-
import { removeItem } from "./api/items.remove.js";
import { removeItemByContentId } from "./api/items.remove-by-content-id.js";
import { removeItemByContentRepositoryAndNumber } from "./api/items.remove-by-content-repository-and-name.js";
+import { getProperties } from "./api/project.getProperties.js";
import { defaultMatchFunction } from "./api/lib/default-match-function.js";
@@ -72,12 +73,31 @@ export default class GitHubProject {
removeByContentRepositoryAndNumber:
removeItemByContentRepositoryAndNumber.bind(null, this, state),
};
+
Object.defineProperties(this, {
owner: { get: () => owner },
number: { get: () => number },
fields: { get: () => ({ ...BUILT_IN_FIELDS, ...fields }) },
octokit: { get: () => octokit },
items: { get: () => itemsApi },
+ getProperties: { get: () => getProperties.bind(null, this, state) },
});
}
+
+ /**
+ * Returns a GithubProject instance and calls `getProperties()` to preload
+ * project level properties.
+ *
+ * @param {import(".").GitHubProjectOptions} options
+ *
+ * @return {Promise}
+ */
+ static async getInstance(options) {
+ const project = /** @type {import(".").default} */ (
+ new GitHubProject(options)
+ );
+ await project.getProperties();
+
+ return project;
+ }
}
diff --git a/index.test-d.ts b/index.test-d.ts
index 535c4f5e..043dc930 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -607,3 +607,37 @@ export async function matchFieldNameOption() {
},
});
}
+
+export async function testGetInstance() {
+ const project = await GitHubProject.getInstance({
+ owner: "owner",
+ number: 1,
+ token: "gpg_secret123",
+ fields: {
+ myField: "My Field",
+ },
+ });
+
+ expectType>(project);
+ expectType(project.fields.myField);
+
+ const items = await project.items.list();
+ expectType(items[0].fields.myField);
+}
+
+export async function testGetProperties() {
+ const project = new GitHubProject({
+ owner: "owner",
+ number: 1,
+ token: "gpg_secret123",
+ });
+
+ const properties = await project.getProperties();
+
+ expectType<{
+ databaseId: string;
+ id: string;
+ title: string;
+ url: string;
+ }>(properties);
+}
diff --git a/test/recorded/api.getProperties-field-not-found/fixtures.json b/test/recorded/api.getProperties-field-not-found/fixtures.json
new file mode 100644
index 00000000..39498ddb
--- /dev/null
+++ b/test/recorded/api.getProperties-field-not-found/fixtures.json
@@ -0,0 +1,168 @@
+[
+ {
+ "query": "\n query getProjectCoreData($owner: String!, $number: Int!) {\n userOrOrganization: repositoryOwner(login: $owner) {\n ... on ProjectV2Owner {\n projectV2(number: $number) {\n \n id\n title\n url\n databaseId\n fields(first: 50) {\n nodes {\n ... on ProjectV2FieldCommon {\n id\n dataType\n name\n }\n ... on ProjectV2SingleSelectField {\n options {\n id\n name\n }\n }\n ... on ProjectV2IterationField {\n configuration {\n iterations {\n title\n duration\n startDate\n }\n completedIterations {\n title\n duration\n startDate\n }\n duration\n startDay\n }\n }\n }\n }\n\n }\n }\n }\n }\n",
+ "variables": {
+ "owner": "github-project-fixtures",
+ "number": 6
+ },
+ "response": {
+ "status": 200,
+ "url": "https://api.github.com/graphql",
+ "headers": {
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
+ "connection": "close",
+ "content-encoding": "gzip",
+ "content-security-policy": "default-src 'none'",
+ "content-type": "application/json; charset=utf-8",
+ "date": "Mon, 13 Feb 2023 19:41:17 GMT",
+ "referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+ "server": "GitHub.com",
+ "strict-transport-security": "max-age=31536000; includeSubdomains; preload",
+ "transfer-encoding": "chunked",
+ "vary": "Accept-Encoding, Accept, X-Requested-With",
+ "x-content-type-options": "nosniff",
+ "x-frame-options": "deny",
+ "x-github-media-type": "github.v3; format=json",
+ "x-github-request-id": "D662:5EF5:4BD76F8:9CE8ECC:63EA925D",
+ "x-ratelimit-limit": "5000",
+ "x-ratelimit-remaining": "4988",
+ "x-ratelimit-reset": "1676319618",
+ "x-ratelimit-resource": "graphql",
+ "x-ratelimit-used": "12",
+ "x-xss-protection": "0"
+ },
+ "data": {
+ "data": {
+ "userOrOrganization": {
+ "projectV2": {
+ "id": "PVT_1",
+ "title": "@tmelliottjrs Test Fixtures",
+ "url": "https://github.com/orgs/github-project-fixtures/projects/6",
+ "databaseId": 1001,
+ "fields": {
+ "nodes": [
+ {
+ "id": "PVTF_1",
+ "dataType": "TITLE",
+ "name": "Title"
+ },
+ {
+ "id": "PVTF_2",
+ "dataType": "ASSIGNEES",
+ "name": "Assignees"
+ },
+ {
+ "id": "PVTSSF_1",
+ "dataType": "SINGLE_SELECT",
+ "name": "Status",
+ "options": [
+ {
+ "id": "a57b7c0b",
+ "name": "Todo"
+ },
+ {
+ "id": "e5a5bc6d",
+ "name": "In Progress"
+ },
+ {
+ "id": "7eb1a684",
+ "name": "Done"
+ }
+ ]
+ },
+ {
+ "id": "PVTF_3",
+ "dataType": "LABELS",
+ "name": "Labels"
+ },
+ {
+ "id": "PVTF_4",
+ "dataType": "LINKED_PULL_REQUESTS",
+ "name": "Linked pull requests"
+ },
+ {
+ "id": "PVTF_5",
+ "dataType": "REVIEWERS",
+ "name": "Reviewers"
+ },
+ {
+ "id": "PVTF_6",
+ "dataType": "REPOSITORY",
+ "name": "Repository"
+ },
+ {
+ "id": "PVTF_7",
+ "dataType": "MILESTONE",
+ "name": "Milestone"
+ },
+ {
+ "id": "PVTF_8",
+ "dataType": "TEXT",
+ "name": "Text"
+ },
+ {
+ "id": "PVTF_9",
+ "dataType": "NUMBER",
+ "name": "Number"
+ },
+ {
+ "id": "PVTF_10",
+ "dataType": "DATE",
+ "name": "Date"
+ },
+ {
+ "id": "PVTSSF_2",
+ "dataType": "SINGLE_SELECT",
+ "name": "Single select",
+ "options": [
+ {
+ "id": "c35f8785",
+ "name": "One"
+ },
+ {
+ "id": "7390103a",
+ "name": "Two"
+ },
+ {
+ "id": "cbba01c9",
+ "name": "Three"
+ }
+ ]
+ },
+ {
+ "id": "PVTIF_1",
+ "dataType": "ITERATION",
+ "name": "Iteration",
+ "configuration": {
+ "iterations": [],
+ "completedIterations": [
+ {
+ "title": "Iteration 3",
+ "duration": 14,
+ "startDate": "2022-08-05"
+ },
+ {
+ "title": "Iteration 2",
+ "duration": 14,
+ "startDate": "2022-07-22"
+ },
+ {
+ "title": "Iteration 1",
+ "duration": 14,
+ "startDate": "2022-07-08"
+ }
+ ],
+ "duration": 14,
+ "startDay": 5
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/recorded/api.getProperties-field-not-found/prepare.js b/test/recorded/api.getProperties-field-not-found/prepare.js
new file mode 100644
index 00000000..38419981
--- /dev/null
+++ b/test/recorded/api.getProperties-field-not-found/prepare.js
@@ -0,0 +1,14 @@
+// @ts-check
+
+/**
+ * Prepare state in order to record fixtures for test.js. Returns array of arguments that will be passed
+ * passed as `test(project, ...arguments)`.
+ *
+ * @param {import("@octokit/openapi-types").components["schemas"]["repository"]} repository
+ * @param {import("@octokit/core").Octokit} octokit
+ * @param {import("../../..").default<{text: string, number: number, date: string, singleSelect: "One" | "Two" | "Three"}>} project
+ * @returns {Promise<[]>}
+ */
+export async function prepare(repository, octokit, project) {
+ return [];
+}
diff --git a/test/recorded/api.getProperties-field-not-found/test.js b/test/recorded/api.getProperties-field-not-found/test.js
new file mode 100644
index 00000000..309f3a92
--- /dev/null
+++ b/test/recorded/api.getProperties-field-not-found/test.js
@@ -0,0 +1,23 @@
+// @ts-check
+import GitHubProject from "../../../index.js";
+
+/**
+ * @param {import("../../..").default} defaultTestProject
+ */
+export function test(defaultTestProject) {
+ const project = new GitHubProject({
+ owner: defaultTestProject.owner,
+ number: defaultTestProject.number,
+ octokit: defaultTestProject.octokit,
+ fields: {
+ nope: "NOPE",
+ },
+ });
+
+ return project.getProperties().then(
+ () => {
+ throw new Error("Should not resolve");
+ },
+ (error) => error
+ );
+}
diff --git a/test/recorded/api.getProperties-project-not-found/fixtures.json b/test/recorded/api.getProperties-project-not-found/fixtures.json
new file mode 100644
index 00000000..743dbb48
--- /dev/null
+++ b/test/recorded/api.getProperties-project-not-found/fixtures.json
@@ -0,0 +1,44 @@
+[
+ {
+ "query": "\n query getProjectCoreData($owner: String!, $number: Int!) {\n userOrOrganization: repositoryOwner(login: $owner) {\n ... on ProjectV2Owner {\n projectV2(number: $number) {\n \n id\n title\n url\n databaseId\n fields(first: 50) {\n nodes {\n ... on ProjectV2FieldCommon {\n id\n dataType\n name\n }\n ... on ProjectV2SingleSelectField {\n options {\n id\n name\n }\n }\n ... on ProjectV2IterationField {\n configuration {\n iterations {\n title\n duration\n startDate\n }\n completedIterations {\n title\n duration\n startDate\n }\n duration\n startDay\n }\n }\n }\n }\n\n }\n }\n }\n }\n",
+ "variables": {
+ "owner": "github-project-fixtures",
+ "number": 99999
+ },
+ "response": {
+ "status": 200,
+ "url": "https://api.github.com/graphql",
+ "headers": {
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
+ "connection": "close",
+ "content-encoding": "gzip",
+ "content-security-policy": "default-src 'none'",
+ "content-type": "application/json; charset=utf-8",
+ "date": "Mon, 13 Feb 2023 19:41:22 GMT",
+ "referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+ "server": "GitHub.com",
+ "strict-transport-security": "max-age=31536000; includeSubdomains; preload",
+ "transfer-encoding": "chunked",
+ "vary": "Accept-Encoding, Accept, X-Requested-With",
+ "x-content-type-options": "nosniff",
+ "x-frame-options": "deny",
+ "x-github-media-type": "github.v3; format=json",
+ "x-github-request-id": "D668:589A:4B5E728:9BD7F59:63EA9262",
+ "x-ratelimit-limit": "5000",
+ "x-ratelimit-remaining": "4985",
+ "x-ratelimit-reset": "1676319618",
+ "x-ratelimit-resource": "graphql",
+ "x-ratelimit-used": "15",
+ "x-xss-protection": "0"
+ },
+ "data": {
+ "data": {
+ "userOrOrganization": {
+ "projectV2": null
+ }
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/recorded/api.getProperties-project-not-found/prepare.js b/test/recorded/api.getProperties-project-not-found/prepare.js
new file mode 100644
index 00000000..38419981
--- /dev/null
+++ b/test/recorded/api.getProperties-project-not-found/prepare.js
@@ -0,0 +1,14 @@
+// @ts-check
+
+/**
+ * Prepare state in order to record fixtures for test.js. Returns array of arguments that will be passed
+ * passed as `test(project, ...arguments)`.
+ *
+ * @param {import("@octokit/openapi-types").components["schemas"]["repository"]} repository
+ * @param {import("@octokit/core").Octokit} octokit
+ * @param {import("../../..").default<{text: string, number: number, date: string, singleSelect: "One" | "Two" | "Three"}>} project
+ * @returns {Promise<[]>}
+ */
+export async function prepare(repository, octokit, project) {
+ return [];
+}
diff --git a/test/recorded/api.getProperties-project-not-found/test.js b/test/recorded/api.getProperties-project-not-found/test.js
new file mode 100644
index 00000000..16b7e034
--- /dev/null
+++ b/test/recorded/api.getProperties-project-not-found/test.js
@@ -0,0 +1,20 @@
+// @ts-check
+import GitHubProject from "../../../index.js";
+
+/**
+ * @param {import("../../..").default} defaultTestProject
+ */
+export function test(defaultTestProject) {
+ const project = new GitHubProject({
+ owner: defaultTestProject.owner,
+ number: 99999,
+ octokit: defaultTestProject.octokit,
+ });
+
+ return project.getProperties().then(
+ () => {
+ throw new Error("Should not resolve");
+ },
+ (error) => error
+ );
+}
diff --git a/test/recorded/api.getProperties/fixtures.json b/test/recorded/api.getProperties/fixtures.json
new file mode 100644
index 00000000..d13d10a3
--- /dev/null
+++ b/test/recorded/api.getProperties/fixtures.json
@@ -0,0 +1,168 @@
+[
+ {
+ "query": "\n query getProjectCoreData($owner: String!, $number: Int!) {\n userOrOrganization: repositoryOwner(login: $owner) {\n ... on ProjectV2Owner {\n projectV2(number: $number) {\n \n id\n title\n url\n databaseId\n fields(first: 50) {\n nodes {\n ... on ProjectV2FieldCommon {\n id\n dataType\n name\n }\n ... on ProjectV2SingleSelectField {\n options {\n id\n name\n }\n }\n ... on ProjectV2IterationField {\n configuration {\n iterations {\n title\n duration\n startDate\n }\n completedIterations {\n title\n duration\n startDate\n }\n duration\n startDay\n }\n }\n }\n }\n\n }\n }\n }\n }\n",
+ "variables": {
+ "owner": "github-project-fixtures",
+ "number": 6
+ },
+ "response": {
+ "status": 200,
+ "url": "https://api.github.com/graphql",
+ "headers": {
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
+ "connection": "close",
+ "content-encoding": "gzip",
+ "content-security-policy": "default-src 'none'",
+ "content-type": "application/json; charset=utf-8",
+ "date": "Mon, 13 Feb 2023 19:20:21 GMT",
+ "referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+ "server": "GitHub.com",
+ "strict-transport-security": "max-age=31536000; includeSubdomains; preload",
+ "transfer-encoding": "chunked",
+ "vary": "Accept-Encoding, Accept, X-Requested-With",
+ "x-content-type-options": "nosniff",
+ "x-frame-options": "deny",
+ "x-github-media-type": "github.v3; format=json",
+ "x-github-request-id": "D452:6D7A:5023F18:A551E2C:63EA8D75",
+ "x-ratelimit-limit": "5000",
+ "x-ratelimit-remaining": "4997",
+ "x-ratelimit-reset": "1676319618",
+ "x-ratelimit-resource": "graphql",
+ "x-ratelimit-used": "3",
+ "x-xss-protection": "0"
+ },
+ "data": {
+ "data": {
+ "userOrOrganization": {
+ "projectV2": {
+ "id": "PVT_1",
+ "title": "@tmelliottjrs Test Fixtures",
+ "url": "https://github.com/orgs/github-project-fixtures/projects/6",
+ "databaseId": 1001,
+ "fields": {
+ "nodes": [
+ {
+ "id": "PVTF_1",
+ "dataType": "TITLE",
+ "name": "Title"
+ },
+ {
+ "id": "PVTF_2",
+ "dataType": "ASSIGNEES",
+ "name": "Assignees"
+ },
+ {
+ "id": "PVTSSF_1",
+ "dataType": "SINGLE_SELECT",
+ "name": "Status",
+ "options": [
+ {
+ "id": "a57b7c0b",
+ "name": "Todo"
+ },
+ {
+ "id": "e5a5bc6d",
+ "name": "In Progress"
+ },
+ {
+ "id": "7eb1a684",
+ "name": "Done"
+ }
+ ]
+ },
+ {
+ "id": "PVTF_3",
+ "dataType": "LABELS",
+ "name": "Labels"
+ },
+ {
+ "id": "PVTF_4",
+ "dataType": "LINKED_PULL_REQUESTS",
+ "name": "Linked pull requests"
+ },
+ {
+ "id": "PVTF_5",
+ "dataType": "REVIEWERS",
+ "name": "Reviewers"
+ },
+ {
+ "id": "PVTF_6",
+ "dataType": "REPOSITORY",
+ "name": "Repository"
+ },
+ {
+ "id": "PVTF_7",
+ "dataType": "MILESTONE",
+ "name": "Milestone"
+ },
+ {
+ "id": "PVTF_8",
+ "dataType": "TEXT",
+ "name": "Text"
+ },
+ {
+ "id": "PVTF_9",
+ "dataType": "NUMBER",
+ "name": "Number"
+ },
+ {
+ "id": "PVTF_10",
+ "dataType": "DATE",
+ "name": "Date"
+ },
+ {
+ "id": "PVTSSF_2",
+ "dataType": "SINGLE_SELECT",
+ "name": "Single select",
+ "options": [
+ {
+ "id": "c35f8785",
+ "name": "One"
+ },
+ {
+ "id": "7390103a",
+ "name": "Two"
+ },
+ {
+ "id": "cbba01c9",
+ "name": "Three"
+ }
+ ]
+ },
+ {
+ "id": "PVTIF_1",
+ "dataType": "ITERATION",
+ "name": "Iteration",
+ "configuration": {
+ "iterations": [],
+ "completedIterations": [
+ {
+ "title": "Iteration 3",
+ "duration": 14,
+ "startDate": "2022-08-05"
+ },
+ {
+ "title": "Iteration 2",
+ "duration": 14,
+ "startDate": "2022-07-22"
+ },
+ {
+ "title": "Iteration 1",
+ "duration": 14,
+ "startDate": "2022-07-08"
+ }
+ ],
+ "duration": 14,
+ "startDay": 5
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/recorded/api.getProperties/prepare.js b/test/recorded/api.getProperties/prepare.js
new file mode 100644
index 00000000..38419981
--- /dev/null
+++ b/test/recorded/api.getProperties/prepare.js
@@ -0,0 +1,14 @@
+// @ts-check
+
+/**
+ * Prepare state in order to record fixtures for test.js. Returns array of arguments that will be passed
+ * passed as `test(project, ...arguments)`.
+ *
+ * @param {import("@octokit/openapi-types").components["schemas"]["repository"]} repository
+ * @param {import("@octokit/core").Octokit} octokit
+ * @param {import("../../..").default<{text: string, number: number, date: string, singleSelect: "One" | "Two" | "Three"}>} project
+ * @returns {Promise<[]>}
+ */
+export async function prepare(repository, octokit, project) {
+ return [];
+}
diff --git a/test/recorded/api.getProperties/test.js b/test/recorded/api.getProperties/test.js
new file mode 100644
index 00000000..5b9df88f
--- /dev/null
+++ b/test/recorded/api.getProperties/test.js
@@ -0,0 +1,8 @@
+// @ts-check
+
+/**
+ * @param {import("../../..").default} project
+ */
+export function test(project) {
+ return project.getProperties();
+}
diff --git a/test/recorded/getInstance-field-not-found/fixtures.json b/test/recorded/getInstance-field-not-found/fixtures.json
new file mode 100644
index 00000000..59ba4c4f
--- /dev/null
+++ b/test/recorded/getInstance-field-not-found/fixtures.json
@@ -0,0 +1,168 @@
+[
+ {
+ "query": "\n query getProjectCoreData($owner: String!, $number: Int!) {\n userOrOrganization: repositoryOwner(login: $owner) {\n ... on ProjectV2Owner {\n projectV2(number: $number) {\n \n id\n title\n url\n databaseId\n fields(first: 50) {\n nodes {\n ... on ProjectV2FieldCommon {\n id\n dataType\n name\n }\n ... on ProjectV2SingleSelectField {\n options {\n id\n name\n }\n }\n ... on ProjectV2IterationField {\n configuration {\n iterations {\n title\n duration\n startDate\n }\n completedIterations {\n title\n duration\n startDate\n }\n duration\n startDay\n }\n }\n }\n }\n\n }\n }\n }\n }\n",
+ "variables": {
+ "owner": "github-project-fixtures",
+ "number": 6
+ },
+ "response": {
+ "status": 200,
+ "url": "https://api.github.com/graphql",
+ "headers": {
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
+ "connection": "close",
+ "content-encoding": "gzip",
+ "content-security-policy": "default-src 'none'",
+ "content-type": "application/json; charset=utf-8",
+ "date": "Mon, 13 Feb 2023 19:41:31 GMT",
+ "referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+ "server": "GitHub.com",
+ "strict-transport-security": "max-age=31536000; includeSubdomains; preload",
+ "transfer-encoding": "chunked",
+ "vary": "Accept-Encoding, Accept, X-Requested-With",
+ "x-content-type-options": "nosniff",
+ "x-frame-options": "deny",
+ "x-github-media-type": "github.v3; format=json",
+ "x-github-request-id": "D674:9AA3:51FCDF0:A8E4CA9:63EA926B",
+ "x-ratelimit-limit": "5000",
+ "x-ratelimit-remaining": "4979",
+ "x-ratelimit-reset": "1676319618",
+ "x-ratelimit-resource": "graphql",
+ "x-ratelimit-used": "21",
+ "x-xss-protection": "0"
+ },
+ "data": {
+ "data": {
+ "userOrOrganization": {
+ "projectV2": {
+ "id": "PVT_1",
+ "title": "@tmelliottjrs Test Fixtures",
+ "url": "https://github.com/orgs/github-project-fixtures/projects/6",
+ "databaseId": 1001,
+ "fields": {
+ "nodes": [
+ {
+ "id": "PVTF_1",
+ "dataType": "TITLE",
+ "name": "Title"
+ },
+ {
+ "id": "PVTF_2",
+ "dataType": "ASSIGNEES",
+ "name": "Assignees"
+ },
+ {
+ "id": "PVTSSF_1",
+ "dataType": "SINGLE_SELECT",
+ "name": "Status",
+ "options": [
+ {
+ "id": "a57b7c0b",
+ "name": "Todo"
+ },
+ {
+ "id": "e5a5bc6d",
+ "name": "In Progress"
+ },
+ {
+ "id": "7eb1a684",
+ "name": "Done"
+ }
+ ]
+ },
+ {
+ "id": "PVTF_3",
+ "dataType": "LABELS",
+ "name": "Labels"
+ },
+ {
+ "id": "PVTF_4",
+ "dataType": "LINKED_PULL_REQUESTS",
+ "name": "Linked pull requests"
+ },
+ {
+ "id": "PVTF_5",
+ "dataType": "REVIEWERS",
+ "name": "Reviewers"
+ },
+ {
+ "id": "PVTF_6",
+ "dataType": "REPOSITORY",
+ "name": "Repository"
+ },
+ {
+ "id": "PVTF_7",
+ "dataType": "MILESTONE",
+ "name": "Milestone"
+ },
+ {
+ "id": "PVTF_8",
+ "dataType": "TEXT",
+ "name": "Text"
+ },
+ {
+ "id": "PVTF_9",
+ "dataType": "NUMBER",
+ "name": "Number"
+ },
+ {
+ "id": "PVTF_10",
+ "dataType": "DATE",
+ "name": "Date"
+ },
+ {
+ "id": "PVTSSF_2",
+ "dataType": "SINGLE_SELECT",
+ "name": "Single select",
+ "options": [
+ {
+ "id": "c35f8785",
+ "name": "One"
+ },
+ {
+ "id": "7390103a",
+ "name": "Two"
+ },
+ {
+ "id": "cbba01c9",
+ "name": "Three"
+ }
+ ]
+ },
+ {
+ "id": "PVTIF_1",
+ "dataType": "ITERATION",
+ "name": "Iteration",
+ "configuration": {
+ "iterations": [],
+ "completedIterations": [
+ {
+ "title": "Iteration 3",
+ "duration": 14,
+ "startDate": "2022-08-05"
+ },
+ {
+ "title": "Iteration 2",
+ "duration": 14,
+ "startDate": "2022-07-22"
+ },
+ {
+ "title": "Iteration 1",
+ "duration": 14,
+ "startDate": "2022-07-08"
+ }
+ ],
+ "duration": 14,
+ "startDay": 5
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/recorded/getInstance-field-not-found/prepare.js b/test/recorded/getInstance-field-not-found/prepare.js
new file mode 100644
index 00000000..38419981
--- /dev/null
+++ b/test/recorded/getInstance-field-not-found/prepare.js
@@ -0,0 +1,14 @@
+// @ts-check
+
+/**
+ * Prepare state in order to record fixtures for test.js. Returns array of arguments that will be passed
+ * passed as `test(project, ...arguments)`.
+ *
+ * @param {import("@octokit/openapi-types").components["schemas"]["repository"]} repository
+ * @param {import("@octokit/core").Octokit} octokit
+ * @param {import("../../..").default<{text: string, number: number, date: string, singleSelect: "One" | "Two" | "Three"}>} project
+ * @returns {Promise<[]>}
+ */
+export async function prepare(repository, octokit, project) {
+ return [];
+}
diff --git a/test/recorded/getInstance-field-not-found/test.js b/test/recorded/getInstance-field-not-found/test.js
new file mode 100644
index 00000000..c20cb6bd
--- /dev/null
+++ b/test/recorded/getInstance-field-not-found/test.js
@@ -0,0 +1,21 @@
+// @ts-check
+import GitHubProject from "../../../index.js";
+
+/**
+ * @param {import("../../..").default} defaultTestProject
+ */
+export function test(defaultTestProject) {
+ return GitHubProject.getInstance({
+ owner: defaultTestProject.owner,
+ number: defaultTestProject.number,
+ octokit: defaultTestProject.octokit,
+ fields: {
+ nope: "NOPE",
+ },
+ }).then(
+ () => {
+ throw new Error("Should not resolve");
+ },
+ (error) => error
+ );
+}
diff --git a/test/recorded/getInstance-project-not-found/fixtures.json b/test/recorded/getInstance-project-not-found/fixtures.json
new file mode 100644
index 00000000..dd3bf059
--- /dev/null
+++ b/test/recorded/getInstance-project-not-found/fixtures.json
@@ -0,0 +1,44 @@
+[
+ {
+ "query": "\n query getProjectCoreData($owner: String!, $number: Int!) {\n userOrOrganization: repositoryOwner(login: $owner) {\n ... on ProjectV2Owner {\n projectV2(number: $number) {\n \n id\n title\n url\n databaseId\n fields(first: 50) {\n nodes {\n ... on ProjectV2FieldCommon {\n id\n dataType\n name\n }\n ... on ProjectV2SingleSelectField {\n options {\n id\n name\n }\n }\n ... on ProjectV2IterationField {\n configuration {\n iterations {\n title\n duration\n startDate\n }\n completedIterations {\n title\n duration\n startDate\n }\n duration\n startDay\n }\n }\n }\n }\n\n }\n }\n }\n }\n",
+ "variables": {
+ "owner": "github-project-fixtures",
+ "number": 99999
+ },
+ "response": {
+ "status": 200,
+ "url": "https://api.github.com/graphql",
+ "headers": {
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
+ "connection": "close",
+ "content-encoding": "gzip",
+ "content-security-policy": "default-src 'none'",
+ "content-type": "application/json; charset=utf-8",
+ "date": "Mon, 13 Feb 2023 19:41:35 GMT",
+ "referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+ "server": "GitHub.com",
+ "strict-transport-security": "max-age=31536000; includeSubdomains; preload",
+ "transfer-encoding": "chunked",
+ "vary": "Accept-Encoding, Accept, X-Requested-With",
+ "x-content-type-options": "nosniff",
+ "x-frame-options": "deny",
+ "x-github-media-type": "github.v3; format=json",
+ "x-github-request-id": "D67E:7282:6A83D42:DB2C3CD:63EA926F",
+ "x-ratelimit-limit": "5000",
+ "x-ratelimit-remaining": "4976",
+ "x-ratelimit-reset": "1676319618",
+ "x-ratelimit-resource": "graphql",
+ "x-ratelimit-used": "24",
+ "x-xss-protection": "0"
+ },
+ "data": {
+ "data": {
+ "userOrOrganization": {
+ "projectV2": null
+ }
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/recorded/getInstance-project-not-found/prepare.js b/test/recorded/getInstance-project-not-found/prepare.js
new file mode 100644
index 00000000..38419981
--- /dev/null
+++ b/test/recorded/getInstance-project-not-found/prepare.js
@@ -0,0 +1,14 @@
+// @ts-check
+
+/**
+ * Prepare state in order to record fixtures for test.js. Returns array of arguments that will be passed
+ * passed as `test(project, ...arguments)`.
+ *
+ * @param {import("@octokit/openapi-types").components["schemas"]["repository"]} repository
+ * @param {import("@octokit/core").Octokit} octokit
+ * @param {import("../../..").default<{text: string, number: number, date: string, singleSelect: "One" | "Two" | "Three"}>} project
+ * @returns {Promise<[]>}
+ */
+export async function prepare(repository, octokit, project) {
+ return [];
+}
diff --git a/test/recorded/getInstance-project-not-found/test.js b/test/recorded/getInstance-project-not-found/test.js
new file mode 100644
index 00000000..cd03468a
--- /dev/null
+++ b/test/recorded/getInstance-project-not-found/test.js
@@ -0,0 +1,18 @@
+// @ts-check
+import GitHubProject from "../../../index.js";
+
+/**
+ * @param {import("../../..").default} defaultTestProject
+ */
+export function test(defaultTestProject) {
+ return GitHubProject.getInstance({
+ owner: defaultTestProject.owner,
+ number: 99999,
+ octokit: defaultTestProject.octokit,
+ }).then(
+ () => {
+ throw new Error("Should not resolve");
+ },
+ (error) => error
+ );
+}
diff --git a/test/recorded/getInstance/fixtures.json b/test/recorded/getInstance/fixtures.json
new file mode 100644
index 00000000..919b5186
--- /dev/null
+++ b/test/recorded/getInstance/fixtures.json
@@ -0,0 +1,168 @@
+[
+ {
+ "query": "\n query getProjectCoreData($owner: String!, $number: Int!) {\n userOrOrganization: repositoryOwner(login: $owner) {\n ... on ProjectV2Owner {\n projectV2(number: $number) {\n \n id\n title\n url\n databaseId\n fields(first: 50) {\n nodes {\n ... on ProjectV2FieldCommon {\n id\n dataType\n name\n }\n ... on ProjectV2SingleSelectField {\n options {\n id\n name\n }\n }\n ... on ProjectV2IterationField {\n configuration {\n iterations {\n title\n duration\n startDate\n }\n completedIterations {\n title\n duration\n startDate\n }\n duration\n startDay\n }\n }\n }\n }\n\n }\n }\n }\n }\n",
+ "variables": {
+ "owner": "github-project-fixtures",
+ "number": 6
+ },
+ "response": {
+ "status": 200,
+ "url": "https://api.github.com/graphql",
+ "headers": {
+ "access-control-allow-origin": "*",
+ "access-control-expose-headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
+ "connection": "close",
+ "content-encoding": "gzip",
+ "content-security-policy": "default-src 'none'",
+ "content-type": "application/json; charset=utf-8",
+ "date": "Mon, 13 Feb 2023 19:41:26 GMT",
+ "referrer-policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+ "server": "GitHub.com",
+ "strict-transport-security": "max-age=31536000; includeSubdomains; preload",
+ "transfer-encoding": "chunked",
+ "vary": "Accept-Encoding, Accept, X-Requested-With",
+ "x-content-type-options": "nosniff",
+ "x-frame-options": "deny",
+ "x-github-media-type": "github.v3; format=json",
+ "x-github-request-id": "D66F:4E1E:4DA0AED:A03FD44:63EA9266",
+ "x-ratelimit-limit": "5000",
+ "x-ratelimit-remaining": "4982",
+ "x-ratelimit-reset": "1676319618",
+ "x-ratelimit-resource": "graphql",
+ "x-ratelimit-used": "18",
+ "x-xss-protection": "0"
+ },
+ "data": {
+ "data": {
+ "userOrOrganization": {
+ "projectV2": {
+ "id": "PVT_1",
+ "title": "@tmelliottjrs Test Fixtures",
+ "url": "https://github.com/orgs/github-project-fixtures/projects/6",
+ "databaseId": 1001,
+ "fields": {
+ "nodes": [
+ {
+ "id": "PVTF_1",
+ "dataType": "TITLE",
+ "name": "Title"
+ },
+ {
+ "id": "PVTF_2",
+ "dataType": "ASSIGNEES",
+ "name": "Assignees"
+ },
+ {
+ "id": "PVTSSF_1",
+ "dataType": "SINGLE_SELECT",
+ "name": "Status",
+ "options": [
+ {
+ "id": "a57b7c0b",
+ "name": "Todo"
+ },
+ {
+ "id": "e5a5bc6d",
+ "name": "In Progress"
+ },
+ {
+ "id": "7eb1a684",
+ "name": "Done"
+ }
+ ]
+ },
+ {
+ "id": "PVTF_3",
+ "dataType": "LABELS",
+ "name": "Labels"
+ },
+ {
+ "id": "PVTF_4",
+ "dataType": "LINKED_PULL_REQUESTS",
+ "name": "Linked pull requests"
+ },
+ {
+ "id": "PVTF_5",
+ "dataType": "REVIEWERS",
+ "name": "Reviewers"
+ },
+ {
+ "id": "PVTF_6",
+ "dataType": "REPOSITORY",
+ "name": "Repository"
+ },
+ {
+ "id": "PVTF_7",
+ "dataType": "MILESTONE",
+ "name": "Milestone"
+ },
+ {
+ "id": "PVTF_8",
+ "dataType": "TEXT",
+ "name": "Text"
+ },
+ {
+ "id": "PVTF_9",
+ "dataType": "NUMBER",
+ "name": "Number"
+ },
+ {
+ "id": "PVTF_10",
+ "dataType": "DATE",
+ "name": "Date"
+ },
+ {
+ "id": "PVTSSF_2",
+ "dataType": "SINGLE_SELECT",
+ "name": "Single select",
+ "options": [
+ {
+ "id": "c35f8785",
+ "name": "One"
+ },
+ {
+ "id": "7390103a",
+ "name": "Two"
+ },
+ {
+ "id": "cbba01c9",
+ "name": "Three"
+ }
+ ]
+ },
+ {
+ "id": "PVTIF_1",
+ "dataType": "ITERATION",
+ "name": "Iteration",
+ "configuration": {
+ "iterations": [],
+ "completedIterations": [
+ {
+ "title": "Iteration 3",
+ "duration": 14,
+ "startDate": "2022-08-05"
+ },
+ {
+ "title": "Iteration 2",
+ "duration": 14,
+ "startDate": "2022-07-22"
+ },
+ {
+ "title": "Iteration 1",
+ "duration": 14,
+ "startDate": "2022-07-08"
+ }
+ ],
+ "duration": 14,
+ "startDay": 5
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/recorded/getInstance/prepare.js b/test/recorded/getInstance/prepare.js
new file mode 100644
index 00000000..38419981
--- /dev/null
+++ b/test/recorded/getInstance/prepare.js
@@ -0,0 +1,14 @@
+// @ts-check
+
+/**
+ * Prepare state in order to record fixtures for test.js. Returns array of arguments that will be passed
+ * passed as `test(project, ...arguments)`.
+ *
+ * @param {import("@octokit/openapi-types").components["schemas"]["repository"]} repository
+ * @param {import("@octokit/core").Octokit} octokit
+ * @param {import("../../..").default<{text: string, number: number, date: string, singleSelect: "One" | "Two" | "Three"}>} project
+ * @returns {Promise<[]>}
+ */
+export async function prepare(repository, octokit, project) {
+ return [];
+}
diff --git a/test/recorded/getInstance/test.js b/test/recorded/getInstance/test.js
new file mode 100644
index 00000000..85ee50b1
--- /dev/null
+++ b/test/recorded/getInstance/test.js
@@ -0,0 +1,13 @@
+// @ts-check
+import GitHubProject from "../../../index.js";
+
+/**
+ * @param {import("../../..").default} defaultTestProject
+ */
+export function test(defaultTestProject) {
+ return GitHubProject.getInstance({
+ owner: defaultTestProject.owner,
+ number: defaultTestProject.number,
+ octokit: defaultTestProject.octokit,
+ });
+}
diff --git a/test/snapshots/recorded.test.js.md b/test/snapshots/recorded.test.js.md
index 0ee2c882..e9e2aabc 100644
--- a/test/snapshots/recorded.test.js.md
+++ b/test/snapshots/recorded.test.js.md
@@ -4,6 +4,33 @@ The actual snapshot is saved in `recorded.test.js.snap`.
Generated by [AVA](https://avajs.dev).
+## api.getProperties
+
+> Snapshot 1
+
+ {
+ databaseId: 1001,
+ id: 'PVT_1',
+ title: '@tmelliottjrs Test Fixtures',
+ url: 'https://github.com/orgs/github-project-fixtures/projects/6',
+ }
+
+## api.getProperties-field-not-found
+
+> Snapshot 1
+
+ Error {
+ message: '[github-project] "NOPE" could not be matched with any of the existing field names: "Title", "Assignees", "Status", "Labels", "Linked pull requests", "Reviewers", "Repository", "Milestone", "Text", "Number", "Date", "Single select", "Iteration". If the field should be considered optional, then set it to "nope: { name: "NOPE", optional: true}',
+ }
+
+## api.getProperties-project-not-found
+
+> Snapshot 1
+
+ Error {
+ message: '[github-project] Cannot find project with number: 99999 and owner: github-project-fixtures',
+ }
+
## api.items.add
> Snapshot 1
@@ -7084,3 +7111,25 @@ Generated by [AVA](https://avajs.dev).
isArchived: undefined,
type: 'ISSUE',
}
+
+## getInstance
+
+> Snapshot 1
+
+ GitHubProject {}
+
+## getInstance-field-not-found
+
+> Snapshot 1
+
+ Error {
+ message: '[github-project] "NOPE" could not be matched with any of the existing field names: "Title", "Assignees", "Status", "Labels", "Linked pull requests", "Reviewers", "Repository", "Milestone", "Text", "Number", "Date", "Single select", "Iteration". If the field should be considered optional, then set it to "nope: { name: "NOPE", optional: true}',
+ }
+
+## getInstance-project-not-found
+
+> Snapshot 1
+
+ Error {
+ message: '[github-project] Cannot find project with number: 99999 and owner: github-project-fixtures',
+ }
diff --git a/test/snapshots/recorded.test.js.snap b/test/snapshots/recorded.test.js.snap
index de2cf93b..29bd37e9 100644
Binary files a/test/snapshots/recorded.test.js.snap and b/test/snapshots/recorded.test.js.snap differ