Showing
7 changed files
with
113 additions
and
4 deletions
| ... | @@ -42,8 +42,9 @@ class BookController extends Controller | ... | @@ -42,8 +42,9 @@ class BookController extends Controller |
| 42 | public function index() | 42 | public function index() |
| 43 | { | 43 | { |
| 44 | $books = $this->bookRepo->getAllPaginated(10); | 44 | $books = $this->bookRepo->getAllPaginated(10); |
| 45 | - $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed(10, 0) : false; | 45 | + $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed(4, 0) : false; |
| 46 | - return view('books/index', ['books' => $books, 'recents' => $recents]); | 46 | + $popular = $this->bookRepo->getPopular(4, 0); |
| 47 | + return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]); | ||
| 47 | } | 48 | } |
| 48 | 49 | ||
| 49 | /** | 50 | /** | ... | ... |
| ... | @@ -77,6 +77,11 @@ class BookRepo | ... | @@ -77,6 +77,11 @@ class BookRepo |
| 77 | return Views::getUserRecentlyViewed($count, $page, $this->book); | 77 | return Views::getUserRecentlyViewed($count, $page, $this->book); |
| 78 | } | 78 | } |
| 79 | 79 | ||
| 80 | + public function getPopular($count = 10, $page = 0) | ||
| 81 | + { | ||
| 82 | + return Views::getPopular($count, $page, $this->book); | ||
| 83 | + } | ||
| 84 | + | ||
| 80 | /** | 85 | /** |
| 81 | * Get a book by slug | 86 | * Get a book by slug |
| 82 | * @param $slug | 87 | * @param $slug | ... | ... |
| ... | @@ -44,6 +44,29 @@ class ViewService | ... | @@ -44,6 +44,29 @@ class ViewService |
| 44 | return 1; | 44 | return 1; |
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * Get the entities with the most views. | ||
| 50 | + * @param int $count | ||
| 51 | + * @param int $page | ||
| 52 | + * @param bool|false $filterModel | ||
| 53 | + */ | ||
| 54 | + public function getPopular($count = 10, $page = 0, $filterModel = false) | ||
| 55 | + { | ||
| 56 | + $skipCount = $count * $page; | ||
| 57 | + $query = $this->view->select('id', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) | ||
| 58 | + ->groupBy('viewable_id', 'viewable_type') | ||
| 59 | + ->orderBy('view_count', 'desc'); | ||
| 60 | + | ||
| 61 | + if($filterModel) $query->where('viewable_type', '=', get_class($filterModel)); | ||
| 62 | + | ||
| 63 | + $views = $query->with('viewable')->skip($skipCount)->take($count)->get(); | ||
| 64 | + $viewedEntities = $views->map(function ($item) { | ||
| 65 | + return $item->viewable()->getResults(); | ||
| 66 | + }); | ||
| 67 | + return $viewedEntities; | ||
| 68 | + } | ||
| 69 | + | ||
| 47 | /** | 70 | /** |
| 48 | * Get all recently viewed entities for the current user. | 71 | * Get all recently viewed entities for the current user. |
| 49 | * @param int $count | 72 | * @param int $count | ... | ... |
| ... | @@ -34,12 +34,23 @@ | ... | @@ -34,12 +34,23 @@ |
| 34 | @endif | 34 | @endif |
| 35 | </div> | 35 | </div> |
| 36 | <div class="col-sm-4 col-sm-offset-1"> | 36 | <div class="col-sm-4 col-sm-offset-1"> |
| 37 | - <div class="margin-top large"> </div> | 37 | + <div id="recents"> |
| 38 | @if($recents) | 38 | @if($recents) |
| 39 | + <div class="margin-top large"> </div> | ||
| 39 | <h3>Recently Viewed</h3> | 40 | <h3>Recently Viewed</h3> |
| 40 | @include('partials/entity-list', ['entities' => $recents]) | 41 | @include('partials/entity-list', ['entities' => $recents]) |
| 41 | @endif | 42 | @endif |
| 42 | </div> | 43 | </div> |
| 44 | + <div class="margin-top large"> </div> | ||
| 45 | + <div id="popular"> | ||
| 46 | + <h3>Popular Books</h3> | ||
| 47 | + @if(count($popular) > 0) | ||
| 48 | + @include('partials/entity-list', ['entities' => $popular]) | ||
| 49 | + @else | ||
| 50 | + <p class="text-muted">The most popular books will appear here.</p> | ||
| 51 | + @endif | ||
| 52 | + </div> | ||
| 53 | + </div> | ||
| 43 | </div> | 54 | </div> |
| 44 | </div> | 55 | </div> |
| 45 | 56 | ... | ... |
| 1 | 1 | ||
| 2 | @if(count($entities) > 0) | 2 | @if(count($entities) > 0) |
| 3 | - @foreach($entities as $entity) | 3 | + @foreach($entities as $index => $entity) |
| 4 | @if($entity->isA('page')) | 4 | @if($entity->isA('page')) |
| 5 | @include('pages/list-item', ['page' => $entity]) | 5 | @include('pages/list-item', ['page' => $entity]) |
| 6 | @elseif($entity->isA('book')) | 6 | @elseif($entity->isA('book')) |
| ... | @@ -8,7 +8,11 @@ | ... | @@ -8,7 +8,11 @@ |
| 8 | @elseif($entity->isA('chapter')) | 8 | @elseif($entity->isA('chapter')) |
| 9 | @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true]) | 9 | @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true]) |
| 10 | @endif | 10 | @endif |
| 11 | + | ||
| 12 | + @if($index !== count($entities) - 1) | ||
| 11 | <hr> | 13 | <hr> |
| 14 | + @endif | ||
| 15 | + | ||
| 12 | @endforeach | 16 | @endforeach |
| 13 | @else | 17 | @else |
| 14 | <p class="text-muted"> | 18 | <p class="text-muted"> | ... | ... |
tests/ActivityTrackingTest.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +use Illuminate\Foundation\Testing\WithoutMiddleware; | ||
| 4 | +use Illuminate\Foundation\Testing\DatabaseMigrations; | ||
| 5 | +use Illuminate\Foundation\Testing\DatabaseTransactions; | ||
| 6 | + | ||
| 7 | +class ActivityTrackingTest extends TestCase | ||
| 8 | +{ | ||
| 9 | + | ||
| 10 | + public function testRecentlyViewedBooks() | ||
| 11 | + { | ||
| 12 | + $books = \BookStack\Book::all()->take(10); | ||
| 13 | + | ||
| 14 | + $this->asAdmin()->visit('/books') | ||
| 15 | + ->dontSeeInElement('#recents', $books[0]->name) | ||
| 16 | + ->dontSeeInElement('#recents', $books[1]->name) | ||
| 17 | + ->visit($books[0]->getUrl()) | ||
| 18 | + ->visit($books[1]->getUrl()) | ||
| 19 | + ->visit('/books') | ||
| 20 | + ->seeInElement('#recents', $books[0]->name) | ||
| 21 | + ->seeInElement('#recents', $books[1]->name); | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public function testPopularBooks() | ||
| 25 | + { | ||
| 26 | + $books = \BookStack\Book::all()->take(10); | ||
| 27 | + | ||
| 28 | + $this->asAdmin()->visit('/books') | ||
| 29 | + ->dontSeeInElement('#popular', $books[0]->name) | ||
| 30 | + ->dontSeeInElement('#popular', $books[1]->name) | ||
| 31 | + ->visit($books[0]->getUrl()) | ||
| 32 | + ->visit($books[1]->getUrl()) | ||
| 33 | + ->visit($books[0]->getUrl()) | ||
| 34 | + ->visit('/books') | ||
| 35 | + ->seeInNthElement('#popular .book', 0, $books[0]->name) | ||
| 36 | + ->seeInNthElement('#popular .book', 1, $books[1]->name); | ||
| 37 | + } | ||
| 38 | +} |
| ... | @@ -48,4 +48,31 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase | ... | @@ -48,4 +48,31 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase |
| 48 | $settings->put($key, $value); | 48 | $settings->put($key, $value); |
| 49 | } | 49 | } |
| 50 | } | 50 | } |
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * Assert that a given string is seen inside an element. | ||
| 54 | + * | ||
| 55 | + * @param bool|string|null $element | ||
| 56 | + * @param integer $position | ||
| 57 | + * @param string $text | ||
| 58 | + * @param bool $negate | ||
| 59 | + * @return $this | ||
| 60 | + */ | ||
| 61 | + protected function seeInNthElement($element, $position, $text, $negate = false) | ||
| 62 | + { | ||
| 63 | + $method = $negate ? 'assertNotRegExp' : 'assertRegExp'; | ||
| 64 | + | ||
| 65 | + $rawPattern = preg_quote($text, '/'); | ||
| 66 | + | ||
| 67 | + $escapedPattern = preg_quote(e($text), '/'); | ||
| 68 | + | ||
| 69 | + $content = $this->crawler->filter($element)->eq($position)->html(); | ||
| 70 | + | ||
| 71 | + $pattern = $rawPattern == $escapedPattern | ||
| 72 | + ? $rawPattern : "({$rawPattern}|{$escapedPattern})"; | ||
| 73 | + | ||
| 74 | + $this->$method("/$pattern/i", $content); | ||
| 75 | + | ||
| 76 | + return $this; | ||
| 77 | + } | ||
| 51 | } | 78 | } | ... | ... |
-
Please register or sign in to post a comment