Merge pull request #8425 from appwrite/feat-expose-execution-schedule-date
Feat: scheduledAt in execution response model
This commit is contained in:
commit
153736a020
7 changed files with 127 additions and 3 deletions
|
@ -3848,6 +3848,39 @@ $projectCollections = array_merge([
|
||||||
'array' => false,
|
'array' => false,
|
||||||
'filters' => [],
|
'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' => [
|
'indexes' => [
|
||||||
[
|
[
|
||||||
|
|
|
@ -1744,9 +1744,8 @@ App::post('/v1/functions/:functionId/executions')
|
||||||
->setContext('function', $function);
|
->setContext('function', $function);
|
||||||
|
|
||||||
if ($async) {
|
if ($async) {
|
||||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
|
||||||
|
|
||||||
if(is_null($scheduledAt)) {
|
if(is_null($scheduledAt)) {
|
||||||
|
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||||
$queueForFunctions
|
$queueForFunctions
|
||||||
->setType('http')
|
->setType('http')
|
||||||
->setExecution($execution)
|
->setExecution($execution)
|
||||||
|
@ -1770,7 +1769,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||||
'jwt' => $jwt,
|
'jwt' => $jwt,
|
||||||
];
|
];
|
||||||
|
|
||||||
$dbForConsole->createDocument('schedules', new Document([
|
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||||
'resourceType' => ScheduleExecutions::getSupportedResource(),
|
'resourceType' => ScheduleExecutions::getSupportedResource(),
|
||||||
'resourceId' => $execution->getId(),
|
'resourceId' => $execution->getId(),
|
||||||
|
@ -1781,6 +1780,13 @@ App::post('/v1/functions/:functionId/executions')
|
||||||
'data' => $data,
|
'data' => $data,
|
||||||
'active' => true,
|
'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
|
return $response
|
||||||
|
|
|
@ -116,6 +116,33 @@ class V21 extends Migration
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
Console::warning("'_key_deployment' from {$id}: {$th->getMessage()}");
|
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);
|
usleep(50000);
|
||||||
|
|
|
@ -14,6 +14,7 @@ class V18 extends Filter
|
||||||
|
|
||||||
$parsedResponse = match($model) {
|
$parsedResponse = match($model) {
|
||||||
Response::MODEL_FUNCTION => $this->parseFunction($content),
|
Response::MODEL_FUNCTION => $this->parseFunction($content),
|
||||||
|
Response::MODEL_EXECUTION => $this->parseExecution($content),
|
||||||
Response::MODEL_PROJECT => $this->parseProject($content),
|
Response::MODEL_PROJECT => $this->parseProject($content),
|
||||||
default => $parsedResponse,
|
default => $parsedResponse,
|
||||||
};
|
};
|
||||||
|
@ -21,6 +22,12 @@ class V18 extends Filter
|
||||||
return $parsedResponse;
|
return $parsedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function parseExecution(array $content)
|
||||||
|
{
|
||||||
|
unset($content['scheduledAt']);
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
protected function parseFunction(array $content)
|
protected function parseFunction(array $content)
|
||||||
{
|
{
|
||||||
unset($content['scopes']);
|
unset($content['scopes']);
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
use Appwrite\Utopia\Response;
|
use Appwrite\Utopia\Response;
|
||||||
use Appwrite\Utopia\Response\Model;
|
use Appwrite\Utopia\Response\Model;
|
||||||
|
use Utopia\Database\DateTime;
|
||||||
use Utopia\Database\Helpers\Role;
|
use Utopia\Database\Helpers\Role;
|
||||||
|
|
||||||
class Execution extends Model
|
class Execution extends Model
|
||||||
|
@ -110,6 +111,13 @@ class Execution extends Model
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
'example' => 0.400,
|
'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,
|
||||||
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ use Tests\E2E\Client;
|
||||||
use Tests\E2E\Scopes\ProjectCustom;
|
use Tests\E2E\Scopes\ProjectCustom;
|
||||||
use Tests\E2E\Scopes\Scope;
|
use Tests\E2E\Scopes\Scope;
|
||||||
use Tests\E2E\Scopes\SideClient;
|
use Tests\E2E\Scopes\SideClient;
|
||||||
|
use Utopia\Database\DateTime;
|
||||||
use Utopia\Database\Document;
|
use Utopia\Database\Document;
|
||||||
use Utopia\Database\Helpers\ID;
|
use Utopia\Database\Helpers\ID;
|
||||||
use Utopia\Database\Helpers\Role;
|
use Utopia\Database\Helpers\Role;
|
||||||
|
@ -269,6 +270,7 @@ class FunctionsCustomClientTest extends Scope
|
||||||
// Schedule execution for the future
|
// Schedule execution for the future
|
||||||
\date_default_timezone_set('UTC');
|
\date_default_timezone_set('UTC');
|
||||||
$futureTime = (new \DateTime())->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s');
|
$futureTime = (new \DateTime())->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s');
|
||||||
|
$futureTimeIso = DateTime::formatTz($futureTime);
|
||||||
|
|
||||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
|
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
|
@ -286,9 +288,23 @@ class FunctionsCustomClientTest extends Scope
|
||||||
|
|
||||||
$this->assertEquals(202, $execution['headers']['status-code']);
|
$this->assertEquals(202, $execution['headers']['status-code']);
|
||||||
$this->assertEquals('scheduled', $execution['body']['status']);
|
$this->assertEquals('scheduled', $execution['body']['status']);
|
||||||
|
$this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']);
|
||||||
|
|
||||||
$executionId = $execution['body']['$id'];
|
$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);
|
sleep(20);
|
||||||
|
|
||||||
$execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [
|
$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('/custom', $execution['body']['requestPath']);
|
||||||
$this->assertEquals('GET', $execution['body']['requestMethod']);
|
$this->assertEquals('GET', $execution['body']['requestMethod']);
|
||||||
$this->assertGreaterThan(0, $execution['body']['duration']);
|
$this->assertGreaterThan(0, $execution['body']['duration']);
|
||||||
|
$this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']);
|
||||||
|
|
||||||
/* Test for FAILURE */
|
/* Test for FAILURE */
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,32 @@ class V18Test extends TestCase
|
||||||
$this->assertEquals($expected, $result);
|
$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
|
public function projectProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
Loading…
Reference in a new issue