diff options
author | Julien Veyssier <julien-nc@posteo.net> | 2024-07-23 18:54:58 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-23 18:54:58 +0200 |
commit | f9d4becf60da69f272f6c5700bbdf5cb99761bc4 (patch) | |
tree | f59d62c957f5ee612dd888e1f68d274ba5490c03 /lib | |
parent | a899ba2a0f18a351e58faf574c3f5a2ab6536451 (diff) | |
parent | c004f5376feedd1b141a56fb3b3c8d30414b8a96 (diff) | |
download | nextcloud-server-f9d4becf60da69f272f6c5700bbdf5cb99761bc4.tar.gz nextcloud-server-f9d4becf60da69f272f6c5700bbdf5cb99761bc4.zip |
Merge pull request #46359 from nextcloud/enh/task-processing-more-datetimes
[TaskProcessing] Add start, stop and schedule time to tasks
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 3 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 3 | ||||
-rw-r--r-- | lib/private/TaskProcessing/Db/Task.php | 22 | ||||
-rw-r--r-- | lib/private/TaskProcessing/Db/TaskMapper.php | 45 | ||||
-rw-r--r-- | lib/private/TaskProcessing/Manager.php | 33 | ||||
-rw-r--r-- | lib/public/TaskProcessing/IManager.php | 18 | ||||
-rw-r--r-- | lib/public/TaskProcessing/Task.php | 57 |
7 files changed, 178 insertions, 3 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index c074a50f26e..7a2c2c6d227 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1180,6 +1180,8 @@ return array( 'OC\\Core\\Command\\SystemTag\\Delete' => $baseDir . '/core/Command/SystemTag/Delete.php', 'OC\\Core\\Command\\SystemTag\\Edit' => $baseDir . '/core/Command/SystemTag/Edit.php', 'OC\\Core\\Command\\SystemTag\\ListCommand' => $baseDir . '/core/Command/SystemTag/ListCommand.php', + 'OC\\Core\\Command\\TaskProcessing\\ListCommand' => $baseDir . '/core/Command/TaskProcessing/ListCommand.php', + 'OC\\Core\\Command\\TaskProcessing\\Statistics' => $baseDir . '/core/Command/TaskProcessing/Statistics.php', 'OC\\Core\\Command\\TwoFactorAuth\\Base' => $baseDir . '/core/Command/TwoFactorAuth/Base.php', 'OC\\Core\\Command\\TwoFactorAuth\\Cleanup' => $baseDir . '/core/Command/TwoFactorAuth/Cleanup.php', 'OC\\Core\\Command\\TwoFactorAuth\\Disable' => $baseDir . '/core/Command/TwoFactorAuth/Disable.php', @@ -1326,6 +1328,7 @@ return array( 'OC\\Core\\Migrations\\Version29000Date20240124132202' => $baseDir . '/core/Migrations/Version29000Date20240124132202.php', 'OC\\Core\\Migrations\\Version29000Date20240131122720' => $baseDir . '/core/Migrations/Version29000Date20240131122720.php', 'OC\\Core\\Migrations\\Version30000Date20240429122720' => $baseDir . '/core/Migrations/Version30000Date20240429122720.php', + 'OC\\Core\\Migrations\\Version30000Date20240708160048' => $baseDir . '/core/Migrations/Version30000Date20240708160048.php', 'OC\\Core\\Migrations\\Version30000Date20240717111406' => $baseDir . '/core/Migrations/Version30000Date20240717111406.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 61df306bda3..2f733673312 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1213,6 +1213,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Command\\SystemTag\\Delete' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Delete.php', 'OC\\Core\\Command\\SystemTag\\Edit' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Edit.php', 'OC\\Core\\Command\\SystemTag\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/SystemTag/ListCommand.php', + 'OC\\Core\\Command\\TaskProcessing\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/ListCommand.php', + 'OC\\Core\\Command\\TaskProcessing\\Statistics' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/Statistics.php', 'OC\\Core\\Command\\TwoFactorAuth\\Base' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Base.php', 'OC\\Core\\Command\\TwoFactorAuth\\Cleanup' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Cleanup.php', 'OC\\Core\\Command\\TwoFactorAuth\\Disable' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Disable.php', @@ -1359,6 +1361,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Migrations\\Version29000Date20240124132202' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240124132202.php', 'OC\\Core\\Migrations\\Version29000Date20240131122720' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240131122720.php', 'OC\\Core\\Migrations\\Version30000Date20240429122720' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240429122720.php', + 'OC\\Core\\Migrations\\Version30000Date20240708160048' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240708160048.php', 'OC\\Core\\Migrations\\Version30000Date20240717111406' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240717111406.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php', diff --git a/lib/private/TaskProcessing/Db/Task.php b/lib/private/TaskProcessing/Db/Task.php index 9fc999faf1a..6787c11344a 100644 --- a/lib/private/TaskProcessing/Db/Task.php +++ b/lib/private/TaskProcessing/Db/Task.php @@ -39,6 +39,12 @@ use OCP\TaskProcessing\Task as OCPTask; * @method string getWebhookUri() * @method setWebhookMethod(string $webhookMethod) * @method string getWebhookMethod() + * @method setScheduledAt(int $scheduledAt) + * @method int getScheduledAt() + * @method setStartedAt(int $startedAt) + * @method int getStartedAt() + * @method setEndedAt(int $endedAt) + * @method int getEndedAt() */ class Task extends Entity { protected $lastUpdated; @@ -54,16 +60,19 @@ class Task extends Entity { protected $progress; protected $webhookUri; protected $webhookMethod; + protected $scheduledAt; + protected $startedAt; + protected $endedAt; /** * @var string[] */ - public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress', 'webhook_uri', 'webhook_method']; + public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress', 'webhook_uri', 'webhook_method', 'scheduled_at', 'started_at', 'ended_at']; /** * @var string[] */ - public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress', 'webhookUri', 'webhookMethod']; + public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress', 'webhookUri', 'webhookMethod', 'scheduledAt', 'startedAt', 'endedAt']; public function __construct() { @@ -82,6 +91,9 @@ class Task extends Entity { $this->addType('progress', 'float'); $this->addType('webhookUri', 'string'); $this->addType('webhookMethod', 'string'); + $this->addType('scheduledAt', 'integer'); + $this->addType('startedAt', 'integer'); + $this->addType('endedAt', 'integer'); } public function toRow(): array { @@ -107,6 +119,9 @@ class Task extends Entity { 'progress' => $task->getProgress(), 'webhookUri' => $task->getWebhookUri(), 'webhookMethod' => $task->getWebhookMethod(), + 'scheduledAt' => $task->getScheduledAt(), + 'startedAt' => $task->getStartedAt(), + 'endedAt' => $task->getEndedAt(), ]); return $taskEntity; } @@ -126,6 +141,9 @@ class Task extends Entity { $task->setProgress($this->getProgress()); $task->setWebhookUri($this->getWebhookUri()); $task->setWebhookMethod($this->getWebhookMethod()); + $task->setScheduledAt($this->getScheduledAt()); + $task->setStartedAt($this->getStartedAt()); + $task->setEndedAt($this->getEndedAt()); return $task; } } diff --git a/lib/private/TaskProcessing/Db/TaskMapper.php b/lib/private/TaskProcessing/Db/TaskMapper.php index da3910dcb3d..2bdee4fa134 100644 --- a/lib/private/TaskProcessing/Db/TaskMapper.php +++ b/lib/private/TaskProcessing/Db/TaskMapper.php @@ -137,6 +137,51 @@ class TaskMapper extends QBMapper { } /** + * @param string|null $userId + * @param string|null $taskType + * @param string|null $appId + * @param string|null $customId + * @param int|null $status + * @param int|null $scheduleAfter + * @param int|null $endedBefore + * @return list<Task> + * @throws Exception + */ + public function findTasks( + ?string $userId, ?string $taskType = null, ?string $appId = null, ?string $customId = null, + ?int $status = null, ?int $scheduleAfter = null, ?int $endedBefore = null): array { + $qb = $this->db->getQueryBuilder(); + $qb->select(Task::$columns) + ->from($this->tableName); + + // empty string: no userId filter + if ($userId !== '') { + $qb->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId))); + } + if ($taskType !== null) { + $qb->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter($taskType))); + } + if ($appId !== null) { + $qb->andWhere($qb->expr()->eq('app_id', $qb->createPositionalParameter($appId))); + } + if ($customId !== null) { + $qb->andWhere($qb->expr()->eq('custom_id', $qb->createPositionalParameter($customId))); + } + if ($status !== null) { + $qb->andWhere($qb->expr()->eq('status', $qb->createPositionalParameter($status, IQueryBuilder::PARAM_INT))); + } + if ($scheduleAfter !== null) { + $qb->andWhere($qb->expr()->isNotNull('scheduled_at')); + $qb->andWhere($qb->expr()->gt('scheduled_at', $qb->createPositionalParameter($scheduleAfter, IQueryBuilder::PARAM_INT))); + } + if ($endedBefore !== null) { + $qb->andWhere($qb->expr()->isNotNull('ended_at')); + $qb->andWhere($qb->expr()->lt('ended_at', $qb->createPositionalParameter($endedBefore, IQueryBuilder::PARAM_INT))); + } + return array_values($this->findEntities($qb)); + } + + /** * @param int $timeout * @return int the number of deleted tasks * @throws Exception diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php index 714a23ce5e2..ad690acefd7 100644 --- a/lib/private/TaskProcessing/Manager.php +++ b/lib/private/TaskProcessing/Manager.php @@ -616,6 +616,7 @@ class Manager implements IManager { // remove superfluous keys and set input $task->setInput($this->removeSuperfluousArrayKeys($task->getInput(), $inputShape, $optionalInputShape)); $task->setStatus(Task::STATUS_SCHEDULED); + $task->setScheduledAt(time()); $provider = $this->getPreferredProvider($task->getTaskTypeId()); // calculate expected completion time $completionExpectedAt = new \DateTime('now'); @@ -656,6 +657,7 @@ class Manager implements IManager { return; } $task->setStatus(Task::STATUS_CANCELLED); + $task->setEndedAt(time()); $taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task); try { $this->taskMapper->update($taskEntity); @@ -671,6 +673,10 @@ class Manager implements IManager { if ($task->getStatus() === Task::STATUS_CANCELLED) { return false; } + // only set the start time if the task is going from scheduled to running + if ($task->getstatus() === Task::STATUS_SCHEDULED) { + $task->setStartedAt(time()); + } $task->setStatus(Task::STATUS_RUNNING); $task->setProgress($progress); $taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task); @@ -691,6 +697,7 @@ class Manager implements IManager { } if ($error !== null) { $task->setStatus(Task::STATUS_FAILED); + $task->setEndedAt(time()); $task->setErrorMessage($error); $this->logger->warning('A TaskProcessing ' . $task->getTaskTypeId() . ' task with id ' . $id . ' failed with the following message: ' . $error); } elseif ($result !== null) { @@ -725,21 +732,25 @@ class Manager implements IManager { $task->setOutput($output); $task->setProgress(1); $task->setStatus(Task::STATUS_SUCCESSFUL); + $task->setEndedAt(time()); } catch (ValidationException $e) { $task->setProgress(1); $task->setStatus(Task::STATUS_FAILED); + $task->setEndedAt(time()); $error = 'The task was processed successfully but the provider\'s output doesn\'t pass validation against the task type\'s outputShape spec and/or the provider\'s own optionalOutputShape spec'; $task->setErrorMessage($error); $this->logger->error($error, ['exception' => $e]); } catch (NotPermittedException $e) { $task->setProgress(1); $task->setStatus(Task::STATUS_FAILED); + $task->setEndedAt(time()); $error = 'The task was processed successfully but storing the output in a file failed'; $task->setErrorMessage($error); $this->logger->error($error, ['exception' => $e]); } catch (InvalidPathException|\OCP\Files\NotFoundException $e) { $task->setProgress(1); $task->setStatus(Task::STATUS_FAILED); + $task->setEndedAt(time()); $error = 'The task was processed successfully but the result file could not be found'; $task->setErrorMessage($error); $this->logger->error($error, ['exception' => $e]); @@ -839,6 +850,20 @@ class Manager implements IManager { } } + public function getTasks( + ?string $userId, ?string $taskTypeId = null, ?string $appId = null, ?string $customId = null, + ?int $status = null, ?int $scheduleAfter = null, ?int $endedBefore = null + ): array { + try { + $taskEntities = $this->taskMapper->findTasks($userId, $taskTypeId, $appId, $customId, $status, $scheduleAfter, $endedBefore); + return array_map(fn ($taskEntity): Task => $taskEntity->toPublicTask(), $taskEntities); + } catch (\OCP\DB\Exception $e) { + throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding the tasks', 0, $e); + } catch (\JsonException $e) { + throw new \OCP\TaskProcessing\Exception\Exception('There was a problem parsing JSON after finding the tasks', 0, $e); + } + } + public function getUserTasksByApp(?string $userId, string $appId, ?string $customId = null): array { try { $taskEntities = $this->taskMapper->findUserTasksByApp($userId, $appId, $customId); @@ -926,6 +951,14 @@ class Manager implements IManager { * @throws Exception */ public function setTaskStatus(Task $task, int $status): void { + $currentTaskStatus = $task->getStatus(); + if ($currentTaskStatus === Task::STATUS_SCHEDULED && $status === Task::STATUS_RUNNING) { + $task->setStartedAt(time()); + } elseif ($currentTaskStatus === Task::STATUS_RUNNING && ($status === Task::STATUS_FAILED || $status === Task::STATUS_CANCELLED)) { + $task->setEndedAt(time()); + } elseif ($currentTaskStatus === Task::STATUS_UNKNOWN && $status === Task::STATUS_SCHEDULED) { + $task->setScheduledAt(time()); + } $task->setStatus($status); $taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task); $this->taskMapper->update($taskEntity); diff --git a/lib/public/TaskProcessing/IManager.php b/lib/public/TaskProcessing/IManager.php index c68ad1afbac..d7cd96edc45 100644 --- a/lib/public/TaskProcessing/IManager.php +++ b/lib/public/TaskProcessing/IManager.php @@ -141,6 +141,24 @@ interface IManager { public function getUserTasks(?string $userId, ?string $taskTypeId = null, ?string $customId = null): array; /** + * @param string|null $userId The user id that scheduled the task + * @param string|null $taskTypeId The task type id to filter by + * @param string|null $appId The app ID of the app that submitted the task + * @param string|null $customId The custom task ID + * @param int|null $status The task status + * @param int|null $scheduleAfter Minimum schedule time filter + * @param int|null $endedBefore Maximum ending time filter + * @return list<Task> + * @throws Exception If the query failed + * @throws NotFoundException If the task could not be found + * @since 30.0.0 + */ + public function getTasks( + ?string $userId, ?string $taskTypeId = null, ?string $appId = null, ?string $customId = null, + ?int $status = null, ?int $scheduleAfter = null, ?int $endedBefore = null + ): array; + + /** * @param string|null $userId * @param string $appId * @param string|null $customId diff --git a/lib/public/TaskProcessing/Task.php b/lib/public/TaskProcessing/Task.php index 44834c3b846..db8e4d7fab5 100644 --- a/lib/public/TaskProcessing/Task.php +++ b/lib/public/TaskProcessing/Task.php @@ -63,6 +63,10 @@ final class Task implements \JsonSerializable { */ protected int $status = self::STATUS_UNKNOWN; + protected ?int $scheduledAt = null; + protected ?int $startedAt = null; + protected ?int $endedAt = null; + /** * @param string $taskTypeId * @param array<string,list<numeric|string>|numeric|string> $input @@ -201,7 +205,55 @@ final class Task implements \JsonSerializable { } /** - * @psalm-return array{id: ?int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array<array-key, list<numeric|string>|numeric|string>, output: ?array<array-key, list<numeric|string>|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float} + * @return int|null + * @since 30.0.0 + */ + final public function getScheduledAt(): ?int { + return $this->scheduledAt; + } + + /** + * @param int|null $scheduledAt + * @since 30.0.0 + */ + final public function setScheduledAt(?int $scheduledAt): void { + $this->scheduledAt = $scheduledAt; + } + + /** + * @return int|null + * @since 30.0.0 + */ + final public function getStartedAt(): ?int { + return $this->startedAt; + } + + /** + * @param int|null $startedAt + * @since 30.0.0 + */ + final public function setStartedAt(?int $startedAt): void { + $this->startedAt = $startedAt; + } + + /** + * @return int|null + * @since 30.0.0 + */ + final public function getEndedAt(): ?int { + return $this->endedAt; + } + + /** + * @param int|null $endedAt + * @since 30.0.0 + */ + final public function setEndedAt(?int $endedAt): void { + $this->endedAt = $endedAt; + } + + /** + * @psalm-return array{id: ?int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array<array-key, list<numeric|string>|numeric|string>, output: ?array<array-key, list<numeric|string>|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float, scheduledAt: ?int, startedAt: ?int, endedAt: ?int} * @since 30.0.0 */ final public function jsonSerialize(): array { @@ -217,6 +269,9 @@ final class Task implements \JsonSerializable { 'customId' => $this->getCustomId(), 'completionExpectedAt' => $this->getCompletionExpectedAt()?->getTimestamp(), 'progress' => $this->getProgress(), + 'scheduledAt' => $this->getScheduledAt(), + 'startedAt' => $this->getStartedAt(), + 'endedAt' => $this->getEndedAt(), ]; } |