diff --git a/CHANGES.md b/CHANGES.md index bd23456d8..d11186427 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ - Queries have been improved to allow even more flexibility, and introduced to new endpoints. See the Queries V2 section in the document for more information [#3702](https://github.com/appwrite/appwrite/pull/3702) - Compound indexes are now more flexible [#151](https://github.com/utopia-php/database/pull/151) - `createExecution` parameter `async` default value was changed from `true` to `false` [#3781](https://github.com/appwrite/appwrite/pull/3781) +- String attribute `status` has been refactored to a Boolean attribute `enabled` in the functions collection [#3798](https://github.com/appwrite/appwrite/pull/3798) - `time` attribute in Execution response model has been reanamed to `duration` to be more consistent with other response models. [#3801](https://github.com/appwrite/appwrite/pull/3801) ## Features diff --git a/app/config/collections.php b/app/config/collections.php index fbc00feb9..0c8108fb5 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -2084,15 +2084,14 @@ $collections = [ 'filters' => [], ], [ - 'array' => false, - '$id' => ID::custom('status'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, + '$id' => ID::custom('enabled'), + 'type' => Database::VAR_BOOLEAN, 'signed' => true, - 'required' => false, - 'default' => null, + 'size' => 0, + 'format' => '', 'filters' => [], + 'required' => true, + 'array' => false, ], [ '$id' => ID::custom('runtime'), @@ -2210,10 +2209,10 @@ $collections = [ 'orders' => [Database::ORDER_ASC], ], [ - '$id' => ID::custom('_key_status'), + '$id' => ID::custom('_key_enabled'), 'type' => Database::INDEX_KEY, - 'attributes' => ['status'], - 'lengths' => [Database::LENGTH_KEY], + 'attributes' => ['enabled'], + 'lengths' => [], 'orders' => [Database::ORDER_ASC], ], [ diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index f12c2ebba..db8b15e99 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -67,16 +67,17 @@ App::post('/v1/functions') ->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true) + ->param('enabled', true, new Boolean(), 'Is function enabled?', true) ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $functionId, string $name, array $execute, string $runtime, array $events, string $schedule, int $timeout, Response $response, Database $dbForProject, Event $eventsInstance) { + ->action(function (string $functionId, string $name, array $execute, string $runtime, array $events, string $schedule, int $timeout, bool $enabled, Response $response, Database $dbForProject, Event $eventsInstance) { $functionId = ($functionId == 'unique()') ? ID::unique() : $functionId; $function = $dbForProject->createDocument('functions', new Document([ '$id' => $functionId, 'execute' => $execute, - 'status' => 'disabled', + 'enabled' => $enabled, 'name' => $name, 'runtime' => $runtime, 'deployment' => '', @@ -424,12 +425,13 @@ App::put('/v1/functions/:functionId') ->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true) + ->param('enabled', true, new Boolean(), 'Is function enabled?', true) ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('events') - ->action(function (string $functionId, string $name, array $execute, array $events, string $schedule, int $timeout, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { + ->action(function (string $functionId, string $name, array $execute, array $events, string $schedule, int $timeout, bool $enabled, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { $function = $dbForProject->getDocument('functions', $functionId); @@ -441,6 +443,8 @@ App::put('/v1/functions/:functionId') $cron = (!empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null; $next = (!empty($function->getAttribute('deployment')) && !empty($schedule)) ? DateTime::format($cron->getNextRunDate()) : null; + $enabled ??= $function->getAttribute('enabled', true); + $function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ 'execute' => $execute, 'name' => $name, @@ -448,6 +452,7 @@ App::put('/v1/functions/:functionId') 'schedule' => $schedule, 'scheduleNext' => $next, 'timeout' => $timeout, + 'enabled' => $enabled, 'search' => implode(' ', [$functionId, $name, $function->getAttribute('runtime')]), ]))); @@ -945,12 +950,15 @@ App::post('/v1/functions/:functionId/executions') ->inject('user') ->inject('events') ->inject('usage') - ->action(function (string $functionId, string $data, bool $async, Response $response, Document $project, Database $dbForProject, Document $user, Event $events, Stats $usage) { + ->inject('mode') + ->action(function (string $functionId, string $data, bool $async, Response $response, Document $project, Database $dbForProject, Document $user, Event $events, Stats $usage, string $mode) { $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); + if ($function->isEmpty() || !$function->getAttribute('enabled')) { + if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } } $runtimes = Config::getParam('runtimes', []); @@ -1137,12 +1145,15 @@ App::get('/v1/functions/:functionId/executions') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) { + ->inject('mode') + ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); + if ($function->isEmpty() || !$function->getAttribute('enabled')) { + if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } } $queries = Query::parseQueries($queries); @@ -1206,12 +1217,15 @@ App::get('/v1/functions/:functionId/executions/:executionId') ->param('executionId', '', new UID(), 'Execution ID.') ->inject('response') ->inject('dbForProject') - ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject) { + ->inject('mode') + ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode) { $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); + if ($function->isEmpty() || !$function->getAttribute('enabled')) { + if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } } $execution = $dbForProject->getDocument('executions', $executionId); @@ -1359,43 +1373,18 @@ App::get('/v1/functions/:functionId/variables') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_VARIABLE_LIST) ->param('functionId', null, new UID(), 'Function unique ID.', false) - ->param('queries', [], new Variables(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Variables::ALLOWED_ATTRIBUTES), true) - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) { + ->action(function (string $functionId, Response $response, Database $dbForProject) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { throw new Exception(Exception::FUNCTION_NOT_FOUND); } - $queries = Query::parseQueries($queries); - - if (!empty($search)) { - $queries[] = Query::search('search', $search); - } - - // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); - $cursor = reset($cursor); - if ($cursor) { - /** @var Query $cursor */ - $variableId = $cursor->getValue(); - $cursorDocument = $dbForProject->getDocument('variables', $variableId); - - if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Variable '{$variableId}' for the 'cursor' value not found."); - } - - $cursor->setValue($cursorDocument); - } - - $filterQueries = Query::groupByType($queries)['filters']; - $response->dynamic(new Document([ - 'variables' => $dbForProject->find('variables', $queries), - 'total' => $dbForProject->count('variables', $filterQueries, APP_LIMIT_COUNT), + 'variables' => $function->getAttribute('vars'), + 'total' => \count($function->getAttribute('vars')), ]), Response::MODEL_VARIABLE_LIST); }); diff --git a/app/views/console/functions/function.phtml b/app/views/console/functions/function.phtml index d4381c03e..46ac7a4db 100644 --- a/app/views/console/functions/function.phtml +++ b/app/views/console/functions/function.phtml @@ -523,8 +523,6 @@ sort($patterns); data-service="functions.listVariables" data-event="load,project.update,functions.createVariable,functions.updateVariable,functions.deleteVariable" data-name="function-variables" - data-param-queries="limit(100)" - data-param-queries-cast-to="array" data-param-queries-cast-from="csv" data-param-function-id="{{router.params.id}}" data-scope="sdk">Variables diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Functions.php b/src/Appwrite/Utopia/Database/Validator/Queries/Functions.php index ee4c31116..a2ba36895 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Functions.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Functions.php @@ -6,7 +6,7 @@ class Functions extends Base { public const ALLOWED_ATTRIBUTES = [ 'name', - 'status', + 'enabled', 'runtime', 'deployment', 'schedule', diff --git a/src/Appwrite/Utopia/Response/Model/Func.php b/src/Appwrite/Utopia/Response/Model/Func.php index fbe23bab0..c7e69fff8 100644 --- a/src/Appwrite/Utopia/Response/Model/Func.php +++ b/src/Appwrite/Utopia/Response/Model/Func.php @@ -43,11 +43,11 @@ class Func extends Model 'default' => '', 'example' => 'My Function', ]) - ->addRule('status', [ - 'type' => self::TYPE_STRING, - 'description' => 'Function status. Possible values: `disabled`, `enabled`', - 'default' => '', - 'example' => 'enabled', + ->addRule('enabled', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Function enabled.', + 'default' => true, + 'example' => false, ]) ->addRule('runtime', [ 'type' => self::TYPE_STRING, diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index a6e0434f6..68b9ac46e 100644 --- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php @@ -182,74 +182,14 @@ class FunctionsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(1, sizeof($response['body']['variables'])); + $this->assertEquals(1, $response['body']['total']); $this->assertEquals("APP_TEST", $response['body']['variables'][0]['key']); $this->assertEquals("TESTINGVALUE", $response['body']['variables'][0]['value']); - $variableId = $response['body']['variables'][0]['$id']; - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ 'limit(0)' ] - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(0, $response['body']['variables']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ] - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(0, $response['body']['variables']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ 'equal("key", "APP_TEST")' ] - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(1, $response['body']['variables']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'search' => $variableId - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(1, $response['body']['variables']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ 'equal("key", "NON_EXISTING_VARIABLE")' ] - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(0, $response['body']['variables']); - /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ 'equal("value", "MY_SECRET")' ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - return $data; } @@ -403,6 +343,7 @@ class FunctionsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(0, sizeof($response['body']['variables'])); + $this->assertEquals(0, $response['body']['total']); /** * Test for FAILURE diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index f974c6006..94f7e84f4 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -140,7 +140,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("status", "disabled")' ] + 'queries' => [ 'equal("enabled", true)' ] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -150,7 +150,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("status", "enabled")' ] + 'queries' => [ 'equal("enabled", false)' ] ]); $this->assertEquals($response['headers']['status-code'], 200);