Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GitHub] Top language, languages count and code size badges #1160

Merged
merged 6 commits into from
Oct 12, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3906,6 +3906,71 @@ cache(function(data, match, sendBadge, request) {
});
}));

// GitHub languages integration.
camp.route(/^\/github\/languages\/(top|count|bytes)\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
cache(function(data, match, sendBadge, request) {
var type = match[1];
var user = match[2];
var repo = match[3];
var format = match[4];
var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo + '/languages';
var badgeData = getBadgeData('', data);
if (badgeData.template === 'social') {
badgeData.logo = getLogo('github', data);
}
githubAuth.request(request, apiUrl, {}, function(err, res, buffer) {
if (err != null) {
badgeData.text[1] = 'inaccessible';
sendBadge(format, badgeData);
return;
}
try {
const parsedData = JSON.parse(buffer);
var sumBytes = 0;
switch(type) {
case 'top':
var topLanguage = 'language';
var maxBytes = 0;
for (const language of Object.keys(parsedData)) {
const bytes = parseInt(parsedData[language]);
if (bytes >= maxBytes) {
maxBytes = bytes;
topLanguage = language;
}
sumBytes += bytes;
}
badgeData.text[0] = topLanguage;
if (sumBytes === 0) { // eg, empty repo, only .md files, etc.
badgeData.text[1] = 'none';
badgeData.colorscheme = 'blue';
} else {
badgeData.text[1] = (maxBytes / sumBytes * 100).toFixed(1) + '%'; // eg, 9.1%
}
break;
case 'count':
badgeData.text[0] = 'languages';
badgeData.text[1] = Object.keys(parsedData).length;
badgeData.colorscheme = 'blue';
break;
case 'bytes':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about code-size or size to match the label?

for (const language of Object.keys(parsedData)) {
sumBytes += parseInt(parsedData[language]);
}
badgeData.text[0] = 'code size';
badgeData.text[1] = metric(sumBytes) + 'B';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use prettyBytes instead?

badgeData.colorscheme = 'blue';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really glad to have a repo size badge! Though, it would be more reliable to get this information from the Repositories endpoint. Would you be up for implementing it that way instead, under a different URL path?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you get the code size directly using that endpoint? The size parameter seemed to be a good candidate, but must actually relate to something else, as it indicates only 4945 for this repository for instance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is right. It's in kilobytes. Here's a StackOverflow answer that talks about it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I think about it, the repo size is more about how many revisions and binary blobs are checked in. The size of the identifiable code could be useful, distinct from the repo size.

Do they provide lines of code? That would also be really cool. 😃

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it's the size on disk rather than the code size. I would be inclined to keep the code size badge as it is, but I can also provide a repo size badge through a new pull request, if you want!
They do not provide lines of code directly, but this StackOverflow answer suggests a nice approach to get it. Yet another pull request coming up? 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Yes, a separate badge + PR for repo size makes sense to me.

Wow, if that SO answer works, that would be cool!

Copy link
Member Author

@PyvesB PyvesB Oct 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll take care of that!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the solutions proposed to calculate the lines of code using the API are dodgy according to a comment on the answer I had previously linked. I don't think a line count badge can be implemented reliably and efficiently with the current API possibilities.

break;
default:
throw Error('Unreachable due to regex');
}
sendBadge(format, badgeData);
} catch(e) {
badgeData.text[1] = 'invalid';
sendBadge(format, badgeData);
}
});
}));

// Bitbucket issues integration.
camp.route(/^\/bitbucket\/issues(-raw)?\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
cache(function(data, match, sendBadge, request) {
Expand Down
28 changes: 28 additions & 0 deletions service-tests/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,31 @@ t.create('github pull request check state')
t.create('github pull request check contexts')
.get('/status/contexts/pulls/badges/shields/1110.json')
.expectJSONTypes(Joi.object().keys({ name: 'checks', value: '1 failure' }));

t.create('top language')
.get('/languages/top/badges/shields.json')
.expectJSONTypes(Joi.object().keys({
name: Joi.equal('JavaScript'),
value: Joi.string().regex(/^([1-9]?[0-9]\.[0-9]|100\.0)%$/),
}));

t.create('top language with empty repository')
.get('/languages/top/pyvesb/emptyrepo.json')
.expectJSONTypes(Joi.object().keys({
name: Joi.equal('language'),
value: Joi.equal('none'),
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can tighten this up a bit since these are literals:

.expectJSON({ name: 'language', value: 'none' })

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


t.create('language count')
.get('/languages/count/badges/shields.json')
.expectJSONTypes(Joi.object().keys({
name: Joi.equal('languages'),
value: Joi.string().regex(/^[0-9]*$/),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Joi.number().integer().positive() should work here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Fixed.

}));

t.create('code size in bytes for all languages')
.get('/languages/bytes/badges/shields.json')
.expectJSONTypes(Joi.object().keys({
name: Joi.equal('code size'),
value: Joi.string().regex(/^[0-9]*(k|M|G|T|P|E|Z|Y)B$/),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the isFileSize helper from service-tests/helpers/validators.js.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I had forked the repository before the helpers were added, this is now fixed.

}));
12 changes: 12 additions & 0 deletions try.html
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,18 @@ <h3 id="miscellaneous"> Miscellaneous </h3>
<td><img src='/github/last-commit/google/skia/infra/config.svg' alt=''/></td>
<td><code>https://img.shields.io/github/commits/google/skia/infra/config/last.svg</code></td>
</tr>
<tr><th data-keywords='GitHub top language' data-doc='githubDoc'> GitHub top language: </th>
<td><img src='/github/languages/top/badges/shields.svg' alt=''/></td>
<td><code>https://img.shields.io/github/languages/top/badges/shields.svg</code></td>
</tr>
<tr><th data-keywords='GitHub language count' data-doc='githubDoc'> GitHub language count: </th>
<td><img src='/github/languages/count/badges/shields.svg' alt=''/></td>
<td><code>https://img.shields.io/github/languages/count/badges/shields.svg</code></td>
</tr>
<tr><th data-keywords='GitHub byte code size' data-doc='githubDoc'> GitHub code size in bytes: </th>
<td><img src='/github/languages/bytes/badges/shields.svg' alt=''/></td>
<td><code>https://img.shields.io/github/languages/bytes/badges/shields.svg</code></td>
</tr>
<tr><th> Bitbucket issues: </th>
<td><img src='/bitbucket/issues/atlassian/python-bitbucket.svg' alt=''/></td>
<td><code>https://img.shields.io/bitbucket/issues/atlassian/python-bitbucket.svg</code></td>
Expand Down