-
Notifications
You must be signed in to change notification settings - Fork 5
Dist landing page #33
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
base: master
Are you sure you want to change the base?
Changes from all commits
92ce577
81159f0
0662141
a743e1d
3d38af6
d7d753f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| package CPAN::Testers::Web::Controller::Dist; | ||
| our $VERSION = '0.001'; | ||
| # ABSTRACT: Endpoints for viewing and managing distributions | ||
|
|
||
| =head1 DESCRIPTION | ||
|
|
||
| =head1 SEE ALSO | ||
|
|
||
| =cut | ||
|
|
||
| use Mojo::Base 'Mojolicious::Controller'; | ||
| use CPAN::Testers::Web::Base; | ||
| use CPAN::Testers::Web::Controller::Legacy; | ||
|
|
||
| =method search | ||
|
|
||
| Landing page for /dist | ||
|
|
||
| Lists modules configured in the config | ||
|
|
||
| =cut | ||
|
|
||
| sub search ( $c ) { | ||
| $c->render('dist/search', | ||
| dists => $c->config->{dist_modules} | ||
| ); | ||
| } | ||
|
|
||
| =method recent | ||
|
|
||
| Expects a list of dists and will return the most recent for each. | ||
|
|
||
| =cut | ||
|
|
||
| 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 => { like => $c->req->json->{dist} . '%' } | ||
| }, { | ||
| distinct => 1, | ||
| select => ['dist'], | ||
| as => ['dist'], | ||
| rows => 50 | ||
| }); | ||
|
|
||
| $c->render( json => { | ||
| dists => [ | ||
| map { $_->dist } | ||
| $rs->all | ||
| ] | ||
| } ); | ||
| } | ||
|
|
||
| 1; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
|
|
||
| % title 'Search Distributions'; | ||
|
|
||
| <div class="container"> | ||
| <div class="row"> | ||
| <div class="col-md-12"> | ||
|
|
||
| <h1>Search Distributions</h1> | ||
|
|
||
| <form id="search-form" class="form-inline"> | ||
| <div class="form-group"> | ||
| <label for="search">Search:</label> | ||
| <input id="search" class="form-control" | ||
| placeholder="Search Distributions" | ||
| > | ||
| </div> | ||
| <button class="btn btn-primary">Search</button> | ||
| </form> | ||
|
|
||
| <h2 id="search-title">Popular Distributions</h2> | ||
|
|
||
| <table id="popular" class="table table-striped"> | ||
| <thead> | ||
| <tr> | ||
| <th>Distribution</th> | ||
| <th>Last Version</th> | ||
| <th>Released</th> | ||
| <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> | ||
|
|
||
|
|
||
| </tbody> | ||
| </table> | ||
| <script> | ||
| (function () { | ||
|
|
||
| let Dist = function (dists) { | ||
| this.title = document.querySelector('#search-title'); | ||
| this.search_form = document.querySelector('#search-form'); | ||
| this.tbody = document.querySelector('#popular tbody'); | ||
| this.template = this.tbody.querySelector('template'); | ||
| this.init(dists); | ||
| }; | ||
|
|
||
| Dist.prototype = { | ||
| active_type: 'popular', | ||
| init: function (dists) { | ||
| this.setupEvents(); | ||
| this.poll(dists, 'popular', 0); | ||
| }, | ||
| setupEvents: function () { | ||
| let self = this; | ||
| let input = self.search_form.querySelector('input#search'); | ||
| let last_search = ""; | ||
| self.search_form.addEventListener('submit', function (evt) { | ||
| evt.preventDefault(); | ||
|
|
||
| if (!input.value) { | ||
| return self.switchPopularMode(); | ||
| } | ||
|
|
||
| if (last_search == input.value) return; | ||
|
|
||
| last_search = input.value; | ||
|
|
||
| self.request('/dist/valid', { dist: input.value }, function (res) { | ||
| if (!res.dists || !res.dists.length) { | ||
| alert('Distribution with the name ' + input.value + ' not found'); | ||
| return; | ||
| } | ||
|
|
||
| self.switchSearchMode(input.value); | ||
|
|
||
| self.tbody.querySelectorAll('.search-dist').forEach(function (n) { | ||
| n.remove(); | ||
| }); | ||
|
|
||
| self.poll(res.dists, input.value, 0); | ||
| }); | ||
| }); | ||
|
|
||
| input.addEventListener('keyup', function () { | ||
| input.value = input.value.replace(new RegExp("([^a-zA-Z0-9\-]+)", "g"), ""); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can do this without JavaScript if you'd like by using the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's just a preference, I prefer validating the input and preventing the characters this way. |
||
| }); | ||
| }, | ||
| switchSearchMode: function (type) { | ||
| let self = this; | ||
|
|
||
| self.active_type = type; | ||
|
|
||
| self.title.innerText = 'Search results'; | ||
|
|
||
| self.tbody.querySelectorAll('.popular-dist').forEach(function (n) { | ||
| n.classList.add('hide');; | ||
| }); | ||
|
|
||
| }, | ||
| switchPopularMode: function () { | ||
| let self = this; | ||
|
|
||
| self.active_type = 'popular'; | ||
|
|
||
| self.title.innerText = 'Popular Distributions'; | ||
|
|
||
| self.tbody.querySelectorAll('.popular-dist').forEach(function (n) { | ||
| n.classList.remove('hide');; | ||
| }); | ||
|
|
||
| self.tbody.querySelectorAll('.search-dist').forEach(function (n) { | ||
| n.remove(); | ||
| }); | ||
| }, | ||
| poll: function (dists, type, inc) { | ||
| let self = this; | ||
| if (inc < 10) inc++; | ||
| let pdists = dists.splice(0, inc); | ||
| self.request('/dist', { dists: pdists }, function (res) { | ||
| res.dists.forEach(function (dist) { | ||
| self.render_dist(dist, type); | ||
| }); | ||
| if ( | ||
| (type == 'popular' || type == self.active_type) | ||
| && dists.length > 0 | ||
| ) self.poll(dists, type, 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, type) { | ||
| 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'); | ||
| }); | ||
| tr.firstElementChild.classList.add(type == "popular" ? "popular-dist" : "search-dist"); | ||
| if (type != this.active_type) { | ||
| tr.firstElementChild.classList.add("hide"); | ||
| } | ||
| this.tbody.appendChild(tr); | ||
| } | ||
| }; | ||
|
|
||
| let dists = [ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be better as |
||
| % for my $dist (@$dists) { | ||
| "<%= $dist %>", | ||
| % } | ||
| ]; | ||
|
|
||
| new Dist(dists); | ||
| })(); | ||
| </script> | ||
| </div> | ||
| </div> | ||
| </div> | ||
There was a problem hiding this comment.
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;: