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