Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
42 changes: 36 additions & 6 deletions lib/CPAN/Testers/Web.pm
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,44 @@ sub startup ( $app ) {
catdir( dist_dir( 'CPAN-Testers-Web' ), 'public' );
unshift @{ $app->commands->namespaces }, 'CPAN::Testers::Web::Command';


$app->moniker( 'web' );
# This application has no configuration yet
$app->plugin( Config => {
default => {
api_host => 'api.cpantesters.org',
dist_modules => [qw/
Mojolicious
Moose
Moo
DBIx-Class
App-cpanminus
DBI
Plack
DateTime
Devel-NYTProf
Test-Simple
Path-Tiny
Dist-Zilla
Scalar-List-Utils
App-perlbrew
Try-Tiny
libwww-perl
AnyEvent
Catalyst-Runtime
Data-Printer
Dancer
Template-Toolkit
Type-Tiny
Perl-Tidy
Dancer2
Perl-Critic
ack
Carton
Getopt-Long
List-MoreUtils
Task-Kensho
/]
},
} );

Expand Down Expand Up @@ -222,12 +255,9 @@ sub startup ( $app ) {
->name( 'release.dist' )
->to( 'reports#dist_versions' );

$r->get( '/dist' )
->name( 'dist-search' )
->to( cb => sub {
my ( $c ) = @_;
$c->render( 'dist-search' );
} );
$r->get( '/dist' )->name( 'dist-search' )->to( 'dist#search' );
$r->post( '/dist' )->name( 'dist-recent' )->to( 'dist#recent' );
$r->post( '/dist/valid' )->name( 'dist-valid' )->to( 'dist#valid' );

$r->get( '/author/:author', [ format => [qw( html rss json)] ] )
->name( 'reports.author' )
Expand Down
84 changes: 51 additions & 33 deletions lib/CPAN/Testers/Web/Controller/Dist.pm
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,64 @@ Lists modules configured in the config
=cut

sub search ( $c ) {

$c->render('dist-search', {

});
$c->render('dist/search',
dists => $c->config->{dist_modules}
);
}

=method recent_uploads
=method recent

List the recent uploads to CPAN and the reports received so far.
Expects a list of dists and will return the most recent for each.

=cut

sub recent_uploads( $c ) {
# XXX: Disabling for now as too slow
# my $recent_uploads = $c->schema->perl5->resultset('Upload')->recent(20);
# my $rs = $c->schema->perl5->resultset('Release')->search({
# -or => [
# map +{ 'me.dist' => $_->dist, 'me.version' => $_->version }, $recent_uploads->all
# ]
# },
# {
# join => 'upload',
# order_by => { -desc => 'upload.released' },
# group_by => [qw( me.dist me.version )],
# select => [qw( dist version uploadid upload.author upload.released), \('COUNT(*)', 'SUM(pass)', 'SUM(fail)', 'SUM(na)', 'SUM(unknown)') ],
# as => [qw( dist version uploadid author released total pass fail na unknown )],
# }
# );

$c->render( 'reports/recent_uploads',
items => []
# items => [
# map +{
# $_->get_inflated_columns,
# released => $_->upload->released->datetime( ' ' ),
# },
# $rs->all
# ],
);
sub recent ( $c ) {
my $join = $c->schema->perl5->resultset('Release')->search({
dist => { -in => $c->req->json->{dists} }
}, {
group_by => [qw( me.dist )],
select => [qw/dist/, \('MAX(version)')],
as => [qw/dist version/]
});

if (!$join->count) {
return $c->render( json => { dists => [] } );
}

my $rs = $c->schema->perl5->resultset('Release')->search({
-or => [
map +{ 'me.dist' => $_->dist, 'me.version' => $_->version }, $join->all()
],
}, {
join => 'upload'
});

$c->render( json => {
dists => [
map +{
$_->get_inflated_columns,
released => $_->upload->released->datetime( ' ' ),
},
$rs->all
]

} );
}

=method valid

validate that a dist exists

=cut

sub valid ( $c ) {
my $rs = $c->schema->perl5->resultset('Release')->search({
dist => $c->req->json->{dist}
});

$c->render( json => {
ok => $rs->count()
} );
}

1;
10 changes: 10 additions & 0 deletions share/public/css/cpantesters.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ body {
margin-bottom: 1em;
}


#popular tr th:first-child {
min-width: 150px;
}

@media only screen and (max-width: 600px) {
#popular tr th:first-child {
min-width: auto;
}
}
170 changes: 112 additions & 58 deletions share/templates/dist/search.html.ep
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<h1>Search Distributions</h1>

<form class="form-inline">
<form id="search-form" class="form-inline">
<div class="form-group">
<label for="search">Search:</label>
<input id="search" class="form-control"
Expand All @@ -19,7 +19,7 @@

<h2>Results</h2>

<table class="table table-striped">
<table id="popular" class="table table-striped">
<thead>
<tr>
<th>Distribution</th>
Expand All @@ -28,70 +28,124 @@
<th class="text-center">Reports</th>
<th class="text-center">Pass</th>
<th class="text-center">Fail</th>
<th class="text-center">Unkn</th>
<th class="text-center">NA</th>
</tr>
</thead>
<tbody>
<template>
<tr>
<td>
<a data-id="dist" href="/dist/Statocles">Statocles</a>
</td>
<td data-id="version">0.066</td>
<td>
<time data-id="released">2015-01-11 00:12:34</time>
</td>
<td data-id="total" class="text-center">
12
</td>
<td data-id="pass" class="bg-success text-center">
11
</td>
<td data-id="fail" class="text-center">
1
</td>
<td data-id="unknown" class="text-center">
1
</td>
<td data-id="na" class="text-center">
1
</td>
</tr>
</template>

<tr>
<td>
<a href="/dist/Statocles">Statocles</a>
</td>
<td>0.066</td>
<td>
<time>2015-01-11 00:12:34</time>
</td>
<td class="text-center">
12
</td>
<td class="bg-success text-center">
11
</td>
<td class="bg-warning text-center">
1
</td>
</tr>

<tr>
<td>
<a href="/dist/Beam-Wire">Beam-Wire</a>
</td>
<td>1.011</td>
<td>
<time>2015-01-10 01:24:12</time>
</td>
<td class="text-center">
50
</td>
<td class="bg-success text-center">
50
</td>
<td class="bg-success text-center">
0
</td>
</tr>

<tr>
<td>
<a href="/dist/Import-Base">Import-Base</a>
</td>
<td>1.001</td>
<td>
<time>2015-01-07 01:24:12</time>
</td>
<td class="text-center">
74
</td>
<td class="bg-success text-center">
70
</td>
<td class="bg-warning text-center">
4
</td>
</tr>

</tbody>
</table>
<script>
(function () {

let Dist = function (dists) {
this.search_form = document.querySelector('#search-form');
this.tbody = document.querySelector('#popular tbody');
this.template = this.tbody.querySelector('template');
this.init(dists);
};

Dist.prototype = {
init: function (dists) {
this.setupEvents();
this.poll(dists, 0);
},
setupEvents: function () {
let self = this;
let input = self.search_form.querySelector('input#search');
self.search_form.addEventListener('submit', function (evt) {
Copy link
Member

Choose a reason for hiding this comment

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

If you use arrow functions, you can get rid of the need to do let self = this;:

this.search_form.addEventListener('submit', (evt) => {
   // inside here, `this` is the same as outside
});

evt.preventDefault();
self.request('/dist/valid', { dist: input.value }, function (res) {
if (res.ok) {
window.location.pathname = '/dist/' + input.value;
} else {
alert('Distribution with the name ' + input.value + ' not found');
}
});
});
},
poll: function (dists, inc) {
let self = this;
if (inc < 10) inc++;
let pdists = dists.splice(0, inc);
console.log(dists);
self.request('/dist', { dists: pdists }, function (res) {
res.dists.forEach(function (dist) {
self.render_dist(dist);
});
if (dists.length > 0) self.poll(dists, inc);
});
},
request: function (url, params, cb) {
fetch(url, {
method: "POST",
body: JSON.stringify(params),
}).then(function (res) {
return res.json();
}).then(function (res) {
cb(res);
}).catch(function (res) {
console.log(res);
});
},
render_dist: function (dist) {
let tr = this.template.content.cloneNode(true);
let name = tr.querySelector('[data-id="dist"]');
name.innerText = dist.dist;
name.href = '/dist/' + dist.dist;
dist.total = dist.pass + dist.fail + dist.unknown + dist.na;
['version', 'released', 'total', 'pass'].forEach(function (n) {
tr.querySelector('[data-id="' + n + '"]').innerText = dist[n];
});
let fail = tr.querySelector('[data-id="fail"]');
fail.innerText = dist.fail;
fail.classList.add(dist.fail ? 'bg-danger' : 'bg-success');
['unknown', 'na'].forEach(function (n) {
let x = tr.querySelector('[data-id="' + n + '"]');
x.innerText = dist[n];
x.classList.add(dist[n] ? 'bg-warning' : 'bg-success');
});
this.tbody.appendChild(tr);
}
};

let dists = [
Copy link
Member

Choose a reason for hiding this comment

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

This might be better as let dists = <%= encode_json $dists %>, but that's just an opinion, not a requirement :)

% for my $dist (@$dists) {
"<%= $dist %>",
% }
];

new Dist(dists);
})();
</script>
</div>
</div>
</div>