Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
41 changes: 41 additions & 0 deletions lib/BackgroundJob/ContextChatInitialImport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
/**
* Analytics
*
* SPDX-FileCopyrightText: 2026 Marcel Scherello
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Analytics\BackgroundJob;

use OCA\Analytics\Service\DatasetService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\QueuedJob;
use Psr\Log\LoggerInterface;

/**
* Re-submits all datasets with indexing enabled after context chat requested an initial import
*/
class ContextChatInitialImport extends QueuedJob
{

private $logger;
private $DatasetService;

public function __construct(ITimeFactory $time,
LoggerInterface $logger,
DatasetService $DatasetService
)
{
parent::__construct($time);
$this->logger = $logger;
$this->DatasetService = $DatasetService;
}

public function run($arguments)
{
$count = $this->DatasetService->providerAll();
$this->logger->info('Analytics context chat initial import submitted ' . $count . ' datasets');
}

}
5 changes: 4 additions & 1 deletion lib/ContextChat/ContentProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,6 +26,7 @@ class ContentProvider implements IContentProvider, IEventListener {

public function __construct(
private IURLGenerator $urlGenerator,
private IJobList $jobList,
LoggerInterface $logger,
) {
$this->logger = $logger;
Expand Down Expand Up @@ -74,6 +77,6 @@ public function getItemUrl(string $id): string {
* @since 1.1.0
*/
public function triggerInitialImport(): void {
return;
$this->jobList->add(ContextChatInitialImport::class);
}
}
16 changes: 16 additions & 0 deletions lib/Db/DatasetMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions lib/Service/DatasetService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
45 changes: 43 additions & 2 deletions tests/Service/DatasetServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -102,15 +143,15 @@ 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(),
$logger,
$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),
Expand Down
Loading