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
13 changes: 12 additions & 1 deletion app/config/collections/platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,18 @@
'default' => null,
'array' => false,
'filters' => ['datetime'],
]
],
[
'$id' => ID::custom('labels'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
Comment thread
Meldiron marked this conversation as resolved.
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
Expand Down
1 change: 1 addition & 0 deletions app/controllers/api/projects.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
'accessedAt' => DateTime::now(),
'search' => implode(' ', [$projectId, $name]),
'database' => $dsn,
'labels' => [],
Comment thread
Meldiron marked this conversation as resolved.
]));
} catch (Duplicate) {
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Appwrite\Platform\Modules\Projects\Http\Projects\Labels;

use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Text;

class Update extends Action
{
use HTTP;

public static function getName()
{
return 'updateProjectLabels';
}

protected function getQueriesValidator(): Validator
{
return new Projects();
}

public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
->setHttpPath('/v1/projects/:projectId/labels')
->desc('Update project labels')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'updateLabels',
description: <<<EOT
Update the project labels by its unique ID. Labels can be used to easily filter projects in an organization.
EOT,
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROJECT
)
],
contentType: ContentType::JSON
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('labels', [], new ArrayList(new Text(36, allowList: [...Text::NUMBERS, ...Text::ALPHABET_UPPER, ...Text::ALPHABET_LOWER]), APP_LIMIT_ARRAY_LABELS_SIZE), 'Array of project labels. Replaces the previous labels. Maximum of ' . APP_LIMIT_ARRAY_LABELS_SIZE . ' labels are allowed, each up to 36 alphanumeric characters long.')
->inject('response')
->inject('dbForPlatform')
->callback($this->action(...));
}

/**
* @param array<string> $labels
*/
public function action(
string $projectId,
array $labels,
Response $response,
Database $dbForPlatform
): void {
$project = $dbForPlatform->getDocument('projects', $projectId);

if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}

$project->setAttribute('labels', (array) \array_values(\array_unique($labels)));

$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project);

$response->dynamic($project, Response::MODEL_PROJECT);
}
}
2 changes: 2 additions & 0 deletions src/Appwrite/Platform/Modules/Projects/Services/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Appwrite\Platform\Modules\Projects\Http\DevKeys\Get as GetDevKey;
use Appwrite\Platform\Modules\Projects\Http\DevKeys\Update as UpdateDevKey;
use Appwrite\Platform\Modules\Projects\Http\DevKeys\XList as ListDevKeys;
use Appwrite\Platform\Modules\Projects\Http\Projects\Labels\Update as UpdateProjectLabels;
use Appwrite\Platform\Modules\Projects\Http\Projects\XList as ListProjects;
use Utopia\Platform\Service;

Expand All @@ -22,5 +23,6 @@ public function __construct()
$this->addAction(DeleteDevKey::getName(), new DeleteDevKey());

$this->addAction(ListProjects::getName(), new ListProjects());
$this->addAction(UpdateProjectLabels::getName(), new UpdateProjectLabels());
}
}
3 changes: 2 additions & 1 deletion src/Appwrite/Utopia/Database/Validator/Queries/Projects.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ class Projects extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'teamId'
'teamId',
'labels',
];

/**
Expand Down
7 changes: 7 additions & 0 deletions src/Appwrite/Utopia/Response/Model/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,13 @@ public function __construct()
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('labels', [
'type' => self::TYPE_STRING,
'description' => 'Labels for the project.',
'default' => [],
'example' => ['vip'],
'array' => true,
])
;

$services = Config::getParam('services', []);
Expand Down
212 changes: 212 additions & 0 deletions tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5382,4 +5382,216 @@ public function testDeleteProjectDevKey(): void
/**
* Devkeys Tests ends here ------------------------------------------------
*/

public function testProjectLabels(): void
{
// Setup: Prepare team
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'teamId' => ID::unique(),
'name' => 'Query Select Test Team',
]);

$this->assertEquals(201, $team['headers']['status-code']);
$teamId = $team['body']['$id'];

// Setup: Prepare project
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'projectId' => ID::unique(),
'name' => 'Test project - Labels 1',
'teamId' => $teamId,
'region' => System::getEnv('_APP_REGION', 'default')
]);

$this->assertEquals(201, $project['headers']['status-code']);
$this->assertIsArray($project['body']['labels']);
$this->assertCount(0, $project['body']['labels']);
$projectId = $project['body']['$id'];

// Apply labels
$project = $this->client->call(Client::METHOD_PUT, '/projects/' . $projectId . '/labels', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'labels' => ['vip', 'imagine', 'blocked']
]);

