diff --git a/CHANGELOG.md b/CHANGELOG.md index 844edfdb..6818334b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Changed - Increase the connection timeout for external datasources from 10 to 30 seconds. +### Fixed +- Re-index all AI-enabled datasets in Context Chat when it requests an initial import, e.g. after its index was reset. + ## 7.1.0 - 2026-10-21 ### Added - Visual field picker for JSON datasources, with a browsable hierarchy and selection preview. diff --git a/lib/BackgroundJob/ContextChatInitialImport.php b/lib/BackgroundJob/ContextChatInitialImport.php new file mode 100644 index 00000000..798536c3 --- /dev/null +++ b/lib/BackgroundJob/ContextChatInitialImport.php @@ -0,0 +1,41 @@ +logger = $logger; + $this->DatasetService = $DatasetService; + } + + public function run($arguments) + { + $count = $this->DatasetService->providerAll(); + $this->logger->info('Analytics context chat initial import submitted ' . $count . ' datasets'); + } + +} diff --git a/lib/ContextChat/ContentProvider.php b/lib/ContextChat/ContentProvider.php index 9ef6c4cf..5b7c8270 100644 --- a/lib/ContextChat/ContentProvider.php +++ b/lib/ContextChat/ContentProvider.php @@ -8,7 +8,9 @@ namespace OCA\Analytics\ContextChat; +use OCA\Analytics\BackgroundJob\ContextChatInitialImport; use OCA\ContextChat\Public\IContentProvider; +use OCP\BackgroundJob\IJobList; use OCP\IURLGenerator; use OCA\ContextChat\Event\ContentProviderRegisterEvent; use OCP\EventDispatcher\Event; @@ -24,6 +26,7 @@ class ContentProvider implements IContentProvider, IEventListener { public function __construct( private IURLGenerator $urlGenerator, + private IJobList $jobList, LoggerInterface $logger, ) { $this->logger = $logger; @@ -74,6 +77,6 @@ public function getItemUrl(string $id): string { * @since 1.1.0 */ public function triggerInitialImport(): void { - return; + $this->jobList->add(ContextChatInitialImport::class); } } diff --git a/lib/Db/DatasetMapper.php b/lib/Db/DatasetMapper.php index cbeb6860..5786fa31 100644 --- a/lib/Db/DatasetMapper.php +++ b/lib/Db/DatasetMapper.php @@ -9,6 +9,7 @@ namespace OCA\Analytics\Db; use OCP\DB\Exception; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IL10N; use Psr\Log\LoggerInterface; @@ -74,6 +75,21 @@ public function indexByUser($userId): array { return $result; } + /** + * get ids of all datasets with context chat indexing enabled + * @return array + * @throws Exception + */ + public function indexAiIndexed(): array { + $sql = $this->db->getQueryBuilder(); + $sql->from(self::TABLE_NAME)->select('id') + ->where($sql->expr()->eq('ai_index', $sql->createNamedParameter(1, IQueryBuilder::PARAM_INT))); + $statement = $sql->executeQuery(); + $result = $statement->fetchAll(); + $statement->closeCursor(); + return array_map('intval', array_column($result, 'id')); + } + /** * create dataset * @param $name diff --git a/lib/Service/DatasetService.php b/lib/Service/DatasetService.php index 9b152d1d..05789393 100644 --- a/lib/Service/DatasetService.php +++ b/lib/Service/DatasetService.php @@ -317,6 +317,21 @@ public function provider(int $datasetId) { return true; } + /** + * Submit all datasets with context chat indexing enabled + * Used for the initial import requested by context chat + * + * @return int number of processed datasets + * @throws Exception + */ + public function providerAll(): int { + $datasetIds = $this->DatasetMapper->indexAiIndexed(); + foreach ($datasetIds as $datasetId) { + $this->provider($datasetId); + } + return count($datasetIds); + } + /** * Remove dataset from context chat * diff --git a/tests/Service/DatasetServiceTest.php b/tests/Service/DatasetServiceTest.php index edc729ad..d5e80dc2 100644 --- a/tests/Service/DatasetServiceTest.php +++ b/tests/Service/DatasetServiceTest.php @@ -75,6 +75,47 @@ public function query(string $class) { $this->assertTrue($service->provider(11)); } + public function testProviderAllSubmitsEveryIndexedDataset(): void { + if (!class_exists('\OC')) { + eval('class OC { public static $server; }'); + } + + $contextChatManager = new class() { + public array $submitted = []; + + public function submitContent(int $datasetId): void { + $this->submitted[] = $datasetId; + if ($datasetId === 5) { + throw new \RuntimeException('indexing failed'); + } + } + }; + + \OC::$server = new class($contextChatManager) { + private $contextChatManager; + + public function __construct($contextChatManager) { + $this->contextChatManager = $contextChatManager; + } + + public function query(string $class) { + return $this->contextChatManager; + } + }; + + $datasetMapper = $this->createMock(DatasetMapper::class); + $datasetMapper->expects($this->once())->method('indexAiIndexed')->willReturn([3, 5, 8]); + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once())->method('warning'); + + $this->setContextChatAvailable(true); + + $service = $this->createService($logger, $datasetMapper); + + $this->assertSame(3, $service->providerAll()); + $this->assertSame([3, 5, 8], $contextChatManager->submitted); + } + public function testUpdateRejectsForeignDatasetBeforeSideEffects(): void { $datasetMapper = $this->createMock(DatasetMapper::class); $datasetMapper->expects($this->once())->method('readOwn')->with(77)->willReturn(false); @@ -102,7 +143,7 @@ public function testUpdateRejectsForeignDatasetBeforeSideEffects(): void { $this->assertFalse($service->update(77, 'foreign', '', '', '', '', 0)); } - private function createService(LoggerInterface $logger): DatasetService { + private function createService(LoggerInterface $logger, ?DatasetMapper $datasetMapper = null): DatasetService { return new DatasetService( 'u1', new FakeL10N(), @@ -110,7 +151,7 @@ private function createService(LoggerInterface $logger): DatasetService { $this->createMock(ITagManager::class), $this->createMock(ShareService::class), $this->createMock(StorageMapper::class), - $this->createMock(DatasetMapper::class), + $datasetMapper ?? $this->createMock(DatasetMapper::class), $this->createMock(ThresholdService::class), $this->createMock(DataloadMapper::class), $this->createMock(ActivityManager::class),