From 304910e303b90db4258624cb147ad8f207f00c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 22 Jul 2024 10:36:18 +0000 Subject: [PATCH 1/5] Add schedule date to response format --- app/config/collections.php | 33 +++++++++++++++++++ app/controllers/api/functions.php | 12 +++++-- src/Appwrite/Migration/Version/V16.php | 30 +++++++++++++++++ .../Utopia/Response/Model/Execution.php | 8 +++++ .../Functions/FunctionsCustomClientTest.php | 17 ++++++++++ 5 files changed, 97 insertions(+), 3 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index d0c3df165e..f6a357b3c5 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -3848,6 +3848,39 @@ $projectCollections = array_merge([ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('scheduledAt'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('scheduleInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('scheduleId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [ [ diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d2a1790d94..00988840c9 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1744,9 +1744,8 @@ App::post('/v1/functions/:functionId/executions') ->setContext('function', $function); if ($async) { - $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); - if(is_null($scheduledAt)) { + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); $queueForFunctions ->setType('http') ->setExecution($execution) @@ -1770,7 +1769,7 @@ App::post('/v1/functions/:functionId/executions') 'jwt' => $jwt, ]; - $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForConsole->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => ScheduleExecutions::getSupportedResource(), 'resourceId' => $execution->getId(), @@ -1781,6 +1780,13 @@ App::post('/v1/functions/:functionId/executions') 'data' => $data, 'active' => true, ])); + + $execution = $execution + ->setAttribute('scheduleId', $schedule->getId()) + ->setAttribute('scheduleInternalId', $schedule->getInternalId()) + ->setAttribute('scheduledAt', $scheduledAt); + + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); } return $response diff --git a/src/Appwrite/Migration/Version/V16.php b/src/Appwrite/Migration/Version/V16.php index 49f244598e..636a301717 100644 --- a/src/Appwrite/Migration/Version/V16.php +++ b/src/Appwrite/Migration/Version/V16.php @@ -48,6 +48,36 @@ class V16 extends Migration $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); switch ($id) { + case 'executions': + try { + /** + * Create 'scheduledAt' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); + } catch (\Throwable $th) { + Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); + } catch (\Throwable $th) { + Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); + } catch (\Throwable $th) { + Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); + } + + break; + case 'sessions': try { /** diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php index fbd9619a40..90fbdc9689 100644 --- a/src/Appwrite/Utopia/Response/Model/Execution.php +++ b/src/Appwrite/Utopia/Response/Model/Execution.php @@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; +use Utopia\Database\DateTime; use Utopia\Database\Helpers\Role; class Execution extends Model @@ -110,6 +111,13 @@ class Execution extends Model 'default' => 0, 'example' => 0.400, ]) + ->addRule('scheduledAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'The scheduled time for execution. If left empty, execution will be queued immediately.', + 'required' => false, + 'default' => DateTime::now(), + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) ; } diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index bf969d388a..39f97cba65 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -269,6 +269,7 @@ class FunctionsCustomClientTest extends Scope // Schedule execution for the future \date_default_timezone_set('UTC'); $futureTime = (new \DateTime())->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s'); + $futureTimeIso = (new \DateTime($futureTime))->format('Y-m-d\TH:i:s.vP'); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ 'content-type' => 'application/json', @@ -286,9 +287,24 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $execution['headers']['status-code']); $this->assertEquals('scheduled', $execution['body']['status']); + $this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']); $executionId = $execution['body']['$id']; + // List executions and ensure it has schedule date + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertGreaterThan(0, \count($response['body']['executions'])); + $recentExecution = $response['body']['executions'][0]; + $this->assertEquals($executionId, $recentExecution['$id']); + $this->assertEquals($futureTimeIso, $recentExecution['scheduledAt']); + sleep(20); $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [ @@ -303,6 +319,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('/custom', $execution['body']['requestPath']); $this->assertEquals('GET', $execution['body']['requestMethod']); $this->assertGreaterThan(0, $execution['body']['duration']); + $this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']); /* Test for FAILURE */ From 51c163a42f6fe556425e8b18075635f2892c4b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 24 Jul 2024 06:55:36 +0000 Subject: [PATCH 2/5] Apply migrations properly --- src/Appwrite/Migration/Version/V16.php | 30 ------------------- src/Appwrite/Migration/Version/V21.php | 29 ++++++++++++++++++ src/Appwrite/Utopia/Response/Filters/V18.php | 7 +++++ .../unit/Utopia/Response/Filters/V18Test.php | 26 ++++++++++++++++ 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/Appwrite/Migration/Version/V16.php b/src/Appwrite/Migration/Version/V16.php index 636a301717..49f244598e 100644 --- a/src/Appwrite/Migration/Version/V16.php +++ b/src/Appwrite/Migration/Version/V16.php @@ -48,36 +48,6 @@ class V16 extends Migration $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); switch ($id) { - case 'executions': - try { - /** - * Create 'scheduledAt' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); - } catch (\Throwable $th) { - Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); - } - - try { - /** - * Create 'scheduleInternalId' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); - } catch (\Throwable $th) { - Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); - } - - try { - /** - * Create 'scheduleId' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); - } catch (\Throwable $th) { - Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); - } - - break; - case 'sessions': try { /** diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index be6c38be67..1eda618c52 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -69,6 +69,35 @@ class V21 extends Migration Console::warning("'accessedAt' from {$id}: {$th->getMessage()}"); } break; + case 'executions': + try { + /** + * Create 'scheduledAt' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); + } catch (\Throwable $th) { + Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); + } catch (\Throwable $th) { + Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); + } catch (\Throwable $th) { + Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); + } + + break; case 'schedules': // Create data attribute try { diff --git a/src/Appwrite/Utopia/Response/Filters/V18.php b/src/Appwrite/Utopia/Response/Filters/V18.php index b32856bdb4..d0aa680e3b 100644 --- a/src/Appwrite/Utopia/Response/Filters/V18.php +++ b/src/Appwrite/Utopia/Response/Filters/V18.php @@ -14,6 +14,7 @@ class V18 extends Filter $parsedResponse = match($model) { Response::MODEL_FUNCTION => $this->parseFunction($content), + Response::MODEL_EXECUTION => $this->parseExecution($content), Response::MODEL_PROJECT => $this->parseProject($content), default => $parsedResponse, }; @@ -21,6 +22,12 @@ class V18 extends Filter return $parsedResponse; } + protected function parseExecution(array $content) + { + unset($content['scheduledAt']); + return $content; + } + protected function parseFunction(array $content) { unset($content['scopes']); diff --git a/tests/unit/Utopia/Response/Filters/V18Test.php b/tests/unit/Utopia/Response/Filters/V18Test.php index 36719a7620..c4011c08a1 100644 --- a/tests/unit/Utopia/Response/Filters/V18Test.php +++ b/tests/unit/Utopia/Response/Filters/V18Test.php @@ -50,6 +50,32 @@ class V18Test extends TestCase $this->assertEquals($expected, $result); } + + public function executionProvider(): array + { + return [ + 'remove scheduledAt' => [ + [ + 'scheduledAt' => '2024-07-13T09:00:00.000Z', + ], + [ + ] + ] + ]; + } + + /** + * @dataProvider executionProvider + */ + public function testExecution(array $content, array $expected): void + { + $model = Response::MODEL_EXECUTION; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + public function projectProvider(): array { return [ From ab55e6f5117407bbf7e1e635d1f041bba33cf155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 24 Jul 2024 07:00:20 +0000 Subject: [PATCH 3/5] Linter fix --- src/Appwrite/Migration/Version/V21.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index 1eda618c52..a75ba58949 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -97,7 +97,7 @@ class V21 extends Migration Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); } - break; + break; case 'schedules': // Create data attribute try { From c42a2bc5b7e99ecda80ee02ad3cb17bb64105af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 24 Jul 2024 07:11:48 +0000 Subject: [PATCH 4/5] PR review changes --- src/Appwrite/Migration/Version/V21.php | 56 +++++++++++++------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index a75ba58949..7dd2912234 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -68,35 +68,6 @@ class V21 extends Migration } catch (Throwable $th) { Console::warning("'accessedAt' from {$id}: {$th->getMessage()}"); } - break; - case 'executions': - try { - /** - * Create 'scheduledAt' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); - } catch (\Throwable $th) { - Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); - } - - try { - /** - * Create 'scheduleInternalId' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); - } catch (\Throwable $th) { - Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); - } - - try { - /** - * Create 'scheduleId' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); - } catch (\Throwable $th) { - Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); - } - break; case 'schedules': // Create data attribute @@ -145,6 +116,33 @@ class V21 extends Migration } catch (\Throwable $th) { Console::warning("'_key_deployment' from {$id}: {$th->getMessage()}"); } + + try { + /** + * Create 'scheduledAt' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); + } catch (\Throwable $th) { + Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); + } catch (\Throwable $th) { + Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); + } catch (\Throwable $th) { + Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); + } } usleep(50000); From 5c65fa895d0fe788260b8261495795f7f5d3f54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 25 Jul 2024 06:54:50 +0000 Subject: [PATCH 5/5] PR review changes --- tests/e2e/Services/Functions/FunctionsCustomClientTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index a44420b544..4e501692fa 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -8,6 +8,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; @@ -269,7 +270,7 @@ class FunctionsCustomClientTest extends Scope // Schedule execution for the future \date_default_timezone_set('UTC'); $futureTime = (new \DateTime())->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s'); - $futureTimeIso = (new \DateTime($futureTime))->format('Y-m-d\TH:i:s.vP'); + $futureTimeIso = DateTime::formatTz($futureTime); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ 'content-type' => 'application/json', @@ -298,7 +299,6 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'], ]); - $this->assertEquals(200, $response['headers']['status-code']); $this->assertGreaterThan(0, \count($response['body']['executions'])); $recentExecution = $response['body']['executions'][0];