$this->assertEquals(200, $project['headers']['status-code']);
$this->assertIsArray($project['body']['labels']);
$this->assertCount(3, $project['body']['labels']);
$this->assertEquals('vip', $project['body']['labels'][0]);
$this->assertEquals('imagine', $project['body']['labels'][1]);
$this->assertEquals('blocked', $project['body']['labels'][2]);

// Update labels
$project = $this->client->call(Client::METHOD_PUT, '/projects/' . $projectId . '/labels', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'labels' => ['nonvip', 'imagine']
]);
$this->assertEquals(200, $project['headers']['status-code']);
$this->assertIsArray($project['body']['labels']);
$this->assertCount(2, $project['body']['labels']);
$this->assertEquals('nonvip', $project['body']['labels'][0]);
$this->assertEquals('imagine', $project['body']['labels'][1]);

// Filter by labels
$projects = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('labels', ['nonvip'])->toString(),
]
]);
$this->assertEquals(200, $projects['headers']['status-code']);
$this->assertEquals(1, $projects['body']['total']);
$this->assertEquals($projectId, $projects['body']['projects'][0]['$id']);

$projects = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('labels', ['vip'])->toString(),
]
]);
$this->assertEquals(200, $projects['headers']['status-code']);
$this->assertEquals(0, $projects['body']['total']);

$projects = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('labels', ['imagine'])->toString(),
]
]);
$this->assertEquals(200, $projects['headers']['status-code']);
$this->assertEquals(1, $projects['body']['total']);
$this->assertEquals($projectId, $projects['body']['projects'][0]['$id']);

$projects = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('labels', ['nonvip', 'imagine'])->toString(),
]
]);
$this->assertEquals(200, $projects['headers']['status-code']);
$this->assertEquals(1, $projects['body']['total']);
$this->assertEquals($projectId, $projects['body']['projects'][0]['$id']);

// Setup: Second project with only imagine label
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'projectId' => ID::unique(),
'name' => 'Test project - Labels 2',
'teamId' => $teamId,
'region' => System::getEnv('_APP_REGION', 'default')
]);

$this->assertEquals(201, $project['headers']['status-code']);
$this->assertIsArray($project['body']['labels']);
$this->assertCount(0, $project['body']['labels']);
$projectId2 = $project['body']['$id'];

$project = $this->client->call(Client::METHOD_PUT, '/projects/' . $projectId2 . '/labels', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'labels' => ['vip', 'imagine']
]);
$this->assertEquals(200, $project['headers']['status-code']);
$this->assertIsArray($project['body']['labels']);
$this->assertCount(2, $project['body']['labels']);
$this->assertEquals('vip', $project['body']['labels'][0]);
$this->assertEquals('imagine', $project['body']['labels'][1]);

// List of imagine has both
$projects = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('labels', ['imagine'])->toString(),
]
]);
$this->assertEquals(200, $projects['headers']['status-code']);
$this->assertEquals(2, $projects['body']['total']);
$this->assertEquals($projectId, $projects['body']['projects'][0]['$id']);
$this->assertEquals($projectId2, $projects['body']['projects'][1]['$id']);

// List of vip only has second
$projects = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('labels', ['vip'])->toString(),
]
]);
$this->assertEquals(200, $projects['headers']['status-code']);
$this->assertEquals(1, $projects['body']['total']);
$this->assertEquals($projectId2, $projects['body']['projects'][0]['$id']);

// List of vip and imagine has second
$projects = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('labels', ['vip'])->toString(),
Query::contains('labels', ['imagine'])->toString(),
]
]);
$this->assertEquals(200, $projects['headers']['status-code']);
$this->assertEquals(1, $projects['body']['total']);
$this->assertEquals($projectId2, $projects['body']['projects'][0]['$id']);

// List of vip or imagine has second
$projects = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('labels', ['vip', 'imagine'])->toString(),
]
]);
$this->assertEquals(200, $projects['headers']['status-code']);
$this->assertEquals(2, $projects['body']['total']);
$this->assertEquals($projectId, $projects['body']['projects'][0]['$id']);
$this->assertEquals($projectId2, $projects['body']['projects'][1]['$id']);

// Cleanup
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));

$this->assertEquals(204, $response['headers']['status-code']);

$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId2, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));

$this->assertEquals(204, $response['headers']['status-code']);

$response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));

$this->assertEquals(204, $response['headers']['status-code']);
Comment thread
Meldiron marked this conversation as resolved.
}
}