diff --git a/README.md b/README.md index a93e325..4cb19b0 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ You'll also need a GitHub token and a config file. (Keep reading for more info o 1. Run `npm install oss-mariner` 1. Add `"type": "module"` to `package.json` to allow using "import" rather than "require". 1. Get a GitHub token. [See instructions here](https://github.com/indeedeng/Mariner#token) -1. Store your GitHub token in your system's environment by running `export MARINER_GITHUB_TOKEN={Insert your GitHub token here}`. You will either have to do this once each time you restart your system, or else configure your system to do so automatically. +1. Store your GitHub token in your system's environment by running `export MARINER_GITHUB_TOKEN={Insert your GitHub token here}`. You will either have to do this once each time you restart your system, or else configure your system to do so automatically. 1. Finally, run the application to find open issues in your dependencies, using the command `node index.js`. ### Optional: Generating HTML @@ -64,58 +64,74 @@ You'll also need a GitHub token and a config file. (Keep reading for more info o ```html

facebook/jest

- - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + +
TitleAge
Rework asynchronous tests documentation15 days
Use Admonitions on website22 days
[Bug]: test `notify › does not report --notify flag` is flaky17 days
TitleAge
+ Rework asynchronous tests documentation + 15 daysJavaScript,CSS,Shell,Handlebars,Prolog
+ Use Admonitions on website + 22 daysJavaScript,CSS,TypeScript,Shell,Handlebars,Prolog
+ [Bug]: test `notify › does not report --notify flag` is flaky + 17 daysJavaScript,CSS,TypeScript,Shell,Handlebars,Prolog
``` ### Optional: Generating Markup -- You can generate markup for use in Confluence/jira -- The `generateConfluenceMarkup()` creates the markup based on two parameters: `maxIssuesAge` and `issuesByDependency` -- `maxIssueAge` defaults to 30 days, anything over 30 days won't get written, You can edit this number. -- Square brackets and curly braces in issue titles will be replaced by parentheses. -- Example of confluenceMarkup output: +- You can generate markup for use in Confluence/jira +- The `generateConfluenceMarkup()` creates the markup based on two parameters: `maxIssuesAge` and `issuesByDependency` +- `maxIssueAge` defaults to 30 days, anything over 30 days won't get written, You can edit this number. +- Square brackets and curly braces in issue titles will be replaced by parentheses. +- Example of confluenceMarkup output: ```md h2. Updated: February 22, 2021, 5:38 PM PST h3. babel/babel -||*Title*||*Age*|| -|[all the core-js imports are removed|https://github.com/babel/babel/issues/12545]|62 days| - +||_Title_||_Age_||_Languages_|| +|[all the core-js imports are removed|https://github.com/babel/babel/issues/12545]|62 days|Javascript| h3. facebook/jest -||*Title*||*Age*|| -|[Lost of context between tests when using dynamic ESM import|https://github.com/facebook/jest/issues/10944]|72 days| +||_Title_||_Age_||_Languages_|| +|[Lost of context between tests when using dynamic ESM import|https://github.com/facebook/jest/issues/10944]|72 days|Typescript, Javascript| ``` ### Optional: Generating Markdown -- You can generate markdown for use in GitHub -- The `generateGitHubMarkdown()` creates the markdown based on two parameters: `maxIssuesAge` and `issuesByDependency` -- `maxIssueAge` defaults to 30 days, anything over 30 days won't get written, You can edit this number. -- Example of GitHub markdown output: + +- You can generate markdown for use in GitHub +- The `generateGitHubMarkdown()` creates the markdown based on two parameters: `maxIssuesAge` and `issuesByDependency` +- `maxIssueAge` defaults to 30 days, anything over 30 days won't get written, You can edit this number. +- Example of GitHub markdown output: ```md ## Updated: 2022-01-18T22:53:35.522Z ### babel/babel + |**Title**|**Age**| |:----|:----| -|[[Bug]: Typescript plugin fails on named tuple positions where the name is a reserved word in JS|https://github.com/babel/babel/issues/13702]|147 days| +|[[Bug]: Typescript plugin fails on named tuple positions where the name is a reserved word in JS|https://github.com/babel/babel/issues/13702]|147 days|Typescript| |[[preset-env] all the core-js imports are removed|https://github.com/babel/babel/issues/12545]|392 days| -|[[Bug]: TypeError: Error while loading config - yield* (intermediate value) is not iterable|https://github.com/babel/babel/issues/13462]|218 days| +| [[Bug]: TypeError: Error while loading config - yield\* (intermediate value) is not iterable|https://github.com/babel/babel/issues/13462]|218 days|Typescript| ``` ### Config.json Format @@ -151,6 +167,7 @@ The output file is a JSON file in the format: "title": "Issue Title 1", "createdAt": "2020-10-16T01:07:36Z", "repositoryNameWithOwner": "repository/name", + "languages": ["JavaScript", "TypeScript", "CSS"], "url": "https://github.com/repository/name/issues/65", "updatedAt": "2020-10-16T01:07:36Z", "labels": [ @@ -162,6 +179,7 @@ The output file is a JSON file in the format: "title": "Issue Title 2", "createdAt": "2020-10-12T22:37:17Z", "repositoryNameWithOwner": "repository/name", + "languages": ["JavaScript"], "url": "https://github.com/repository/name/issues/58", "updatedAt": "2020-10-12T22:37:17Z", "labels": [ @@ -175,6 +193,7 @@ The output file is a JSON file in the format: "title": "Issue 102", "createdAt": "2020-10-03T13:16:58Z", "repositoryNameWithOwner": "respository/second_name", + "languages": ["JavaScript", "Rust"], "url": "https://github.com/respository/second_name/issues/12137", "updatedAt": "2020-10-03T13:16:58Z", "labels": [ diff --git a/src/Utilities/generateConfluenceMarkup.ts b/src/Utilities/generateConfluenceMarkup.ts index 1f70757..3527c65 100644 --- a/src/Utilities/generateConfluenceMarkup.ts +++ b/src/Utilities/generateConfluenceMarkup.ts @@ -29,13 +29,14 @@ export function generateConfluenceMarkup( markupArray.push('\n'); markupArray.push(`h3. ${dependency}`); - markupArray.push('||*Title*||*Age*||'); + markupArray.push('||*Title*||*Age*||*Languages*||'); relevantIssues.forEach((issue) => { const ageInWholeDays = calculateAgeInWholeDays(issue.createdAt, now); - const cleanedTitleMarkup = removeBracesAndBrackets(issue.title); - markupArray.push(`|[${cleanedTitleMarkup}|${issue.url}]|${ageInWholeDays} days|`); + markupArray.push( + `|[${cleanedTitleMarkup}|${issue.url}]|${ageInWholeDays} days|${issue.languages}|` + ); }); } diff --git a/src/Utilities/generateGitHubMarkdown.ts b/src/Utilities/generateGitHubMarkdown.ts index 557137e..001a234 100644 --- a/src/Utilities/generateGitHubMarkdown.ts +++ b/src/Utilities/generateGitHubMarkdown.ts @@ -27,14 +27,14 @@ export function generateGitHubMarkdown( markdownArray.push('\n'); markdownArray.push(`### ${dependency}`); - markdownArray.push('|**Title**|**Age**|'); + markdownArray.push('|**Title**|**Age**|**Languages**|'); markdownArray.push('|:----|:----|'); relevantIssues.forEach((issue) => { const ageInWholeDays = calculateAgeInWholeDays(issue.createdAt, now); const cleanTitleMarkdown = removeBracesAndBrackets(issue.title); markdownArray.push( - `|[${cleanTitleMarkdown}|${issue.url}]|${ageInWholeDays} days|` + `|[${cleanTitleMarkdown}|${issue.url}]|${ageInWholeDays} days|${issue.languages}|` ); }); } diff --git a/src/Utilities/generateHtml.ts b/src/Utilities/generateHtml.ts index f842804..39c2a4d 100644 --- a/src/Utilities/generateHtml.ts +++ b/src/Utilities/generateHtml.ts @@ -44,7 +44,9 @@ function generateHtmlFragmentsForDependency( const encodedDependencyName = encode(dependencyName); arrayOfHtmlFragments.push(`

${encodedDependencyName}

`); arrayOfHtmlFragments.push(''); - arrayOfHtmlFragments.push(''); + arrayOfHtmlFragments.push( + '' + ); relevantIssues.forEach((issue) => { const ageInWholeDays = calculateAgeInWholeDays(issue.createdAt, now); @@ -54,6 +56,7 @@ function generateHtmlFragmentsForDependency( arrayOfHtmlFragments.push(''); arrayOfHtmlFragments.push(``); arrayOfHtmlFragments.push(``); + arrayOfHtmlFragments.push(``); arrayOfHtmlFragments.push(''); }); diff --git a/src/__tests__/generateConfluenceMarkup.test.ts b/src/__tests__/generateConfluenceMarkup.test.ts index 30ce81d..96b9bda 100644 --- a/src/__tests__/generateConfluenceMarkup.test.ts +++ b/src/__tests__/generateConfluenceMarkup.test.ts @@ -13,6 +13,7 @@ const fakeIssues: Issue[] = [ url: 'https://github.com/bc/typescript/issues/30', updatedAt: '', labels: ['help wanted', 'documentation'], + languages: ['JavaScript', 'Typescript', 'Python'], }, { @@ -22,6 +23,7 @@ const fakeIssues: Issue[] = [ url: 'https://github.com/material-ui/issues/24', updatedAt: '', labels: ['good first issue', 'help wanted', 'documentation'], + languages: ['JavaScript', 'CSS', 'Shell'], }, ]; @@ -33,6 +35,7 @@ const singleIssue: Issue[] = [ url: 'https://github.com/marmelab/react-admin/issues/5620', updatedAt: twoDaysAgo, labels: ['good first issue', 'documentation'], + languages: ['Typescript', 'JavaScript'], }, ]; @@ -45,7 +48,7 @@ describe('generateConfluenceMarkup function', () => { const oneDependencyNoIssue = issuesByDependency.set(dependency, noIssues); const results = mariner.generateConfluenceMarkup(oneDependencyNoIssue); expect(results).not.toContain(dependency); - expect(results).not.toContain('||*Title*||*Age*||'); + expect(results).not.toContain('||*Title*||*Age*||*Languages*||'); expect(results).not.toContain('days'); }); @@ -56,8 +59,12 @@ describe('generateConfluenceMarkup function', () => { const twoIssues = mockDependencyMap.set(dependency, fakeIssues); const results = mariner.generateConfluenceMarkup(twoIssues); - expect(results).toContain(`|[${fakeIssues[0].title}|${fakeIssues[0].url}]|8 days|`); - expect(results).toContain(`|[${fakeIssues[1].title}|${fakeIssues[1].url}]|8 days|`); + expect(results).toContain( + `|[${fakeIssues[0].title}|${fakeIssues[0].url}]|8 days|${fakeIssues[0].languages}|` + ); + expect(results).toContain( + `|[${fakeIssues[1].title}|${fakeIssues[1].url}]|8 days|${fakeIssues[1].languages}` + ); }); it('should include both dependencies that have issues', () => { const mockDependencyMap: Map = new Map(); @@ -70,13 +77,19 @@ describe('generateConfluenceMarkup function', () => { const results = mariner.generateConfluenceMarkup(mockDependencyMap); expect(results).toContain(`h3. ${dependency1}`); - expect(results).toContain('||*Title*||*Age*||'); - expect(results).toContain(`|[${fakeIssues[0].title}|${fakeIssues[0].url}]|8 days|`); - expect(results).toContain(`|[${fakeIssues[1].title}|${fakeIssues[1].url}]|8 days|`); + expect(results).toContain('||*Title*||*Age*||*Languages*||'); + expect(results).toContain( + `|[${fakeIssues[0].title}|${fakeIssues[0].url}]|8 days|${fakeIssues[0].languages}|` + ); + expect(results).toContain( + `|[${fakeIssues[1].title}|${fakeIssues[1].url}]|8 days|${fakeIssues[1].languages}|` + ); expect(results).toContain(`h3. ${dependency2}`); - expect(results).toContain('||*Title*||*Age*||'); - expect(results).toContain(`|[${singleIssue[0].title}|${singleIssue[0].url}]|8 days|`); + expect(results).toContain('||*Title*||*Age*||*Languages*||'); + expect(results).toContain( + `|[${singleIssue[0].title}|${singleIssue[0].url}]|8 days|${singleIssue[0].languages}` + ); }); it('should remove curly braces and square brackets from an issue title', () => { const mockDependencyMap: Map = new Map(); @@ -87,7 +100,7 @@ describe('generateConfluenceMarkup function', () => { const results = mariner.generateConfluenceMarkup(mockDependencyMap); expect(results).not.toContain(singleIssue[0].title); expect(results).toMatch( - `|[(Navigation Editor) Dropdown menus too narrow ()|${singleIssue[0].url}]|8 days|` + `|[(Navigation Editor) Dropdown menus too narrow ()|${singleIssue[0].url}]|8 days|${singleIssue[0].languages}` ); }); it('should return correct markup for a dependency and an issue', () => { @@ -100,7 +113,7 @@ describe('generateConfluenceMarkup function', () => { mockDependencyMap.set(dependency, singleIssue); const results = mariner.generateConfluenceMarkup(mockDependencyMap); expect(results).toContain(`h3. ${dependency}`); - expect(results).toContain('||*Title*||*Age*||'); + expect(results).toContain('||*Title*||*Age*||*Languages*||'); expect(results).toContain(`|[${singleIssue[0].title}|${singleIssue[0].url}]|`); expect(results).toContain(`|${ageInWholeDays} days|`); }); @@ -113,7 +126,7 @@ describe('generateConfluenceMarkup function', () => { const results = mariner.generateConfluenceMarkup(mockDependencyMap); expect(results).not.toContainEqual(`h3. ${dependency}`); - expect(results).not.toContainEqual('\n||*Title*||*Age*||'); + expect(results).not.toContainEqual('\n||*Title*||*Age*||*Languages*||'); expect(results).not.toContainEqual(singleIssue[0].title); }); }); diff --git a/src/__tests__/generateGitHubMarkdown.test.ts b/src/__tests__/generateGitHubMarkdown.test.ts index c73f4ce..ff9d6e4 100644 --- a/src/__tests__/generateGitHubMarkdown.test.ts +++ b/src/__tests__/generateGitHubMarkdown.test.ts @@ -13,6 +13,7 @@ const fakeIssues: Issue[] = [ url: 'https://github.com/moment/luxon/issues/1107', updatedAt: '', labels: ['help wanted', 'enhancement'], + languages: ['Javascript', 'Typescript'], }, { @@ -22,6 +23,7 @@ const fakeIssues: Issue[] = [ url: 'https://github.com/expressjs/express/issues/4769', updatedAt: '', labels: ['good first issue', 'help wanted', 'question'], + languages: ['Javascript', 'Python'], }, ]; @@ -33,6 +35,7 @@ const singleIssue: Issue[] = [ url: 'https://github.com/sinonjs/sinon/issues/1898', updatedAt: twoDaysAgo, labels: ['good first issue', 'documentation', 'help wanted'], + languages: ['Javascript'], }, ]; @@ -45,7 +48,7 @@ describe('generateGithubMarkdown function', () => { const oneDependencyNoIssue = issuesByDependency.set(dependency, noIssues); const results = mariner.generateGitHubMarkdown(oneDependencyNoIssue); expect(results).not.toContain(dependency); - expect(results).not.toContain('||*Title*||*Age*||'); + expect(results).not.toContain('||*Title*||*Age*||*Languages*||'); expect(results).not.toContain('days'); }); @@ -56,8 +59,12 @@ describe('generateGithubMarkdown function', () => { const twoIssues = mockDependencyMap.set(dependency, fakeIssues); const results = mariner.generateGitHubMarkdown(twoIssues); - expect(results).toContain(`|[${fakeIssues[0].title}|${fakeIssues[0].url}]|8 days|`); - expect(results).toContain(`|[${fakeIssues[1].title}|${fakeIssues[1].url}]|8 days|`); + expect(results).toContain( + `|[${fakeIssues[0].title}|${fakeIssues[0].url}]|8 days|${fakeIssues[0].languages}|` + ); + expect(results).toContain( + `|[${fakeIssues[1].title}|${fakeIssues[1].url}]|8 days|${fakeIssues[1].languages}|` + ); }); it('should include both dependencies that have issues', () => { const mockDependencyMap: Map = new Map(); @@ -70,13 +77,19 @@ describe('generateGithubMarkdown function', () => { const results = mariner.generateGitHubMarkdown(mockDependencyMap); expect(results).toContain(`### ${dependency1}`); - expect(results).toContain('|**Title**|**Age**|'); - expect(results).toContain(`|[${fakeIssues[0].title}|${fakeIssues[0].url}]|8 days|`); - expect(results).toContain(`|[${fakeIssues[1].title}|${fakeIssues[1].url}]|8 days|`); + expect(results).toContain('|**Title**|**Age**|**Languages**|'); + expect(results).toContain( + `|[${fakeIssues[0].title}|${fakeIssues[0].url}]|8 days|${fakeIssues[0].languages}|` + ); + expect(results).toContain( + `|[${fakeIssues[1].title}|${fakeIssues[1].url}]|8 days|${fakeIssues[1].languages}|` + ); expect(results).toContain(`### ${dependency2}`); - expect(results).toContain('|**Title**|**Age**|'); - expect(results).toContain(`|[${singleIssue[0].title}|${singleIssue[0].url}]|8 days|`); + expect(results).toContain('|**Title**|**Age**|**Languages**'); + expect(results).toContain( + `|[${singleIssue[0].title}|${singleIssue[0].url}]|8 days|${singleIssue[0].languages}|` + ); }); it('should remove curly braces and square brackets from an issue title', () => { const mockDependencyMap: Map = new Map(); @@ -90,7 +103,7 @@ describe('generateGithubMarkdown function', () => { `|[(Navigation Editor) Dropdown menus too narrow ()|${singleIssue[0].url}]|8 days|` ); }); - it('should return correct markup for a dependency and an issue', () => { + it('should return correct markdown for a dependency and an issue', () => { const mockDependencyMap: Map = new Map(); const dependency = 'OSS'; singleIssue[0].title = 'Fixed interface'; @@ -100,9 +113,10 @@ describe('generateGithubMarkdown function', () => { mockDependencyMap.set(dependency, singleIssue); const results = mariner.generateGitHubMarkdown(mockDependencyMap); expect(results).toContain(`### ${dependency}`); - expect(results).toContain('|**Title**|**Age**|'); + expect(results).toContain('|**Title**|**Age**|**Languages**|'); expect(results).toContain(`|[${singleIssue[0].title}|${singleIssue[0].url}]|`); expect(results).toContain(`|${ageInWholeDays} days|`); + expect(results).toContain(`|${singleIssue[0].languages}|`); }); it('should not list a dependency with no issues if its issue is too old', () => { const mockDependencyMap: Map = new Map(); @@ -116,7 +130,8 @@ describe('generateGithubMarkdown function', () => { const results = mariner.generateGitHubMarkdown(mockDependencyMap); expect(results).toContain(`## Updated: ${date}`); expect(results).not.toContainEqual(`### ${dependency}`); - expect(results).not.toContainEqual('\n|**Title**|**Age**|'); + expect(results).not.toContainEqual('\n|**Title**|**Age**|**Languages**|'); expect(results).not.toContainEqual(singleIssue[0].title); + expect(results).not.toContainEqual(singleIssue[0].languages); }); }); diff --git a/src/__tests__/generateHtml.test.ts b/src/__tests__/generateHtml.test.ts index bfdb7f7..95d1761 100644 --- a/src/__tests__/generateHtml.test.ts +++ b/src/__tests__/generateHtml.test.ts @@ -15,6 +15,7 @@ const fakeIssues: Issue[] = [ url: 'https://github.com/bc/typescript/issues/30', updatedAt: '', labels: ['help wanted', 'documentation'], + languages: ['JavaScript', 'CSS', 'Shell'], }, { @@ -24,6 +25,7 @@ const fakeIssues: Issue[] = [ url: 'https://github.com/material-ui/issues/24', updatedAt: '', labels: ['good first issue', 'help wanted', 'documentation'], + languages: ['JavaScript', 'CSS', 'Shell'], }, ]; @@ -35,6 +37,7 @@ const singleIssue: Issue[] = [ url: 'https://github.com/marmelab/react-admin/issues/5620', updatedAt: twoDaysAgo, labels: ['good first issue', 'documentation'], + languages: ['JavaScript', 'CSS', 'Shell'], }, ]; @@ -48,12 +51,14 @@ describe('generateHtml function', () => { const results = mariner.generateHtml(mockDependencyMap, maxAgeInDays); expect(results).toContain(`

${dependency}

\n`); expect(results).toContain('
TitleAge
TitleAgeLanguages
${title}${ageInWholeDays} days${issue.languages}
\n'); - expect(results).toContain('\n'); + expect(results).toContain( + '\n' + ); const issue = singleIssue[0]; const titleCellContents = `${encode(issue.title)}`; const ageCellContents = '8 days'; expect(results).toContain( - `\n\n\n` + `\n\n\n\n` ); expect(results).toContain('
TitleAge
TitleAgeLanguages
${titleCellContents}${ageCellContents}
${titleCellContents}${ageCellContents}${issue.languages}
'); }); @@ -103,6 +108,7 @@ describe('generateHtml function', () => { expect(results).not.toContain(dependency); expect(results).not.toContain('Title'); expect(results).not.toContain('Age'); + expect(results).not.toContain('Languages'); expect(results).not.toContain('days'); }); diff --git a/src/gitHubIssueFetcher.ts b/src/gitHubIssueFetcher.ts index 3fd787b..0599310 100644 --- a/src/gitHubIssueFetcher.ts +++ b/src/gitHubIssueFetcher.ts @@ -48,8 +48,14 @@ export interface GitHubIssue { interface GitHubRepository { nameWithOwner: string; + languages: { edges: Languages[] }; } +export interface Languages { + node: { + name: string; + }; +} export interface GitHubLabelEdge { node: { name: string; @@ -83,6 +89,13 @@ query findByLabel($queryString:String!, $pageSize:Int, $maxLabelsToRetrieve:Int, createdAt repository { nameWithOwner + languages(first: 10) { + edges { + node { + name + } + } + } } url updatedAt diff --git a/src/issueFinder.ts b/src/issueFinder.ts index 0d1b038..664788f 100644 --- a/src/issueFinder.ts +++ b/src/issueFinder.ts @@ -1,10 +1,11 @@ -import { GitHubIssueFetcher, GitHubIssue, GitHubLabelEdge } from './gitHubIssueFetcher'; +import { GitHubIssueFetcher, GitHubIssue, GitHubLabelEdge, Languages } from './gitHubIssueFetcher'; import { Config } from './config'; export interface Issue { title: string; createdAt: string; repositoryNameWithOwner: string; + languages: string[]; url: string; updatedAt: string; labels: string[]; @@ -67,6 +68,7 @@ export class IssueFinder { title: node.title, createdAt: node.createdAt, repositoryNameWithOwner: node.repository.nameWithOwner, + languages: this.convertFromGitHubLanguages(node.repository.languages.edges), url: node.url, updatedAt: node.updatedAt, labels: this.convertFromGitHubLabels(node.labels.edges), @@ -75,6 +77,14 @@ export class IssueFinder { return issue; } + private convertFromGitHubLanguages(edges: Languages[]) { + const languages = edges.map((edge) => { + return edge.node.name; + }); + + return languages; + } + private convertFromGitHubLabels(edges: GitHubLabelEdge[]) { const labels = edges.map((edge) => { return edge.node.name;