diff --git a/src/__generated__/github-schema-loader.ts b/src/__generated__/github-schema-loader.ts index 292c156c..a5e6a43e 100644 --- a/src/__generated__/github-schema-loader.ts +++ b/src/__generated__/github-schema-loader.ts @@ -1,3 +1,3 @@ const { schema } = require("@octokit/graphql-schema") -module.exports = schema.json \ No newline at end of file +module.exports = schema.json diff --git a/src/__generated__/graphql.ts b/src/__generated__/graphql.ts index 4821d5eb..62fa7c88 100644 --- a/src/__generated__/graphql.ts +++ b/src/__generated__/graphql.ts @@ -29085,25 +29085,71 @@ export type IResolvers = Resolvers; export const OpenRequestsForComments = gql` query OpenRequestsForComments { - search(query: "org:Artsy label:RFC state:open", type: ISSUE, first: 100) { + search(query: "org:Artsy label:RFC state:open", type: ISSUE, first: 20) { + __typename issueCount nodes { + __typename ... on Issue { + participants { + __typename + totalCount + } + timelineItems(last: 1, itemTypes: ISSUE_COMMENT) { + __typename + nodes { + __typename + ... on IssueComment { + createdAt + url + author { + __typename + avatarUrl + login + url + } + } + } + } + createdAt title url author { + __typename + avatarUrl login url - avatarUrl } } ... on PullRequest { + createdAt title url author { + __typename + avatarUrl login url - avatarUrl + } + participants { + __typename + totalCount + } + timelineItems(last: 1, itemTypes: ISSUE_COMMENT) { + __typename + nodes { + __typename + ... on IssueComment { + createdAt + url + author { + __typename + avatarUrl + login + url + } + } + } } } } @@ -29116,46 +29162,96 @@ export type OpenRequestsForCommentsQueryVariables = Exact<{ [key: string]: never export type OpenRequestsForCommentsQuery = ( { __typename?: 'Query' } & { search: ( - { __typename?: 'SearchResultItemConnection' } + { __typename: 'SearchResultItemConnection' } & Pick - & { nodes?: Maybe - & { author?: Maybe<( - { __typename?: 'Bot' } - & Pick + & { nodes?: Maybe + & { participants: ( + { __typename: 'UserConnection' } + & Pick + ), timelineItems: ( + { __typename: 'IssueTimelineItemsConnection' } + & { nodes?: Maybe + & { author?: Maybe<( + { __typename: 'Bot' } + & Pick + ) | ( + { __typename: 'EnterpriseUserAccount' } + & Pick + ) | ( + { __typename: 'Mannequin' } + & Pick + ) | ( + { __typename: 'Organization' } + & Pick + ) | ( + { __typename: 'User' } + & Pick + )> } + ) | { __typename: 'LabeledEvent' } | { __typename: 'LockedEvent' } | { __typename: 'MarkedAsDuplicateEvent' } | { __typename: 'MentionedEvent' } | { __typename: 'MilestonedEvent' } | { __typename: 'MovedColumnsInProjectEvent' } | { __typename: 'PinnedEvent' } | { __typename: 'ReferencedEvent' } | { __typename: 'RemovedFromProjectEvent' } | { __typename: 'RenamedTitleEvent' } | { __typename: 'ReopenedEvent' } | { __typename: 'SubscribedEvent' } | { __typename: 'TransferredEvent' } | { __typename: 'UnassignedEvent' } | { __typename: 'UnlabeledEvent' } | { __typename: 'UnlockedEvent' } | { __typename: 'UnmarkedAsDuplicateEvent' } | { __typename: 'UnpinnedEvent' } | { __typename: 'UnsubscribedEvent' } | { __typename: 'UserBlockedEvent' }>>> } + ), author?: Maybe<( + { __typename: 'Bot' } + & Pick ) | ( - { __typename?: 'EnterpriseUserAccount' } - & Pick + { __typename: 'EnterpriseUserAccount' } + & Pick ) | ( - { __typename?: 'Mannequin' } - & Pick + { __typename: 'Mannequin' } + & Pick ) | ( - { __typename?: 'Organization' } - & Pick + { __typename: 'Organization' } + & Pick ) | ( - { __typename?: 'User' } - & Pick + { __typename: 'User' } + & Pick )> } - ) | { __typename?: 'MarketplaceListing' } | { __typename?: 'Organization' } | ( - { __typename?: 'PullRequest' } - & Pick + ) | { __typename: 'MarketplaceListing' } | { __typename: 'Organization' } | ( + { __typename: 'PullRequest' } + & Pick & { author?: Maybe<( - { __typename?: 'Bot' } - & Pick + { __typename: 'Bot' } + & Pick ) | ( - { __typename?: 'EnterpriseUserAccount' } - & Pick + { __typename: 'EnterpriseUserAccount' } + & Pick ) | ( - { __typename?: 'Mannequin' } - & Pick + { __typename: 'Mannequin' } + & Pick ) | ( - { __typename?: 'Organization' } - & Pick + { __typename: 'Organization' } + & Pick ) | ( - { __typename?: 'User' } - & Pick - )> } - ) | { __typename?: 'Repository' } | { __typename?: 'User' }>>> } + { __typename: 'User' } + & Pick + )>, participants: ( + { __typename: 'UserConnection' } + & Pick + ), timelineItems: ( + { __typename: 'PullRequestTimelineItemsConnection' } + & { nodes?: Maybe + & { author?: Maybe<( + { __typename: 'Bot' } + & Pick + ) | ( + { __typename: 'EnterpriseUserAccount' } + & Pick + ) | ( + { __typename: 'Mannequin' } + & Pick + ) | ( + { __typename: 'Organization' } + & Pick + ) | ( + { __typename: 'User' } + & Pick + )> } + ) | { __typename: 'LabeledEvent' } | { __typename: 'LockedEvent' } | { __typename: 'MarkedAsDuplicateEvent' } | { __typename: 'MentionedEvent' } | { __typename: 'MergedEvent' } | { __typename: 'MilestonedEvent' } | { __typename: 'MovedColumnsInProjectEvent' } | { __typename: 'PinnedEvent' } | { __typename: 'PullRequestCommit' } | { __typename: 'PullRequestCommitCommentThread' } | { __typename: 'PullRequestReview' } | { __typename: 'PullRequestReviewThread' } | { __typename: 'PullRequestRevisionMarker' } | { __typename: 'ReadyForReviewEvent' } | { __typename: 'ReferencedEvent' } | { __typename: 'RemovedFromProjectEvent' } | { __typename: 'RenamedTitleEvent' } | { __typename: 'ReopenedEvent' } | { __typename: 'ReviewDismissedEvent' } | { __typename: 'ReviewRequestRemovedEvent' } | { __typename: 'ReviewRequestedEvent' } | { __typename: 'SubscribedEvent' } | { __typename: 'TransferredEvent' } | { __typename: 'UnassignedEvent' } | { __typename: 'UnlabeledEvent' } | { __typename: 'UnlockedEvent' } | { __typename: 'UnmarkedAsDuplicateEvent' } | { __typename: 'UnpinnedEvent' } | { __typename: 'UnsubscribedEvent' } | { __typename: 'UserBlockedEvent' }>>> } + ) } + ) | { __typename: 'Repository' } | { __typename: 'User' }>>> } ) } ); diff --git a/src/commands/scheduled/rfcs.ts b/src/commands/scheduled/rfcs.ts index 3d629c2a..9daa350d 100644 --- a/src/commands/scheduled/rfcs.ts +++ b/src/commands/scheduled/rfcs.ts @@ -8,46 +8,110 @@ import { githubClient } from "../../utils/github" export default class RFCs extends Command { static description = "lists open RFCs" + static SearchURL = + "https://github.com/search?q=org:Artsy+label:RFC+state:open" + async run() { require("dotenv").config() - const { data: { search }} = await githubClient().query({ + const { + data: { search }, + } = await githubClient().query({ query: OpenRequestsForComments, - }); + }) + + const blocks = [] if (search.issueCount === 0) { - const payload = JSON.stringify({ - text: `No open RFCs this week.`, + blocks.push({ + type: "section", + text: { + type: "plain_text", + text: "No open RFCs this week", + }, }) - this.log(payload) + this.log(JSON.stringify({ blocks })) return + } else { + let text: string + if (search.issueCount === 1) { + text = `There is <${RFCs.SearchURL}|*1 open RFC*>:` + } else { + text = `There are <${RFCs.SearchURL}|*${search.issueCount} open RFCs*>:` + } + blocks.push({ + type: "section", + text: { + type: "mrkdwn", + text, + }, + }) } - const attachments = search.nodes?.map(issue => { - if (issue?.__typename === "Issue" || issue?.__typename === "PullRequest") { - return { - fallback: "Open RFCs", - color: "#36a64f", - author_name: issue.author?.login, - author_link: issue.author?.url, - author_icon: issue.author?.avatarUrl, - title: issue.title, - title_link: issue.url, + search.nodes?.forEach((issue, index) => { + if ( + issue?.__typename === "Issue" || + issue?.__typename === "PullRequest" + ) { + let issueText = `<${issue.url}|${issue.title}>` + + if (issue.timelineItems.nodes?.length) { + const comment = issue.timelineItems.nodes[0] + if (comment?.__typename === "IssueComment") { + issueText += `\n\n:speech_balloon: _Last comment on by <${ + issue.author?.url + }|${issue.author?.login}>_` + } + } + + blocks.push( + ...[ + { + type: "section", + text: { + type: "mrkdwn", + text: issueText, + }, + }, + { + type: "context", + elements: [ + { + type: "image", + image_url: issue.author?.avatarUrl, + alt_text: issue.author?.login, + }, + { + type: "mrkdwn", + text: `Created by <${issue.author?.url}|${ + issue.author?.login + }> on / ${ + issue.participants.totalCount + } participants`, + }, + ], + }, + ] + ) + + if (index < search.issueCount - 1) { + blocks.push({ type: "divider" }) } } }) - const text = - search.issueCount === 1 - ? `There is one open RFC:` - : `There are ${search.issueCount} open RFCs:` - const payload = JSON.stringify({ - text, - attachments, - unfurl_links: false, + blocks, }) this.log(payload) } + + convertTimestampToEpoch(timestamp: string) { + return +Date.parse(timestamp) / 1000 + } } diff --git a/src/generate-types.js b/src/generate-types.js index 31295528..b3f83b52 100644 --- a/src/generate-types.js +++ b/src/generate-types.js @@ -1,9 +1,9 @@ -const fetch = require('cross-fetch'); -const fs = require('fs'); +const fetch = require("cross-fetch") +const fs = require("fs") fetch(`https://api.github.com/graphql`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ variables: {}, query: ` @@ -21,24 +21,29 @@ fetch(`https://api.github.com/graphql`, { `, }), headers: { - authorization: `token d8aead0d846a16777eb47b751c0bc975be33814b` + authorization: `token d8aead0d846a16777eb47b751c0bc975be33814b`, }, -}).then(result => { console.log(result.json()); return result.json()}) +}) .then(result => { - const possibleTypes = {}; + console.log(result.json()) + return result.json() + }) + .then(result => { + const possibleTypes = {} result.data.__schema.types.forEach(supertype => { if (supertype.possibleTypes) { - possibleTypes[supertype.name] = - supertype.possibleTypes.map(subtype => subtype.name); + possibleTypes[supertype.name] = supertype.possibleTypes.map( + subtype => subtype.name + ) } - }); + }) - fs.writeFile('./possibleTypes.json', JSON.stringify(possibleTypes), err => { + fs.writeFile("./possibleTypes.json", JSON.stringify(possibleTypes), err => { if (err) { - console.error('Error writing possibleTypes.json', err); + console.error("Error writing possibleTypes.json", err) } else { - console.log('Fragment types successfully extracted!'); + console.log("Fragment types successfully extracted!") } - }); - }); \ No newline at end of file + }) + }) diff --git a/src/queries/open-rfcs.graphql b/src/queries/open-rfcs.graphql index 6148f63a..fda0cf42 100644 --- a/src/queries/open-rfcs.graphql +++ b/src/queries/open-rfcs.graphql @@ -1,27 +1,77 @@ query OpenRequestsForComments { - search( - query: "org:Artsy label:RFC state:open" - type: ISSUE - first: 100 - ) { + search(query: "org:Artsy label:RFC state:open", type: ISSUE, first: 20) { + __typename issueCount nodes { - ...on Issue { + __typename + + ... on Issue { + participants { + __typename + totalCount + } + timelineItems(last: 1, itemTypes: ISSUE_COMMENT) { + __typename + nodes { + __typename + + ... on IssueComment { + createdAt + url + + author { + __typename + avatarUrl + login + url + } + } + } + } + createdAt title url author { + __typename + avatarUrl login url - avatarUrl } } ... on PullRequest { + createdAt title url + author { + __typename + avatarUrl login url - avatarUrl + } + + participants { + __typename + totalCount + } + + timelineItems(last: 1, itemTypes: ISSUE_COMMENT) { + __typename + nodes { + __typename + + ... on IssueComment { + createdAt + url + + author { + __typename + avatarUrl + login + url + } + } + } } } } diff --git a/src/utils/github.ts b/src/utils/github.ts index 39dfeff3..1cc3f7af 100644 --- a/src/utils/github.ts +++ b/src/utils/github.ts @@ -12,14 +12,12 @@ const { schema } = require("@octokit/graphql-schema") export function githubClient(): ApolloClient { if (!process.env.GITHUB_TOKEN) { - throw new Error( - "You need to provide a `GITHUB_TOKEN` env variable." - ); + throw new Error("You need to provide a `GITHUB_TOKEN` env variable.") } const fragmentMatcher = new IntrospectionFragmentMatcher({ - introspectionQueryResultData: schema.json - }); + introspectionQueryResultData: schema.json, + }) return new ApolloClient({ link: new HttpLink({ diff --git a/test/commands/scheduled/rfcs.test.ts b/test/commands/scheduled/rfcs.test.ts index 8e53f9d0..43cd8d60 100644 --- a/test/commands/scheduled/rfcs.test.ts +++ b/test/commands/scheduled/rfcs.test.ts @@ -1,4 +1,5 @@ import { expect, test } from "@oclif/test" +import { OpenRequestsForCommentsFixture } from "../../fixtures/open-rfcs" describe("scheduled:rfcs", () => { beforeEach(() => { @@ -11,44 +12,47 @@ describe("scheduled:rfcs", () => { test .nock("https://api.github.com", api => - api.post("/graphql").reply(200, { - data: { - search: { - __typename: "SearchResultItemConnection", - issueCount: 1, - nodes: [{ - __typename: "Issue", - author: { - __typename: "User", - login: "dblandin", - avatarUrl: "https://example.com/avatar.jpg", - url: "https://example.com/dblandin" - }, - title: "Test", - url: "https://example.com/issues/1" - }], - }, - }, - }) + api.post("/graphql").reply(200, OpenRequestsForCommentsFixture) ) .stdout() .command(["scheduled:rfcs"]) .it("returns Slack-formatted open RFCs message", ctx => { expect(ctx.stdout.trim()).to.eq( JSON.stringify({ - text: "There is one open RFC:", - attachments: [ + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: + "There is :", + }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: + "\n\n:speech_balloon: _Last comment on by _", + }, + }, { - fallback: "Open RFCs", - color: "#36a64f", - author_name: "dblandin", - author_link: "https://example.com/dblandin", - author_icon: "https://example.com/avatar.jpg", - title: "Test", - title_link: "https://example.com/issues/1" + type: "context", + elements: [ + { + type: "image", + image_url: + "https://avatars.githubusercontent.com/u/28120?u=cdbf28a4a864baaef4ef0e054b60bd5d5517a87b&v=4", + alt_text: "joeyAghion", + }, + { + type: "mrkdwn", + text: + "Created by on / 6 participants", + }, + ], }, ], - unfurl_links: false, }) ) }) diff --git a/test/fixtures/open-rfcs.ts b/test/fixtures/open-rfcs.ts index b1e31588..9d9873c1 100644 --- a/test/fixtures/open-rfcs.ts +++ b/test/fixtures/open-rfcs.ts @@ -27,6 +27,8 @@ const OpenRequestsForCommentsFixture = { { __typename: "IssueComment", createdAt: "2021-02-10T00:20:46Z", + url: + "https://github.com/artsy/README/issues/364#issuecomment-776012598", author: { __typename: "User", avatarUrl: