From 6c04a1ca5e12916488092fa4f9236b8e0b50147f Mon Sep 17 00:00:00 2001 From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com> Date: Sat, 6 Aug 2022 20:39:22 +0200 Subject: [PATCH] feat(plugins/projects): add support for GitHub projects beta (#1178) --- .../app/web/statics/embed/app.placeholder.js | 29 +++++++++++++------ source/plugins/projects/index.mjs | 28 +++++++++++++++--- source/plugins/projects/metadata.yml | 5 ++-- .../projects/queries/repository.graphql | 24 ++++++++++----- .../queries/repository.legacy.graphql | 17 +++++++++++ source/plugins/projects/queries/user.graphql | 24 ++++++++++----- .../projects/queries/user.legacy.graphql | 18 ++++++++++++ .../templates/classic/partials/projects.ejs | 24 ++++++++++++++- source/templates/classic/style.css | 3 ++ .../repository/partials/projects.ejs | 24 ++++++++++++++- .../graphql/projects.repository.legacy.mjs | 21 ++++++++++++++ .../github/graphql/projects.repository.mjs | 14 ++++----- .../github/graphql/projects.user.legacy.mjs | 24 +++++++++++++++ .../api/github/graphql/projects.user.mjs | 14 ++++----- 14 files changed, 222 insertions(+), 47 deletions(-) create mode 100644 source/plugins/projects/queries/repository.legacy.graphql create mode 100644 source/plugins/projects/queries/user.legacy.graphql create mode 100644 tests/mocks/api/github/graphql/projects.repository.legacy.mjs create mode 100644 tests/mocks/api/github/graphql/projects.user.legacy.mjs diff --git a/source/app/web/statics/embed/app.placeholder.js b/source/app/web/statics/embed/app.placeholder.js index 5a05c684..5e7dd1ab 100644 --- a/source/app/web/statics/embed/app.placeholder.js +++ b/source/app/web/statics/embed/app.placeholder.js @@ -715,15 +715,26 @@ name: faker.lorem.sentence(), description: faker.lorem.paragraph(), updated: `${2 + faker.datatype.number(8)} days ago`, - progress: { - enabled: true, - todo: faker.datatype.number(50), - doing: faker.datatype.number(50), - done: faker.datatype.number(50), - get total() { - return this.todo + this.doing + this.done - }, - }, + ...(faker.datatype.boolean() ? { + items: new Array(faker.datatype.number(4)).fill(null).map(() => ({type: faker.helpers.arrayElement(["DRAFT_ISSUE", "ISSUE", "PULL_REQUEST", "REDACTED"]), text: faker.lorem.sentence()})), + progress: { + enabled: false, + todo: NaN, + doing: NaN, + done: NaN, + total: faker.datatype.number(100), + } + } : { + progress: { + enabled: true, + todo: faker.datatype.number(50), + doing: faker.datatype.number(50), + done: faker.datatype.number(50), + get total() { + return this.todo + this.doing + this.done + }, + } + }), })), }, }) diff --git a/source/plugins/projects/index.mjs b/source/plugins/projects/index.mjs index a2154e35..b10f8d7d 100644 --- a/source/plugins/projects/index.mjs +++ b/source/plugins/projects/index.mjs @@ -15,7 +15,10 @@ export default async function({login, data, imports, graphql, q, queries, accoun //Retrieve user owned projects from graphql api console.debug(`metrics/compute/${login}/plugins > projects > querying api`) - const {[account]: {projects}} = await graphql(queries.projects.user({login, limit, account})) + const {[account]: {projects}} = await graphql(queries.projects["user.legacy"]({login, limit, account})) + const {[account]: {projectsV2}} = await graphql(queries.projects.user({login, limit, account})) + projects.nodes.unshift(...projectsV2.nodes) + projects.totalCount += projectsV2.totalCount //Retrieve repositories projects from graphql api for (const identifier of repositories) { @@ -24,11 +27,21 @@ export default async function({login, data, imports, graphql, q, queries, accoun const {user, repository, id} = identifier.match(/(?[-\w]+)[/](?[-\w]+)[/]projects[/](?\d+)/)?.groups ?? {} let project = null for (const account of ["user", "organization"]) { + //Try projects beta try { - ;({project} = (await graphql(queries.projects.repository({user, repository, id, account})))[account].repository) + project = (await graphql(queries.projects.repository({user, repository, id, account})))[account].repository.projectV2 + break } catch (error) { + //Try projects classic console.debug(error) + try { + ;({project} = (await graphql(queries.projects["repository.legacy"]({user, repository, id, account})))[account].repository) + break + } + catch (error) { + console.debug(error) + } } } if (!project) @@ -52,9 +65,16 @@ export default async function({login, data, imports, graphql, q, queries, accoun else if (time < 30) updated = `${Math.floor(time)} day${time >= 2 ? "s" : ""} ago` //Format progress - const {enabled, todoCount: todo, inProgressCount: doing, doneCount: done} = project.progress + const {enabled = false, todoCount: todo = NaN, inProgressCount: doing = NaN, doneCount: done = NaN} = project.progress ?? {} + let total = todo + doing + done + //Format items (v2) + const items = [] + if (project.items) { + items.push(...project.items.nodes.map(({type, fieldValues:{nodes:fields}}) => ({type, text:fields.filter(field => field.text).shift()?.text ?? ""}))) + total = project.items.totalCount + } //Append - list.push({name: project.name, updated, description: project.body, progress: {enabled, todo, doing, done, total: todo + doing + done}}) + list.push({name: project.name, updated, description: project.body, progress: {enabled, todo, doing, done, total}, items}) } //Limit diff --git a/source/plugins/projects/metadata.yml b/source/plugins/projects/metadata.yml index 620cdffc..c6981817 100644 --- a/source/plugins/projects/metadata.yml +++ b/source/plugins/projects/metadata.yml @@ -2,8 +2,6 @@ name: 🗂️ GitHub projects category: github description: | This plugin displays progress of profile and repository projects. -notes: | - > ℹ️ This plugin currently only supports [GitHub projects boards](https://docs.github.com/en/issues/organizing-your-work-with-project-boards/managing-project-boards/about-project-boards) and not [GitHub projects (beta)](https://docs.github.com/en/issues/trying-out-the-new-projects-experience/about-projects) examples: default: https://github.com/lowlighter/metrics/blob/examples/metrics.plugin.projects.svg index: 25 @@ -14,6 +12,7 @@ supports: scopes: - public_access - public_repo + - read:project inputs: plugin_projects: @@ -37,6 +36,8 @@ inputs: Featured repositories projects Use the following syntax for each project `:user/:repo/projects/:project_id` + + > ℹ️ [GitHub projects (beta)](https://docs.github.com/en/issues/trying-out-the-new-projects-experience/about-projects) needs to use the same syntax as above and repository must specified repository must be linked to given project. type: array example: username/repo/projects/1, username/repo/projects/2, ... format: comma-separated diff --git a/source/plugins/projects/queries/repository.graphql b/source/plugins/projects/queries/repository.graphql index d58ee7cd..34b233cd 100644 --- a/source/plugins/projects/queries/repository.graphql +++ b/source/plugins/projects/queries/repository.graphql @@ -1,15 +1,23 @@ query ProjectsRepository { $account(login: "$user") { repository(name: "$repository") { - project(number: $id) { - name - body + projectV2(number: $id) { + name: title + body: shortDescription updatedAt - progress { - doneCount - inProgressCount - todoCount - enabled + items(first: 4, orderBy: {field: POSITION, direction: ASC}) { + totalCount + nodes { + type + isArchived + fieldValues(last: 100) { + nodes { + ... on ProjectV2ItemFieldTextValue { + text + } + } + } + } } } } diff --git a/source/plugins/projects/queries/repository.legacy.graphql b/source/plugins/projects/queries/repository.legacy.graphql new file mode 100644 index 00000000..67f50a99 --- /dev/null +++ b/source/plugins/projects/queries/repository.legacy.graphql @@ -0,0 +1,17 @@ +query ProjectsRepositoryLegacy { + $account(login: "$user") { + repository(name: "$repository") { + project(number: $id) { + name + body + updatedAt + progress { + doneCount + inProgressCount + todoCount + enabled + } + } + } + } +} \ No newline at end of file diff --git a/source/plugins/projects/queries/user.graphql b/source/plugins/projects/queries/user.graphql index a39be1fa..49696eb2 100644 --- a/source/plugins/projects/queries/user.graphql +++ b/source/plugins/projects/queries/user.graphql @@ -1,16 +1,24 @@ query ProjectsUser { $account(login: "$login") { - projects(last: $limit, states: OPEN, orderBy: {field: UPDATED_AT, direction: DESC}) { + projectsV2(last: $limit, orderBy: {field: UPDATED_AT, direction: DESC}) { totalCount nodes { - name - body + name: title + body: shortDescription updatedAt - progress { - doneCount - inProgressCount - todoCount - enabled + items(first: 4, orderBy: {field: POSITION, direction: ASC}) { + totalCount + nodes { + type + isArchived + fieldValues(last: 100) { + nodes { + ... on ProjectV2ItemFieldTextValue { + text + } + } + } + } } } } diff --git a/source/plugins/projects/queries/user.legacy.graphql b/source/plugins/projects/queries/user.legacy.graphql new file mode 100644 index 00000000..2f31f94c --- /dev/null +++ b/source/plugins/projects/queries/user.legacy.graphql @@ -0,0 +1,18 @@ +query ProjectsUserLegacy { + $account(login: "$login") { + projects(last: $limit, states: OPEN, orderBy: {field: UPDATED_AT, direction: DESC}) { + totalCount + nodes { + name + body + updatedAt + progress { + doneCount + inProgressCount + todoCount + enabled + } + } + } + } +} \ No newline at end of file diff --git a/source/templates/classic/partials/projects.ejs b/source/templates/classic/partials/projects.ejs index dcb76179..e16d2470 100644 --- a/source/templates/classic/partials/projects.ejs +++ b/source/templates/classic/partials/projects.ejs @@ -14,7 +14,7 @@ <% } else { %>
- <% for (const {name, updated, progress, description = ""} of plugins.projects.list) { %> + <% for (const {name, updated, progress, items = [], description = ""} of plugins.projects.list) { %>
@@ -60,6 +60,28 @@
<% } %> + <% if (items.length) { %> +
+ <% for (const {type, text} of items) { %> +
+
+
+ <% if (type === "DRAFT_ISSUE") { %> + + <% } else if (type === "ISSUE") { %> + + <% } else if (type === "ISSUE") { %> + + <% } else { %> + + <% } %> + <%= text %> +
+
+
+ <% } %> +
+ <% } %> <% } %>
<% } %> diff --git a/source/templates/classic/style.css b/source/templates/classic/style.css index 39096502..16e436a0 100644 --- a/source/templates/classic/style.css +++ b/source/templates/classic/style.css @@ -918,6 +918,9 @@ -webkit-line-clamp: 2; -webkit-box-orient: vertical; } + .project .items, .project-items { + padding-left: 11px; + } /* Star lists */ .starlist { diff --git a/source/templates/repository/partials/projects.ejs b/source/templates/repository/partials/projects.ejs index 03f7761e..9c50c903 100644 --- a/source/templates/repository/partials/projects.ejs +++ b/source/templates/repository/partials/projects.ejs @@ -14,7 +14,7 @@
<% } else { %>
- <% for (const {name, updated, progress} of plugins.projects.list) { %> + <% for (const {name, updated, progress, items = []} of plugins.projects.list) { %>
@@ -51,6 +51,28 @@
<% } %> + <% if (items.length) { %> +
+ <% for (const {type, text} of items) { %> +
+
+
+ <% if (type === "DRAFT_ISSUE") { %> + + <% } else if (type === "ISSUE") { %> + + <% } else if (type === "ISSUE") { %> + + <% } else { %> + + <% } %> + <%= text %> +
+
+
+ <% } %> +
+ <% } %> <% } %>
<% } %> diff --git a/tests/mocks/api/github/graphql/projects.repository.legacy.mjs b/tests/mocks/api/github/graphql/projects.repository.legacy.mjs new file mode 100644 index 00000000..3bfc196b --- /dev/null +++ b/tests/mocks/api/github/graphql/projects.repository.legacy.mjs @@ -0,0 +1,21 @@ +/**Mocked data */ +export default function({faker, query, login = faker.internet.userName()}) { + console.debug("metrics/compute/mocks > mocking graphql api result > projects/repository.legacy") + return ({ + user: { + repository: { + project: { + name: "Repository project example", + updatedAt: `${faker.date.recent()}`, + body: faker.lorem.paragraph(), + progress: { + doneCount: faker.datatype.number(10), + inProgressCount: faker.datatype.number(10), + todoCount: faker.datatype.number(10), + enabled: true, + }, + }, + }, + }, + }) +} diff --git a/tests/mocks/api/github/graphql/projects.repository.mjs b/tests/mocks/api/github/graphql/projects.repository.mjs index 4024da2f..7ec12294 100644 --- a/tests/mocks/api/github/graphql/projects.repository.mjs +++ b/tests/mocks/api/github/graphql/projects.repository.mjs @@ -4,16 +4,16 @@ export default function({faker, query, login = faker.internet.userName()}) { return ({ user: { repository: { - project: { + projectV2: { name: "Repository project example", updatedAt: `${faker.date.recent()}`, body: faker.lorem.paragraph(), - progress: { - doneCount: faker.datatype.number(10), - inProgressCount: faker.datatype.number(10), - todoCount: faker.datatype.number(10), - enabled: true, - }, + items: { + get totalCount() { + return this.nodes.length + }, + nodes: new Array(faker.datatype.number(10)).fill(null).map(() => ({type: faker.helpers.arrayElement(["DRAFT_ISSUE", "ISSUE", "PULL_REQUEST", "REDACTED"]), fieldValues: { nodes: [ { text: faker.lorem.sentence() } ] }})) + } }, }, }, diff --git a/tests/mocks/api/github/graphql/projects.user.legacy.mjs b/tests/mocks/api/github/graphql/projects.user.legacy.mjs new file mode 100644 index 00000000..4f6c86d7 --- /dev/null +++ b/tests/mocks/api/github/graphql/projects.user.legacy.mjs @@ -0,0 +1,24 @@ +/**Mocked data */ +export default function({faker, query, login = faker.internet.userName()}) { + console.debug("metrics/compute/mocks > mocking graphql api result > projects/user.legacy") + return ({ + user: { + projects: { + totalCount: 1, + nodes: [ + { + name: "User-owned project", + updatedAt: `${faker.date.recent()}`, + body: faker.lorem.paragraph(), + progress: { + doneCount: faker.datatype.number(10), + inProgressCount: faker.datatype.number(10), + todoCount: faker.datatype.number(10), + enabled: true, + }, + }, + ], + }, + }, + }) +} diff --git a/tests/mocks/api/github/graphql/projects.user.mjs b/tests/mocks/api/github/graphql/projects.user.mjs index 36ebcaee..c45d30f8 100644 --- a/tests/mocks/api/github/graphql/projects.user.mjs +++ b/tests/mocks/api/github/graphql/projects.user.mjs @@ -3,19 +3,19 @@ export default function({faker, query, login = faker.internet.userName()}) { console.debug("metrics/compute/mocks > mocking graphql api result > projects/user") return ({ user: { - projects: { + projectsV2: { totalCount: 1, nodes: [ { name: "User-owned project", updatedAt: `${faker.date.recent()}`, body: faker.lorem.paragraph(), - progress: { - doneCount: faker.datatype.number(10), - inProgressCount: faker.datatype.number(10), - todoCount: faker.datatype.number(10), - enabled: true, - }, + items: { + get totalCount() { + return this.nodes.length + }, + nodes: new Array(faker.datatype.number(10)).fill(null).map(() => ({type: faker.helpers.arrayElement(["DRAFT_ISSUE", "ISSUE", "PULL_REQUEST", "REDACTED"]), fieldValues: { nodes: [ { text: faker.lorem.sentence() } ] }})) + } }, ], },