From 69681d12cb3fb55762902192230edd87fd3215f9 Mon Sep 17 00:00:00 2001 From: Benjamin Brahmer Date: Wed, 25 Jan 2023 15:23:44 +0100 Subject: Implement item search The search result can only link to the feed. Signed-off-by: Benjamin Brahmer --- CHANGELOG.md | 1 + lib/AppInfo/Application.php | 3 + lib/Search/FeedSearchProvider.php | 6 +- lib/Search/FolderSearchProvider.php | 4 +- lib/Search/ItemSearchProvider.php | 109 ++++++++++++++++++ tests/Unit/Search/FeedSearchProviderTest.php | 2 +- tests/Unit/Search/FolderSearchProviderTest.php | 2 +- tests/Unit/Search/ItemSearchProviderTest.php | 147 +++++++++++++++++++++++++ 8 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 lib/Search/ItemSearchProvider.php create mode 100644 tests/Unit/Search/ItemSearchProviderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index fbc6bd893..da8d1cfaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is mostly based on [Keep a Changelog](https://keepachangelog.com/en/1 ### Changed - Drop support for Nextcloud 23 (#2077 ) - Make the "open" keyboard shortcut work faster (#2080) +- Implemented search for articles, results can only link to the feed. (#2075) ### Fixed - Stop errors from the favicon library over empty values diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 950d44383..bc5e1b476 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,6 +23,7 @@ use OCA\News\Config\FetcherConfig; use OCA\News\Hooks\UserDeleteHook; use OCA\News\Search\FeedSearchProvider; use OCA\News\Search\FolderSearchProvider; +use OCA\News\Search\ItemSearchProvider; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -82,6 +83,8 @@ class Application extends App implements IBootstrap $context->registerSearchProvider(FolderSearchProvider::class); $context->registerSearchProvider(FeedSearchProvider::class); + $context->registerSearchProvider(ItemSearchProvider::class); + $context->registerEventListener(BeforeUserDeletedEvent::class, UserDeleteHook::class); diff --git a/lib/Search/FeedSearchProvider.php b/lib/Search/FeedSearchProvider.php index bbcd466e4..36b21eaba 100644 --- a/lib/Search/FeedSearchProvider.php +++ b/lib/Search/FeedSearchProvider.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace OCA\News\Search; use OCA\News\Service\FeedServiceV2; -use OCA\News\Service\FolderServiceV2; +use OCA\News\AppInfo\Application; use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUser; @@ -48,7 +48,7 @@ class FeedSearchProvider implements IProvider public function getOrder(string $route, array $routeParameters): int { - if ($route === 'news.page.index') { + if (strpos($route, Application::NAME . '.') === 0) { // Active app, prefer my results return -1; } @@ -67,7 +67,7 @@ class FeedSearchProvider implements IProvider } $list[] = new SearchResultEntry( - $this->urlGenerator->imagePath('core', 'filetypes/text.svg'), + $this->urlGenerator->imagePath('core', 'rss.svg'), $feed->getTitle(), $this->l10n->t('Unread articles') . ': ' . $feed->getUnreadCount(), $this->urlGenerator->linkToRoute('news.page.index') . '#/items/feeds/' . $feed->getId() diff --git a/lib/Search/FolderSearchProvider.php b/lib/Search/FolderSearchProvider.php index 24439d73d..7f6783c68 100644 --- a/lib/Search/FolderSearchProvider.php +++ b/lib/Search/FolderSearchProvider.php @@ -49,9 +49,9 @@ class FolderSearchProvider implements IProvider public function getOrder(string $route, array $routeParameters): int { - if ($route === 'news.page.index') { + if (strpos($route, Application::NAME . '.') === 0) { // Active app, prefer my results - return -1; + return 0; } return 55; diff --git a/lib/Search/ItemSearchProvider.php b/lib/Search/ItemSearchProvider.php new file mode 100644 index 000000000..29b4bf209 --- /dev/null +++ b/lib/Search/ItemSearchProvider.php @@ -0,0 +1,109 @@ +l10n = $l10n; + $this->urlGenerator = $urlGenerator; + $this->service = $service; + } + + public function getId(): string + { + return 'news_item'; + } + + public function getName(): string + { + return $this->l10n->t('News articles'); + } + + public function getOrder(string $route, array $routeParameters): int + { + if (strpos($route, Application::NAME . '.') === 0) { + // Active app, prefer my results + return 1; + } + + return 65; + } + + private function stripTruncate(string $string, int $length = 50): string + { + $string = strip_tags(trim($string)); + + if (strlen($string) > $length) { + $string = wordwrap($string, $length); + $string = explode("\n", $string, 2); + $string = $string[0]; + } + + return $string; + } + + public function search(IUser $user, ISearchQuery $query): SearchResult + { + $list = []; + $offset = (int) ($query->getCursor() ?? 0); + $limit = $query->getLimit(); + + $search_result = $this->service->findAllWithFilters( + $user->getUID(), + ListType::ALL_ITEMS, + $limit, + $offset, + false, + [$query->getTerm()] + ); + + $last = end($search_result); + if ($last === false) { + return SearchResult::complete( + $this->l10n->t('News'), + [] + ); + } + + $icon = $this->urlGenerator->imagePath('core', 'filetypes/text.svg'); + + foreach ($search_result as $item) { + $list[] = new SearchResultEntry( + $icon, + $item->getTitle(), + $this->stripTruncate($item->getBody(), 50), + $this->urlGenerator->linkToRoute('news.page.index') . '#/items/feeds/' . $item->getFeedId() + ); + } + + return SearchResult::paginated($this->l10n->t('News'), $list, $last->getId()); + } +} diff --git a/tests/Unit/Search/FeedSearchProviderTest.php b/tests/Unit/Search/FeedSearchProviderTest.php index 97ec1f984..e1463a7a7 100644 --- a/tests/Unit/Search/FeedSearchProviderTest.php +++ b/tests/Unit/Search/FeedSearchProviderTest.php @@ -110,7 +110,7 @@ class FeedSearchProviderTest extends TestCase $this->generator->expects($this->once()) ->method('imagePath') - ->with('core', 'filetypes/text.svg') + ->with('core', 'rss.svg') ->willReturn('folderpath.svg'); $this->generator->expects($this->once()) diff --git a/tests/Unit/Search/FolderSearchProviderTest.php b/tests/Unit/Search/FolderSearchProviderTest.php index cf4ffd969..e06bf45f7 100644 --- a/tests/Unit/Search/FolderSearchProviderTest.php +++ b/tests/Unit/Search/FolderSearchProviderTest.php @@ -77,7 +77,7 @@ class FolderSearchProviderTest extends TestCase public function testGetOrderInternal() { - $this->assertSame(-1, $this->class->getOrder('news.page.index', [])); + $this->assertSame(0, $this->class->getOrder('news.page.index', [])); } public function testSearch() diff --git a/tests/Unit/Search/ItemSearchProviderTest.php b/tests/Unit/Search/ItemSearchProviderTest.php new file mode 100644 index 000000000..062d52a77 --- /dev/null +++ b/tests/Unit/Search/ItemSearchProviderTest.php @@ -0,0 +1,147 @@ +l10n = $this->getMockBuilder(IL10N::class) + ->disableOriginalConstructor() + ->getMock(); + $this->generator = $this->getMockBuilder(IURLGenerator::class) + ->disableOriginalConstructor() + ->getMock(); + $this->itemService = $this->getMockBuilder(ItemServiceV2::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->class = new ItemSearchProvider( + $this->l10n, + $this->generator, + $this->itemService + ); + } + + public function testGetId() + { + $this->assertSame('news_item', $this->class->getId()); + } + + public function testGetName() + { + $this->l10n->expects($this->once()) + ->method('t') + ->with('News articles') + ->willReturnArgument(0); + + $this->assertSame('News articles', $this->class->getName()); + } + + public function testGetOrderExternal() + { + $this->assertSame(65, $this->class->getOrder('contacts.Page.index', [])); + } + + public function testGetOrderInternal() + { + $this->assertSame(1, $this->class->getOrder('news.page.index', [])); + } + + public function testSearch() + { + $user = $this->getMockBuilder(IUser::class) + ->getMock(); + $query = $this->getMockBuilder(ISearchQuery::class) + ->getMock(); + + $query->expects($this->once()) + ->method('getCursor') + ->willReturn(null); + + $query->expects($this->once()) + ->method('getLimit') + ->willReturn(10); + + $user->expects($this->once()) + ->method('getUID') + ->willReturn('user'); + + $query->expects($this->once()) + ->method('getTerm') + ->willReturn('some text'); + + + $items = [ + Item::fromRow(['id' => 1,'title' => 'some_tErm', 'body' => 'some text', 'feedId' => 1]), + Item::fromRow(['id' => 2,'title' => 'nothing', 'body' => 'some text', 'feedId' => 1]) + ]; + + $this->itemService->expects($this->once()) + ->method('findAllWithFilters') + ->with( + 'user', + ListType::ALL_ITEMS, + 10, + 0, + false, + ['some text']) + ->willReturn($items); + + + $this->l10n->expects($this->once()) + ->method('t') + ->with('News') + ->willReturnArgument(0); + + $this->generator->expects($this->once()) + ->method('imagePath') + ->with('core', 'filetypes/text.svg') + ->willReturn('folderpath.svg'); + + $this->generator->expects($this->exactly(2)) + ->method('linkToRoute') + ->with('news.page.index') + ->willReturn('/news'); + + + $result = $this->class->search($user, $query)->jsonSerialize(); + $entry = $result['entries'][0]->jsonSerialize(); + $this->assertSame('News', $result['name']); + $this->assertSame('some_tErm', $entry['title']); + $this->assertSame('folderpath.svg', $entry['thumbnailUrl']); + $this->assertSame('some text', $entry['subline']); + $this->assertSame('/news#/items/feeds/1', $entry['resourceUrl']); + } +} -- cgit v1.2.3